Mercurial > hgsubversion
changeset 670:186f13b35d15
Merge backout of incorrect patch.
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Fri, 20 Aug 2010 20:37:54 -0500 |
parents | 6be7c9ace365 (diff) a8101fc49663 (current diff) |
children | e910e3ebd23b |
files | |
diffstat | 7 files changed, 173 insertions(+), 282 deletions(-) [+] |
line wrap: on
line diff
--- a/README +++ b/README @@ -38,55 +38,13 @@ part is that all the tests pass. .. _nose: http://code.google.com/p/python-nose/ -Basic Use ------------ -Get a new clone of an svn server:: +Further Reading +--------------- - $ hg clone <svn URI> [destination] +More information on how to use hgsubversion is available from within Mercurial +in the `subversion` help topic. To view it, use:: -Real example:: + $ hg help subversion - $ hg clone http://python-nose.googlecode.com/svn nose-hg - -Note: there are two slightly different ways of cloning -repositories. The most common desire is to have all the -branches/tags/trunk from the svn repo, in which case you should clone -from one level above trunk (as in the example above.) If you instead -want to clone just a single directory rather than the complete -branches/tags/trunk structure of the repo, clone the specific -directory path. In the example above, to get *only* trunk, you would -clone `http://python-nose.googlecode.com/svn/trunk`. - -Pull new revisions into an already-converted repo:: - - $ hg pull - -For more information, see ``hg help svn`` while in a converted repo. - -Support for ``svn:externals`` ------------------------------ -All ``svn:externals`` properties are serialized into a single -``.hgsvnexternals`` file having the following syntax:: - - [.] - common1 http://path/to/external/svn/repo1 - ...additional svn:externals properties lines... - [dir2] - common2 -r123 http://path/to/external/svn/repo2 - ...additional svn:externals properties lines... - -A header line in brackets specifies the directory the property applies -to, where '.' indicates the project root directory. The property content -follows the header, **with every content line being prefixed by a single -space**. Note that the property lines have a format identical to -svn:externals properties as used in Subversion, and do not support the -hgsubversion extended svn+http:// URL format. - -Issuing the command ``hg svn updateexternals`` with the -``.hgsvnexternals`` example above would fetch the latest revision of -repo1 into the subdirectory ./common1, and revision 123 of repo2 into -dir2/common2. Note that ``.hgsvnexternals`` must be tracked by Mercurial -before this will work. If ``.hgsvnexternals`` is created or changed, it -will not be pushed to the related Subversion repository, *but its -contents will be used to update ``svn:externals`` properties on the -related Subversion repository*. +The Restructured Text source for this topic is also available in the file +``hgsubverson/help/subversion.rst``.
--- a/hgsubversion/svnwrap/__init__.py +++ b/hgsubversion/svnwrap/__init__.py @@ -1,16 +1,9 @@ """This is a special package because it contains (or will contain, as of now) two parallel implementations of the same code. One implementation, the original, uses the SWIG Python bindings. That's great, but those leak RAM and have a few -other quirks. There are new, up-and-coming ctypes bindings for Subversion which -look more promising, and are portible backwards to 1.4's libraries. The goal is -to have this file automatically contain the "best" available implementation -without the user having to configure what is actually present. +other quirks. The goal is to have this file automatically contain the "best" +available implementation without the user having to configure what is actually +present. """ -#try: -# # we do __import__ here so that the correct items get pulled in. Otherwise -# # demandimport can make life difficult. -# __import__('csvn') -# from svn_ctypes_wrapper import * -#except ImportError, e: from svn_swig_wrapper import *
deleted file mode 100644 --- a/hgsubversion/svnwrap/svn_ctypes_wrapper.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Right now this is a dummy module, but it should wrap the ctypes API and -allow running this more easily without the SWIG bindings. -""" -from csvn import repos - -class Revision(object): - """Wrapper for a Subversion revision. - """ - def __init__(self, revnum, author, message, date, paths, strip_path=''): - self.revnum, self.author, self.message = revnum, author, message - # TODO parse this into a datetime - self.date = date - self.paths = {} - for p in paths: - self.paths[p[len(strip_path):]] = paths[p] - - def __str__(self): - return 'r%d by %s' % (self.revnum, self.author) - - -class SubversionRepo(object): - """Wrapper for a Subversion repository. - - This uses the SWIG Python bindings, and will only work on svn >= 1.4. - It takes a required param, the URL. - """ - def __init__(self, url=''): - self.svn_url = url - - self.init_ra_and_client() - self.uuid = ra.get_uuid(self.ra, self.pool) - repo_root = ra.get_repos_root(self.ra, self.pool) - # *will* have a leading '/', would not if we used get_repos_root2 - self.subdir = url[len(repo_root):] - if not self.subdir or self.subdir[-1] != '/': - self.subdir += '/' - - def init_ra_and_client(self): - # TODO(augie) need to figure out a way to do auth - self.repo = repos.RemoteRepository(self.svn_url) - - def HEAD(self): - raise NotImplementedError - HEAD = property(HEAD) - - def START(self): - return 0 - START = property(START) - - def branches(self): - """Get the branches defined in this repo assuming a standard layout. - """ - raise NotImplementedError - branches = property(branches) - - def tags(self): - """Get the current tags in this repo assuming a standard layout. - - This returns a dictionary of tag: (source path, source rev) - """ - raise NotImplementedError - tags = property(tags) - - def _get_copy_source(self, path, cached_head=None): - """Get copy revision for the given path, assuming it was meant to be - a copy of the entire tree. - """ - raise NotImplementedError - - def list_dir(self, dir, revision=None): - """List the contents of a server-side directory. - - Returns a dict-like object with one dict key per directory entry. - - Args: - dir: the directory to list, no leading slash - rev: the revision at which to list the directory, defaults to HEAD - """ - raise NotImplementedError - - def revisions(self, paths=None, start=None, stop=None, chunk_size=1000): - """Load the history of this repo. - - This is LAZY. It returns a generator, and fetches a small number - of revisions at a time. - - The reason this is lazy is so that you can use the same repo object - to perform RA calls to get deltas. - """ - # NB: you'd think this would work, but you'd be wrong. I'm pretty - # convinced there must be some kind of svn bug here. - #return self.fetch_history_at_paths(['tags', 'trunk', 'branches'], - # start=start) - # this does the same thing, but at the repo root + filtering. It's - # kind of tough cookies, sadly. - raise NotImplementedError - - def get_replay(self, revision, editor, oldest_rev_i_have=0): - raise NotImplementedError - - def get_unified_diff(self, path, revision, deleted=True, ignore_type=False): - raise NotImplementedError - - def get_file(self, path, revision): - raise NotImplementedError - - def proplist(self, path, revision, recurse=False): - raise NotImplementedError - -class SubversionRepoCanNotReplay(Exception): - """Exception raised when the svn server is too old to have replay. - """
--- a/hgsubversion/svnwrap/svn_swig_wrapper.py +++ b/hgsubversion/svnwrap/svn_swig_wrapper.py @@ -34,7 +34,7 @@ if current_bindings < required_bindings: (required_bindings + current_bindings)) def version(): - return '%d.%d.%d' % current_bindings + return '%d.%d.%d' % current_bindings, 'SWIG' class SubversionRepoCanNotReplay(Exception): """Exception raised when the svn server is too old to have replay. @@ -50,7 +50,7 @@ class SubversionConnectionException(Exce """ '''Default chunk size used in fetch_history_at_paths() and revisions().''' -_chunk_size = 1000 +chunk_size = 1000 # exported values ERR_FS_CONFLICT = core.SVN_ERR_FS_CONFLICT @@ -257,8 +257,8 @@ class SubversionRepo(object): path=urllib.quote(path) url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) self.ra = ra.open2(url, callbacks, - svn_config, self.pool) - except core.SubversionException, e: + svn_config, self.pool) + except SubversionException, e: if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED: msg = ('Subversion does not trust the SSL certificate for this ' 'site; please try running \'svn ls %s\' first.' @@ -278,10 +278,6 @@ class SubversionRepo(object): return ra.get_latest_revnum(self.ra, self.pool) HEAD = property(HEAD) - def START(self): - return 0 - START = property(START) - def last_changed_rev(self): try: holder = [] @@ -295,93 +291,13 @@ class SubversionRepo(object): self.pool) return holder[-1] - except core.SubversionException, e: + except SubversionException, e: if e.apr_err not in [core.SVN_ERR_FS_NOT_FOUND]: raise else: return self.HEAD last_changed_rev = property(last_changed_rev) - def branches(self): - """Get the branches defined in this repo assuming a standard layout. - - This method should be eliminated; this class does not have - sufficient knowledge to yield all known tags. - """ - branches = self.list_dir('branches').keys() - branch_info = {} - head=self.HEAD - for b in branches: - b_path = 'branches/%s' %b - hist_gen = self.revisions([b_path], stop=head) - hist = hist_gen.next() - source, source_rev = self._get_copy_source(b_path, cached_head=head) - # This if statement guards against projects that have non-ancestral - # branches by not listing them has branches - # Note that they probably are really ancestrally related, but there - # is just no way for us to know how. - if source is not None and source_rev is not None: - branch_info[b] = (source, source_rev, hist.revnum) - return branch_info - branches = property(branches) - - def tags(self): - """Get the current tags in this repo assuming a standard layout. - - This returns a dictionary of tag: (source path, source rev) - - This method should be eliminated; this class does not have - sufficient knowledge to yield all known tags. - """ - return self.tags_at_rev(self.HEAD) - tags = property(tags) - - def tags_at_rev(self, revision): - """Get the tags in this repo at the given revision, assuming a - standard layout. - - This returns a dictionary of tag: (source path, source rev) - - This method should be eliminated; this class does not have - sufficient knowledge to yield all known tags. - """ - try: - tags = self.list_dir('tags', revision=revision).keys() - except core.SubversionException, e: - if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: - return {} - raise - tag_info = {} - for t in tags: - tag_info[t] = self._get_copy_source('tags/%s' % t, - cached_head=revision) - return tag_info - - def _get_copy_source(self, path, cached_head=None): - """Get copy revision for the given path, assuming it was meant to be - a copy of the entire tree. - """ - if not cached_head: - cached_head = self.HEAD - hist_gen = self.revisions([path], stop=cached_head) - hist = hist_gen.next() - if hist.paths[path].copyfrom_path is None: - return None, None - source = hist.paths[path].copyfrom_path - source_rev = 0 - for p in hist.paths: - if not p.startswith(path): - continue - if hist.paths[p].copyfrom_rev: - # We assume that the revision of the source tree as it was - # copied was actually the revision of the highest revision - # copied item. This could be wrong, but in practice it will - # *probably* be correct - if source_rev < hist.paths[p].copyfrom_rev: - source_rev = hist.paths[p].copyfrom_rev - source = source[len(self.subdir):] - return source, source_rev - def list_dir(self, dir, revision=None): """List the contents of a server-side directory. @@ -400,8 +316,8 @@ class SubversionRepo(object): folders, props, junk = r return folders - def revisions(self, paths=None, start=None, stop=None, - chunk_size=_chunk_size): + def revisions(self, paths=None, start=0, stop=0, + chunk_size=chunk_size): """Load the history of this repo. This is LAZY. It returns a generator, and fetches a small number @@ -412,8 +328,6 @@ class SubversionRepo(object): """ if paths is None: paths = [''] - if not start: - start = self.START if not stop: stop = self.HEAD while stop > start: @@ -540,7 +454,7 @@ class SubversionRepo(object): try: ra.replay(self.ra, revision, oldest_rev_i_have, True, e_ptr, e_baton, self.pool) - except core.SubversionException, e: #pragma: no cover + except SubversionException, e: #pragma: no cover # can I depend on this number being constant? if (e.apr_err == core.SVN_ERR_RA_NOT_IMPLEMENTED or e.apr_err == core.SVN_ERR_UNSUPPORTED_FEATURE): @@ -588,7 +502,7 @@ class SubversionRepo(object): client.diff3([], url2, optrev(other_rev), url, optrev(revision), True, True, deleted, ignore_type, 'UTF-8', out, err, self.client_context, self.pool) - except core.SubversionException, e: + except SubversionException, e: # "Can't write to stream: The handle is invalid." # This error happens systematically under Windows, possibly # related to file handles being non-write shareable by default. @@ -627,7 +541,7 @@ class SubversionRepo(object): info = info[-1] mode = ("svn:executable" in info) and 'x' or '' mode = ("svn:special" in info) and 'l' or mode - except core.SubversionException, e: + except SubversionException, e: notfound = (core.SVN_ERR_FS_NOT_FOUND, core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) if e.args[1] in notfound: # File not found @@ -649,7 +563,7 @@ class SubversionRepo(object): try: pl = client.proplist2(rpath, rev, rev, False, self.client_context, self.pool) - except core.SubversionException, e: + except SubversionException, e: # Specified path does not exist at this revision if e.apr_err == core.SVN_ERR_NODE_UNKNOWN_KIND: raise IOError(errno.ENOENT, e.args[0]) @@ -671,7 +585,7 @@ class SubversionRepo(object): rev = optrev(revision) try: entries = client.ls(rpath, rev, True, self.client_context, pool) - except core.SubversionException, e: + except SubversionException, e: if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: raise IOError(errno.ENOENT, '%s cannot be found at r%d' % (dirpath, revision))
--- a/hgsubversion/wrappers.py +++ b/hgsubversion/wrappers.py @@ -31,8 +31,11 @@ def version(orig, ui, *args, **opts): svn = opts.pop('svn', None) orig(ui, *args, **opts) if svn: - ui.status('\nsvn bindings: %s\n' % svnwrap.version()) + svnversion, bindings = svnwrap.version() + ui.status('\n') ui.status('hgsubversion: %s\n' % util.version(ui)) + ui.status('Subversion: %s\n' % svnversion) + ui.status('bindings: %s\n' % bindings) def parents(orig, ui, repo, *args, **opts):
new file mode 100644 --- /dev/null +++ b/tests/fixtures/move_into_trunk.svndump @@ -0,0 +1,147 @@ +SVN-fs-dump-format-version: 2 + +UUID: 124c25c6-3b41-4c6b-a3a3-a0c9809dc9cb + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2010-08-14T19:08:00.090868Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 10 +Add files. +K 10 +svn:author +V 9 +anonymous +K 8 +svn:date +V 27 +2010-08-14T19:09:04.290181Z +PROPS-END + +Node-path: A +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 16 +Text-content-md5: 98475036dc73d318982805bf4b16e8b2 +Text-content-sha1: d7dff2b1ef48b9c20c23d7b3a08b557957cec3c9 +Content-length: 26 + +PROPS-END +This is a file. + + +Node-path: B +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 22 +Text-content-md5: d54ff73404ed6041a3bd66850b061bff +Text-content-sha1: 4bdb40dfd6ec75cb730e678b5d7786e30170c5fb +Content-length: 32 + +PROPS-END +This is another file. + + +Revision-number: 2 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 10 +Add trunk. +K 10 +svn:author +V 9 +anonymous +K 8 +svn:date +V 27 +2010-08-14T19:09:23.652445Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 3 +Prop-content-length: 113 +Content-length: 113 + +K 7 +svn:log +V 12 +Move a file. +K 10 +svn:author +V 9 +anonymous +K 8 +svn:date +V 27 +2010-08-14T19:09:33.945291Z +PROPS-END + +Node-path: trunk/A +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: A +Text-copy-source-md5: 98475036dc73d318982805bf4b16e8b2 +Text-copy-source-sha1: d7dff2b1ef48b9c20c23d7b3a08b557957cec3c9 + + +Node-path: A +Node-action: delete + + +Revision-number: 4 +Prop-content-length: 122 +Content-length: 122 + +K 7 +svn:log +V 18 +Move another file. +K 10 +svn:author +V 9 +anonymous +K 8 +svn:date +V 27 +2010-08-14T19:10:03.135714Z +PROPS-END + +Node-path: trunk/B +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: B +Text-copy-source-md5: d54ff73404ed6041a3bd66850b061bff +Text-copy-source-sha1: 4bdb40dfd6ec75cb730e678b5d7786e30170c5fb + + +Node-path: B +Node-action: delete + +
--- a/tests/test_svnwrap.py +++ b/tests/test_svnwrap.py @@ -53,18 +53,6 @@ class TestBasicRepoLayout(unittest.TestC revs = list(self.repo.revisions(start=3)) self.assertEqual(len(revs), 4) - - def test_branches(self): - self.assertEqual(self.repo.branches.keys(), ['crazy', 'more_crazy']) - self.assertEqual(self.repo.branches['crazy'], ('trunk', 2, 4)) - self.assertEqual(self.repo.branches['more_crazy'], ('trunk', 5, 7)) - - - def test_tags(self): - tags = self.repo.tags - self.assertEqual(tags.keys(), ['rev1']) - self.assertEqual(tags['rev1'], ('trunk', 2)) - class TestRootAsSubdirOfRepo(TestBasicRepoLayout): def setUp(self): self.tmpdir = tempfile.mkdtemp('svnwrap_test')