--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null
+This directory contains a simple plugin that adds
+Subversion (http://subversion.tigris.org/) branch support to
+Bazaar (http://www.bazaar-vcs.org/)
+
+You will need a recent version of Bazaar-NG, most likely bzr.dev. If you have
+an older version of Bazaar and don't want to upgrade, try the svn-0.8 branch.
+This contains some hacks to make the Subversion plugin work with Bazaar 0.8.
+
+You also need the Subversion bindings for python, with my patch for svn_info_t,
+which has been in Subversions' repository since r19413.
+
+Simply place this directory in ~/.bazaar/plugins and you should be able
+to check out branches from Subversion using bzr.
+
+Jelmer Vernooij <jelmer@samba.org>, April 2006.
--- /dev/null
+# Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Support for foreign branches (Subversion)
+"""
+import sys
+import os.path
+import transport
+import dumpfile
+import format
+import branch
+import submit
+import workingtree
+
+sys.path.append(os.path.dirname(__file__))
+
+from bzrlib.transport import register_transport
+register_transport('svn:', transport.SvnRaTransport)
+register_transport('svn+', transport.SvnRaTransport)
+
+from bzrlib.bzrdir import BzrDirFormat
+
+BzrDirFormat.register_control_format(format.SvnFormat)
+
+BzrDirFormat.register_control_format(workingtree.SvnWorkingTreeDirFormat)
+
+BzrDirFormat.register_control_format(dumpfile.SvnDumpFileFormat)
+
+def test_suite():
+ from unittest import TestSuite, TestLoader
+ import tests
+
+ suite = TestSuite()
+
+ suite.addTest(tests.test_suite())
+
+ return suite
+
--- /dev/null
+# Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from bzrlib.branch import Branch, BranchFormat, BranchCheckResult, BzrBranch
+from bzrlib.errors import NotBranchError, NoWorkingTree, NoSuchRevision, \
+ NoSuchFile
+from bzrlib.inventory import Inventory, InventoryFile, InventoryDirectory, \
+ ROOT_ID
+from bzrlib.revision import Revision, NULL_REVISION
+from bzrlib.tree import Tree, EmptyTree
+from bzrlib.trace import mutter, note
+from bzrlib.workingtree import WorkingTree
+from bzrlib.delta import compare_trees
+import bzrlib
+
+import svn.core, svn.ra
+import os
+from libsvn.core import SubversionException
+
+
+svn.ra.initialize()
+
+_global_pool = svn.core.svn_pool_create(None)
+
+class FakeControlFiles(object):
+ def get_utf8(self, name):
+ raise NoSuchFile(name)
+
+
+class SvnBranch(Branch):
+ """Maps to a Branch in a Subversion repository """
+ def __init__(self, repos, branch_path):
+ self.repository = repos
+ self.branch_path = branch_path
+ self.base_revnum = svn.ra.get_latest_revnum(self.repository.ra)
+ self.control_files = FakeControlFiles()
+ self._generate_revnum_map()
+ self.base = "%s/%s" % (repos.url, branch_path)
+ self._format = SvnBranchFormat()
+ mutter("Connected to branch at %s" % branch_path)
+
+ def check(self):
+ return BranchCheckResult(self)
+
+ def path_from_file_id(self, revision_id, file_id):
+ """Generate a full Subversion path from a bzr file id.
+
+ :param revision_id:
+ :param file_id:
+ :return: Subversion
+ """
+ return self.base+"/"+self.filename_from_file_id(revision_id, file_id)
+
+ def _generate_revnum_map(self):
+ self._revision_history = []
+
+ def rcvr(paths, rev, author, date, message, pool):
+ revid = self.repository.generate_revision_id(rev, self.branch_path)
+ self._revision_history.append(revid)
+
+ self.repository._get_branch_log(self.branch_path.encode('utf8'), 0,
+ self.base_revnum, 0, False, rcvr)
+
+ def set_root_id(self, file_id):
+ raise NotImplementedError(self.set_root_id)
+
+ def get_root_id(self):
+ inv = self.repository.get_inventory(self.last_revision())
+ return inv.root.file_id
+
+ def _get_nick(self):
+ return self.branch_path
+
+ nick = property(_get_nick)
+
+ def abspath(self, name):
+ return self.base+"/"+name
+
+ def push_stores(self, branch_to):
+ raise NotImplementedError(self.push_stores)
+
+ def get_revision_inventory(self, revision_id):
+ raise NotImplementedError(self.get_revision_inventory)
+
+ def sign_revision(self, revision_id, gpg_strategy):
+ raise NotImplementedError(self.sign_revision)
+
+ def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
+ raise NotImplementedError(self.store_revision_signature)
+
+ def set_revision_history(self, rev_history):
+ raise NotImplementedError(self.set_revision_history)
+
+ def set_push_location(self, location):
+ raise NotImplementedError(self.set_push_location)
+
+ def get_push_location(self):
+ # get_push_location not supported on Subversion
+ return None
+
+ def revision_history(self):
+ return self._revision_history
+
+ def has_revision(self, revision_id):
+ return self.revision_history().has_key(revision_id)
+
+ def get_parents(self, revision_id):
+ revnum = self.get_revnum(revision_id)
+ parents = []
+ if not revision_id is None:
+ parent_id = self.revnum_map[revnum.value.number-1]
+ if not parent_id is None:
+ parents.append(parent_id)
+ # FIXME: Figure out if there are any merges here and where they come
+ # from
+ return parents
+
+ def get_ancestry(self, revision_id):
+ try:
+ i = self.revision_history().index(revision_id)
+ except ValueError:
+ raise NoSuchRevision(revision_id, self)
+
+ # FIXME: Figure out if there are any merges here and where they come
+ # from
+ return self.revision_history()[0:i+1]
+
+ def pull(self, source, overwrite=False):
+ raise NotImplementedError(self.pull)
+
+ def update_revisions(self, other, stop_revision=None):
+ raise NotImplementedError(self.update_revisions)
+
+ def pullable_revisions(self, other, stop_revision):
+ raise NotImplementedError(self.pullable_revisions)
+
+ # The remote server handles all this for us
+ def lock_write(self):
+ pass
+
+ def lock_read(self):
+ pass
+
+ def unlock(self):
+ pass
+
+ def get_parent(self):
+ return None
+
+ def set_parent(self, url):
+ raise NotImplementedError(self.set_parent,
+ 'can not change parent of SVN branch')
+
+ def get_transaction(self):
+ raise NotImplementedError(self.get_transaction)
+
+ def append_revision(self, *revision_ids):
+ # FIXME: raise NotImplementedError(self.append_revision)
+ pass
+
+ def get_physical_lock_status(self):
+ return False
+
+ def sprout(self, to_bzrdir, revision_id=None):
+ result = BranchFormat.get_default_format().initialize(to_bzrdir)
+ self.copy_content_into(result, revision_id=revision_id)
+ result.set_parent(self.bzrdir.root_transport.base)
+ return result
+
+ def copy_content_into(self, destination, revision_id=None):
+ new_history = self.revision_history()
+ if revision_id is not None:
+ try:
+ new_history = new_history[:new_history.index(revision_id) + 1]
+ except ValueError:
+ rev = self.repository.get_revision(revision_id)
+ new_history = rev.get_history(self.repository)[1:]
+ destination.set_revision_history(new_history)
+ parent = self.get_parent()
+ if parent:
+ destination.set_parent(parent)
+
+ def submit(self, from_branch, stop_revision):
+ if stop_revision is None:
+ stop_revision = from_branch.last_revision()
+
+ revisions = self.missing_revisions(from_branch, \
+ from_branch.revision_id_to_revno(stop_revision))
+
+ for revid in revisions:
+ rev = from_branch.repository.get_revision(revid)
+ self.commit(rev.message)
+
+ print revisions
+
+
+class SvnBranchFormat(BranchFormat):
+ """ Branch format for Subversion Branches."""
+ def __init__(self):
+ BranchFormat.__init__(self)
+
+ def get_format_description(self):
+ """See Branch.get_format_description."""
+ return 'Subversion Smart Server'
+
+ def get_format_string(self):
+ return 'Subversion Smart Server'
+
+ def initialize(self, to_bzrdir):
+ raise NotImplementedError(self.initialize)
+
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import svn.ra
+import svn.delta
+
+from bzrlib.repository import CommitBuilder
+from bzrlib.errors import UnsupportedOperation, BzrError
+
+class SvnCommitBuilder(CommitBuilder):
+ def __init__(self, repository, branch, parents, config, revprops):
+ super(SvnCommitBuilder, self).__init__(repository, parents,
+ config, None, None, None, revprops, None)
+ self.branch = branch
+
+ # TODO: Allow revision id to be specified, but only if it
+ # matches the format for Subversion revision ids, the UUID
+ # matches and the revnum is in the future. Set the
+ # revision num on the delta editor using set_target_revision
+
+ def _generate_revision_if_needed(self):
+ pass
+
+ def set_message(self, message):
+ self.message = message
+
+ def finish_inventory(self):
+ # Subversion doesn't have an inventory
+ pass
+
+ def record_entry_contents(self, ie, parent_invs, path, tree):
+ # Subversion doesn't have an inventory
+ pass
+
+ def modified_file_text(self, file_id, file_parents,
+ get_content_byte_lines, text_sha1=None,
+ text_size=None):
+ # FIXME
+ pass
+
+ def modified_link(self, file_id, file_parents, link_target):
+ # FIXME
+ pass
+
+ def modified_directory(self, file_id, file_parents):
+ # FIXME
+ pass
+
+ def commit(self):
+ def done(info, pool):
+ if not info.post_commit_err is None:
+ raise BzrError(info.post_commit_err)
+
+ self.revnum = info.revision
+
+ editor, editor_baton = svn.ra.get_commit_editor2(
+ self.repository.ra, self.message, done, None, False)
+
+ root = svn.delta.editor_invoke_open_root(editor, editor_baton, 4)
+
+ svn.delta.editor_invoke_close_edit(editor, editor_baton)
+
+ # Throw away the cache of revision ids
+ self.branch._generate_revnum_map()
+
+ return self.repository.generate_revision_id(self.revnum,
+ self.branch.branch_path)
--- /dev/null
+# Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from transport import SvnRaTransport
+from format import SvnRemoteAccess
+
+import bzrlib
+from bzrlib.bzrdir import BzrDirFormat, BzrDir
+from bzrlib.errors import NotBranchError
+from bzrlib.inventory import Inventory
+from bzrlib.lockable_files import TransportLock
+from bzrlib.progress import DummyProgress
+import bzrlib.urlutils as urlutils
+import bzrlib.osutils as osutils
+from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
+
+import tempfile
+from cStringIO import StringIO
+
+import svn.repos, svn.core
+from libsvn.core import SubversionException
+
+class SvnDumpFile(SvnRemoteAccess):
+ def __init__(self, nested_transport, format):
+ self.tmp_repos = None
+
+ transport = nested_transport
+
+ dumpfile = None
+
+ while not dumpfile:
+ last_name = urlutils.basename(transport.base)
+ parent_transport = transport
+ transport = transport.clone('..')
+ try:
+ dumpfile = transport.get(last_name)
+ except:
+ pass
+
+ repos_path = parent_transport.relpath(nested_transport.base)
+
+ self.tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
+ repos = svn.repos.create(self.tmp_repos, '', '', None, None)
+ try:
+ svn.repos.load_fs2(repos, dumpfile, StringIO(),
+ svn.repos.load_uuid_default, '', 0, 0, None)
+ except SubversionException, (svn.core.SVN_ERR_STREAM_MALFORMED_DATA, _):
+ raise NotBranchError(path=nested_transport.base)
+
+ svn_url = 'svn+file://%s/%s' % (self.tmp_repos, repos_path)
+ remote_transport = SvnRaTransport(svn_url)
+
+ super(SvnDumpFile, self).__init__(remote_transport, format)
+
+ def __del__(self):
+ if self.tmp_repos:
+ osutils.rmtree(self.tmp_repos)
+ self.tmp_repos = None
+
+class SvnDumpFileFormat(BzrDirFormat):
+ _lock_class = TransportLock
+
+ @classmethod
+ def probe_transport(klass, transport):
+ format = klass()
+
+ # FIXME: This is way inefficient over remote transports..
+ if SvnDumpFile(transport, format):
+ return format
+
+ raise NotBranchError(path=transport.base)
+
+ def _open(self, transport):
+ return SvnDumpFile(transport, self)
+
+ def get_format_string(self):
+ return 'Subversion Dump File'
+
+ def get_format_description(self):
+ return 'Subversion Dump File'
+
+ def initialize(self,url):
+ raise NotImplementedError(SvnFormat.initialize)
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from bzrlib.bzrdir import BzrDirFormat, BzrDir
+from repository import SvnRepository
+from branch import SvnBranch
+from libsvn.core import SubversionException
+from bzrlib.errors import NotBranchError, NotLocalUrl
+from bzrlib.lockable_files import TransportLock
+import svn.core
+from transport import SvnRaTransport
+
+class SvnRemoteAccess(BzrDir):
+ def __init__(self, _transport, _format):
+ self.root_transport = self.transport = _transport
+ self._format = _format
+
+ assert isinstance(_transport, SvnRaTransport)
+
+ self.url = _transport.base
+ self.branch_path = _transport.path
+
+ def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
+ raise NotImplementedError(SvnRemoteAccess.clone)
+
+ def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
+ # FIXME: honor force_new_repo
+ result = BzrDirFormat.get_default_format().initialize(url)
+ repo = self.open_repository()
+ result_repo = repo.clone(result, revision_id, basis)
+ branch = self.open_branch()
+ branch.sprout(result, revision_id)
+ result.create_workingtree()
+ return result
+
+ def open_repository(self):
+ repos = SvnRepository(self, self.transport.root_url)
+ repos._format = self._format
+ return repos
+
+ # Subversion has all-in-one, so a repository is always present
+ find_repository = open_repository
+
+ # Working trees never exist on Subversion repositories
+ def open_workingtree(self):
+ raise NotLocalUrl(self.url)
+
+ def create_workingtree(self, revision_id=None):
+ raise NotImplementedError(self.create_workingtree)
+
+ def open_branch(self, unsupported=True):
+ repos = self.open_repository()
+
+ try:
+ branch = SvnBranch(repos, self.branch_path)
+ except SubversionException, (msg, num):
+ if num == svn.core.SVN_ERR_RA_ILLEGAL_URL or \
+ num == svn.core.SVN_ERR_WC_NOT_DIRECTORY or \
+ num == svn.core.SVN_ERR_RA_NO_REPOS_UUID or \
+ num == svn.core.SVN_ERR_RA_SVN_REPOS_NOT_FOUND or \
+ num == svn.core.SVN_ERR_FS_NOT_FOUND or \
+ num == svn.core.SVN_ERR_RA_DAV_REQUEST_FAILED:
+ raise NotBranchError(path=self.url)
+ raise
+
+ branch.bzrdir = self
+ return branch
+
+class SvnFormat(BzrDirFormat):
+ _lock_class = TransportLock
+
+ @classmethod
+ def probe_transport(klass, transport):
+ format = klass()
+
+ if isinstance(transport, SvnRaTransport):
+ return format
+
+ raise NotBranchError(path=transport.base)
+
+ def _open(self, transport):
+ return SvnRemoteAccess(transport, self)
+
+ def get_format_string(self):
+ return 'Subversion Smart Server'
+
+ def get_format_description(self):
+ return 'Subversion Smart Server'
+
+ def initialize(self, url):
+ raise NotImplementedError(self.initialize)
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from bzrlib.branch import BranchCheckResult
+from bzrlib.config import config_dir
+from bzrlib.repository import Repository
+from bzrlib.lockable_files import LockableFiles, TransportLock
+from bzrlib.trace import mutter
+from bzrlib.revision import Revision
+from bzrlib.errors import NoSuchRevision, InvalidRevisionId, BzrError
+from bzrlib.progress import ProgressBar
+from bzrlib.inventory import Inventory, InventoryFile, InventoryDirectory, \
+ ROOT_ID
+from libsvn.core import SubversionException
+import svn.core
+import os
+import pickle
+import bzrlib
+import branch
+from cStringIO import StringIO
+from bzrlib.graph import Graph
+
+cache_dir = os.path.join(config_dir(), 'svn-cache')
+
+class SvnLogCache:
+ def __init__(self, ra, uuid, to_revnum):
+ cache_file = os.path.join(cache_dir, uuid)
+
+ # Try to load cache from file
+ try:
+ self.revisions = pickle.load(open(cache_file))
+ from_revnum = len(self.revisions)-1
+ except:
+ self.revisions = {}
+ from_revnum = 0
+
+ def rcvr(orig_paths, rev, author, date, message, pool):
+ self.pb.update('fetching svn revision info', rev, to_revnum)
+ paths = {}
+ if orig_paths is None:
+ orig_paths = {}
+ for p in orig_paths:
+ paths[p] = (orig_paths[p].action,
+ orig_paths[p].copyfrom_path,
+ orig_paths[p].copyfrom_rev)
+
+ self.revisions[rev] = {
+ 'paths': paths,
+ 'author': author,
+ 'date': date,
+ 'message': message
+ }
+ if from_revnum != to_revnum:
+ mutter('log -r %r:%r /' % (from_revnum, to_revnum))
+ self.pb = ProgressBar()
+
+ svn.ra.get_log(ra, ["/"], from_revnum, to_revnum, 0, True, True, rcvr)
+ self.pb.clear()
+ try:
+ os.mkdir(cache_dir)
+ except OSError:
+ pass
+ pickle.dump(self.revisions, open(cache_file, 'w'))
+
+ def get_log(self, paths, from_revno, to_revno, limit,
+ strict_node_history, rcvr):
+ num = 0
+ for i in range(0,abs(from_revno-to_revno)+1):
+ if to_revno < from_revno:
+ i = from_revno - i
+ else:
+ i = from_revno + i
+ if i == 0:
+ continue
+ rev = self.revisions[i]
+ changed_paths = {}
+ for p in rev['paths']:
+ for q in paths:
+ if p.startswith(q) or p[1:].startswith(q):
+ changed_paths[p] = rev['paths'][p]
+
+ if len(changed_paths) > 0:
+ num = num + 1
+ rcvr(changed_paths, i, rev['author'], rev['date'],
+ rev['message'], None)
+ if limit and num == limit:
+ return
+
+ def get_branch_log(self, branch_path, from_revno, to_revno, limit, \
+ strict_node_history, rcvr):
+ self.get_log([branch_path], from_revno, to_revno, limit,
+ strict_node_history, rcvr)
+
+class SvnInventoryFile(InventoryFile):
+ """Inventory entry that can either be a plain file or a
+ symbolic link. Avoids fetching data until necessary. """
+ def __init__(self, file_id, name, parent_id, repository, path, revnum,
+ has_props):
+ self.repository = repository
+ self.path = path
+ self.has_props = has_props
+ self.revnum = revnum
+ InventoryFile.__init__(self, file_id, name, parent_id)
+
+ def _get_sha1(self):
+ text = self.repository._get_file(self.path, self.revnum).read()
+ return bzrlib.osutils.sha_string(text)
+
+ def _get_executable(self):
+ if not self.has_props:
+ return False
+
+ value = self.repository._get_file_prop(self.path, self.revnum,
+ svn.core.SVN_PROP_EXECUTABLE)
+ if value and value == svn.core.SVN_PROP_EXECUTABLE_VALUE:
+ return True
+ return False
+
+ def _is_special(self):
+ if not self.has_props:
+ return False
+
+ value = self.repository._get_file_prop(self.path, self.revnum,
+ svn.core.SVN_PROP_SPECIAL)
+ if value and value == svn.core.SVN_PROP_SPECIAL_VALUE:
+ return True
+ return False
+
+ def _get_symlink_target(self):
+ if not self._is_special():
+ return None
+ data = self.repository._get_file(self.path, self.revnum).read()
+ if not data.startswith("link "):
+ raise BzrError("Improperly formatted symlink file")
+ return data[len("link "):]
+
+ def _get_kind(self):
+ if self._is_special():
+ return 'symlink'
+ return 'file'
+
+ # FIXME: we need a set function here because of InventoryEntry.__init__
+ def _phony_set(self, data):
+ pass
+
+ text_sha1 = property(_get_sha1, _phony_set)
+ executable = property(_get_executable, _phony_set)
+ symlink_target = property(_get_symlink_target, _phony_set)
+ kind = property(_get_kind, _phony_set)
+
+
+class SvnRepository(Repository):
+ """
+ Provides a simplified interface to a Subversion repository
+ by using the RA (remote access) API from subversion
+ """
+ def __init__(self, bzrdir, url):
+ _revision_store = None
+
+ control_files = LockableFiles(bzrdir.transport, '', TransportLock)
+ Repository.__init__(self, 'Subversion Smart Server', bzrdir,
+ control_files, None, None, None)
+
+ self.pool = svn.core.svn_pool_create(None)
+
+ self._scheme = bzrdir.transport._scheme
+ self.ra = bzrdir.transport.ra
+
+ self.uuid = svn.ra.get_uuid(self.ra)
+ self.base = self.url = url
+ self.fileid_map = {}
+ self.text_cache = {}
+ self.dir_cache = {}
+
+ assert self.url
+ assert self.uuid
+
+ mutter("Connected to repository at %s, UUID %s" % (
+ bzrdir.transport.svn_root_url, self.uuid))
+
+ self.logcache = SvnLogCache(self.ra, self.uuid,
+ svn.ra.get_latest_revnum(self.ra))
+
+ def __del__(self):
+ svn.core.svn_pool_destroy(self.pool)
+
+ def _check(self, revision_ids):
+ return BranchCheckResult(self)
+
+ def get_inventory(self, revision_id):
+ (path, revnum) = self.parse_revision_id(revision_id)
+ mutter('getting inventory %r for branch %r' % (revnum, path))
+
+ def read_directory(inv, id, path, revnum):
+
+ (props, dirents) = self._cache_get_dir(path, revnum)
+
+ recurse = {}
+
+ for child_name in dirents:
+ dirent = dirents[child_name]
+
+ if path:
+ child_path = "%s/%s" % (path, child_name)
+ else:
+ child_path = child_name
+
+ (child_id, revid) = self.path_to_file_id(dirent.created_rev,
+ child_path)
+ if dirent.kind == svn.core.svn_node_dir:
+ inventry = InventoryDirectory(child_id, child_name, id)
+ recurse[child_path] = dirent.created_rev
+ elif dirent.kind == svn.core.svn_node_file:
+ inventry = SvnInventoryFile(child_id, child_name, id, self,
+ child_path, dirent.created_rev, dirent.has_props)
+
+ else:
+ raise BzrError("Unknown entry kind for '%s': %s" %
+ (child_path, dirent.kind))
+
+ inventry.revision = revid
+ inv.add(inventry)
+
+ for child_path in recurse:
+ (child_id, _) = self.path_to_file_id(recurse[child_path],
+ child_path)
+ read_directory(inv, child_id, child_path, recurse[child_path])
+
+ inv = Inventory()
+
+ read_directory(inv, ROOT_ID, path, revnum)
+
+ return inv
+
+ def path_from_file_id(self, revision_id, file_id):
+ """Generate a Subversion path from a bzr file id."""
+
+ return self.fileid_map[revision_id][file_id]
+
+ def path_to_file_id(self, revnum, path):
+ """Generate a bzr file id from a Subversion file name.
+ Does not use svn.ra """
+
+ (path_branch, filename) = self._scheme.unprefix(path)
+
+ revision_id = self.generate_revision_id(revnum, path_branch)
+
+ if not self.fileid_map.has_key(revision_id):
+ self.fileid_map[revision_id] = {}
+
+ file_id = filename.replace("/", "@")
+ if file_id == "":
+ file_id = ROOT_ID
+
+ self.fileid_map[revision_id][file_id] = (path, revnum)
+ return (file_id, revision_id)
+
+ def all_revision_ids(self):
+ raise NotImplementedError(self.all_revision_ids)
+
+ def get_inventory_weave(self):
+ raise NotImplementedError(self.get_inventory_weave)
+
+ def get_ancestry(self, revision_id):
+ if revision_id is None: # FIXME: Is this correct?
+ return []
+ #FIXME: Find not just direct predecessors
+ # but also branches from which this branch was copied
+ (path, revnum) = self.parse_revision_id(revision_id)
+
+ self._ancestry = [None]
+
+ # FIXME: use get_file_revs() ?
+ def rcvr(paths, rev, author, date, message, pool):
+ revid = self.generate_revision_id(rev, path)
+ self._ancestry.append(revid)
+
+ try:
+ self._get_branch_log(path.encode('utf8'), 0, revnum - 1, 1,
+ False, rcvr)
+ except SubversionException, (_, num):
+ if num != svn.core.SVN_ERR_FS_NOT_FOUND:
+ raise
+
+ return self._ancestry
+
+ def has_revision(self, revision_id):
+ (path, revnum) = self.parse_revision_id(revision_id)
+
+ mutter("svn check_path -r%d %s" % (revnum, path))
+ kind = svn.ra.check_path(self.ra, path.encode('utf8'), revnum)
+
+ return (kind != svn.core.svn_node_none)
+
+ def revision_parents(self, revision_id):
+ (path, revnum) = self.parse_revision_id(revision_id)
+
+ parent_ids = []
+
+ # TODO: Use get_file_revs()
+ def rcvr(paths, rev, *args):
+ revid = self.generate_revision_id(rev, path)
+ parent_ids.append(revid)
+
+
+ try:
+ self._get_branch_log(path.encode('utf8'), revnum - 1, 0, 1, False, rcvr)
+ except SubversionException, (_, num):
+ # If this is the first revision, there are no parents
+ if num != svn.core.SVN_ERR_FS_NOT_FOUND:
+ raise
+
+ return parent_ids
+
+ def get_revision(self, revision_id):
+ if not revision_id or not isinstance(revision_id, basestring):
+ raise InvalidRevisionId(revision_id=revision_id, branch=self)
+
+ mutter("retrieving %s" % revision_id)
+ (path, revnum) = self.parse_revision_id(revision_id)
+
+ mutter('svn proplist -r %r' % revnum)
+ svn_props = svn.ra.rev_proplist(self.ra, revnum)
+
+ parent_ids = self.revision_parents(revision_id)
+
+ # Commit SVN revision properties to a Revision object
+ bzr_props = {}
+ rev = Revision(revision_id=revision_id,
+ parent_ids=parent_ids)
+
+ for name in svn_props:
+ bzr_props[name] = svn_props[name].decode('utf8')
+
+ rev.timestamp = 1.0 * svn.core.secs_from_timestr(
+ bzr_props[svn.core.SVN_PROP_REVISION_DATE], self.pool)
+ rev.timezone = None
+
+ rev.committer = bzr_props[svn.core.SVN_PROP_REVISION_AUTHOR]
+ rev.message = bzr_props[svn.core.SVN_PROP_REVISION_LOG]
+
+ rev.properties = bzr_props
+
+ rev.inventory_sha1 = self.get_inventory_sha1(revision_id)
+
+ return rev
+
+ def add_revision(self, rev_id, rev, inv=None, config=None):
+ raise NotImplementedError()
+
+ def fileid_involved_between_revs(self, from_revid, to_revid):
+ raise NotImplementedError()
+
+ def fileid_involved(self, last_revid=None):
+ raise NotImplementedError()
+
+ def fileids_altered_by_revision_ids(self, revision_ids):
+ ranges = {}
+ interested = {}
+
+ # First, figure out for which revisions to fetch
+ # the logs. Keeps the range as narrow as possible to
+ # save bandwidth (and thus increase speed)
+ for revid in revision_ids:
+ (path, revnum) = self.parse_revision_id(revid)
+
+ if not ranges.has_key(path):
+ ranges[path] = (revnum, revnum)
+ interested[path] = [revnum]
+ else:
+ (min, max) = ranges[path]
+
+ if revnum < min:
+ min = revnum
+ if revnum > max:
+ max = revnum
+
+ interested.append(revnum)
+
+ result = {}
+
+ def rcvr(paths, revnum, *args):
+ if not revnum in interested[self._tmp]:
+ return
+ for path in paths:
+ (file_id, revid) = self.path_to_file_id(revnum, path)
+ if not result.has_key(file_id):
+ result[file_id] = []
+ result[file_id].append(revid)
+
+ for path in ranges:
+ self._tmp = path
+ (min, max) = ranges[path]
+ self._get_branch_log(path.encode('utf8'), min, max, 0, False, rcvr)
+
+ return result
+
+ def _get_log(self, paths, from_revno, to_revno, limit, strict_node_history,
+ rcvr):
+ self.logcache.get_log(paths, from_revno, to_revno, limit,
+ strict_node_history, rcvr)
+
+ def _get_branch_log(self, branch_path, from_revno, to_revno, limit, \
+ strict_node_history, rcvr):
+ self.logcache.get_branch_log(branch_path, from_revno, to_revno, limit,
+ strict_node_history, rcvr)
+
+
+ def fileid_involved_by_set(self, changes):
+ ids = []
+
+ for revid in changes:
+ pass #FIXME
+
+ return ids
+
+ def generate_revision_id(self, rev, path):
+ """ Generate a unambiguous revision id. Does not use svn.ra """
+ return "svn:%d@%s-%s" % (rev, self.uuid, path)
+
+ def parse_revision_id(self, revid):
+ assert revid
+ assert isinstance(revid, basestring)
+
+ if not revid.startswith("svn:"):
+ raise NoSuchRevision()
+
+ revid = revid[len("svn:"):]
+
+ at = revid.index("@")
+ fash = revid.rindex("-")
+ uuid = revid[at+1:fash]
+
+ if uuid != self.uuid:
+ raise NoSuchRevision()
+
+ return (revid[fash+1:], int(revid[0:at]))
+
+ def get_inventory_xml(self, revision_id):
+ return bzrlib.xml5.serializer_v5.write_inventory_to_string(
+ self.get_inventory(revision_id))
+
+ def get_inventory_sha1(self, revision_id):
+ return bzrlib.osutils.sha_string(self.get_inventory_xml(revision_id))
+
+ def get_revision_xml(self, revision_id):
+ return bzrlib.xml5.serializer_v5.write_revision_to_string(
+ self.get_revision(revision_id))
+
+ def get_revision_sha1(self, revision_id):
+ return bzrlib.osutils.sha_string(self.get_revision_xml(revision_id))
+
+ def has_signature_for_revision_id(self, revision_id):
+ return False # SVN doesn't store GPG signatures. Perhaps
+ # store in SVN revision property?
+
+ def get_signature_text(self, revision_id):
+ # SVN doesn't store GPG signatures
+ raise NoSuchRevision(self, revision_id)
+
+ def _cache_get_dir(self, path, revnum):
+ if self.dir_cache.has_key(path) and \
+ self.dir_cache[path].has_key(revnum):
+ return self.dir_cache[path][revnum]
+
+ mutter("svn ls -r %d '%r'" % (revnum, path))
+
+ (dirents, _, props) = svn.ra.get_dir2(
+ self.ra, path.encode('utf8'),
+ revnum, svn.core.SVN_DIRENT_KIND
+ + svn.core.SVN_DIRENT_CREATED_REV
+ + svn.core.SVN_DIRENT_HAS_PROPS, self.pool)
+
+ if not self.dir_cache.has_key(path):
+ self.dir_cache[path] = {}
+
+ self.dir_cache[path][revnum] = (props, dirents)
+
+ return self.dir_cache[path][revnum]
+
+ def _cache_get_file(self, path, revnum):
+ if self.text_cache.has_key(path) and \
+ self.text_cache[path].has_key(revnum):
+ return self.text_cache[path][revnum]
+
+ stream = StringIO()
+ mutter('svn getfile -r %r %s' % (revnum, path))
+ (realrevnum, props) = svn.ra.get_file(self.ra, path.encode('utf8'),
+ revnum, stream, self.pool)
+ if not self.text_cache.has_key(path):
+ self.text_cache[path] = {}
+
+ self.text_cache[path][revnum] = (props, stream)
+ return self.text_cache[path][revnum]
+
+ def _get_file_prop(self, path, revnum, name):
+ (props, _) = self._cache_get_file(path, revnum)
+ if props.has_key(name):
+ return props[name]
+ return None
+
+ def _get_file(self, path, revnum):
+ (_, stream) = self._cache_get_file(path, revnum)
+ stream.seek(0)
+ return stream
+
+ def get_revision_graph(self, revision_id):
+ if revision_id is None:
+ raise NotImplementedError()
+
+ (path, revnum) = self.parse_revision_id(revision_id)
+
+ self._previous = revision_id
+ self._ancestry = {}
+
+ def rcvr(paths, rev, author, date, message, pool):
+ revid = self.generate_revision_id(rev, path)
+ self._ancestry[self._previous] = [revid]
+ self._previous = revid
+
+ try:
+ self._get_branch_log(path.encode('utf8'), revnum - 1, 0, 0, False, rcvr)
+ except SubversionException, (_, num):
+ if num != svn.core.SVN_ERR_FS_NOT_FOUND:
+ raise
+
+ self._ancestry[self._previous] = []
+
+ return self._ancestry
+
+ def is_shared(self):
+ """Return True if this repository is flagged as a shared repository."""
+ return True
+
+ def get_physical_lock_status(self):
+ return False
+
+ def copy_content_into(self, destination, revision_id=None, basis=None):
+ pb = ProgressBar()
+
+ # Loop over all the revnums until revision_id
+ # (or youngest_revnum) and call destination.add_revision()
+ # or destination.add_inventory() each time
+
+ if revision_id is None:
+ path = ""
+ until_revnum = svn.ra.get_latest_revnum(self.ra)
+ else:
+ (path, until_revnum) = self.parse_revision_id(revision_id)
+
+ weave_store = destination.weave_store
+
+ current = {}
+
+ transact = destination.get_transaction()
+
+ changed = []
+
+ def rcvr(paths, revnum, author, date, message, pool):
+ changed.append((paths, revnum))
+ pb.update('receiving revision information', revnum, until_revnum)
+
+ self._get_log([path.encode('utf8')], 0, until_revnum, 0, False, rcvr)
+
+ for (paths, revnum) in changed:
+ pb.update('copying revision', revnum, until_revnum)
+ revid = self.generate_revision_id(revnum, path)
+ inv = self.get_inventory(revid)
+ rev = self.get_revision(revid)
+ destination.add_revision(revid, rev, inv)
+
+ #FIXME: use svn.ra.do_update
+ for item in paths:
+ (fileid, revid) = self.path_to_file_id(revnum, item)
+ branch_path = self.parse_revision_id(revid)[0]
+ if branch_path != path:
+ continue
+
+ if paths[item].action == 'A':
+ weave = weave_store.get_weave_or_empty(fileid, transact)
+ elif paths[item].action == 'M' or paths[item].action == 'R':
+ weave = weave_store.get_weave(fileid, transact)
+ elif paths[item].action == 'D':
+ continue
+ else:
+ raise BzrError("Unknown SVN action '%s'" %
+ paths[item].action)
+
+ parents = []
+ if current.has_key(fileid):
+ parents = [current[fileid]]
+
+ try:
+ stream = self._get_file(item, revnum)
+ except SubversionException, (_, num):
+ if num != svn.core.SVN_ERR_FS_NOT_FILE:
+ raise
+ stream = None
+
+ if stream:
+ stream.seek(0)
+ weave.add_lines(revid, parents, stream.readlines())
+
+ pb.clear()
+
+ def fetch(self, source, revision_id=None, pb=None):
+ raise NotImplementedError(self.fetch)
+
+ def get_commit_builder(self, branch, parents, config, timestamp=None,
+ timezone=None, committer=None, revprops=None,
+ revision_id=None):
+ if timestamp != None:
+ raise NotImplementedError(self.get_commit_builder,
+ "timestamp can not be user-specified for Subversion repositories")
+
+ if timezone != None:
+ raise NotImplementedError(self.get_commit_builder,
+ "timezone can not be user-specified for Subversion repositories")
+
+ if committer != None:
+ raise NotImplementedError(self.get_commit_builder,
+ "committer can not be user-specified for Subversion repositories")
+
+ if revision_id != None:
+ raise NotImplementedError(self.get_commit_builder,
+ "revision_id can not be user-specified for Subversion repositories")
+
+ from commit import SvnCommitBuilder
+ return SvnCommitBuilder(self, branch, parents, config, revprops)
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from bzrlib.errors import NoSuchFile
+
+class BranchingScheme:
+ """ Divides SVN repository data up into branches. Since there
+ is no proper way to do this, there are several subclasses of this class
+ each of which handles a particular convention that may be in use.
+ """
+ @staticmethod
+ def is_branch(name):
+ raise NotImplementedError
+
+ def unprefix(name):
+ raise NotImplementedError
+
+class DefaultBranchingScheme:
+ @staticmethod
+ def is_branch(name):
+ parts = name.split("/")
+ if len(parts) == 1 and parts[0] == "trunk":
+ return True
+
+ if len(parts) == 2 and (parts[0] == "branches" or parts[0] == "tags"):
+ return True
+
+ return False
+
+ @staticmethod
+ def unprefix(path):
+ parts = path.lstrip("/").split("/")
+ if parts[0] == "trunk" or parts[0] == "hooks":
+ return (parts[0], "/".join(parts[1:]))
+ elif parts[0] == "tags" or parts[0] == "branches":
+ return ("/".join(parts[0:2]), "/".join(parts[2:]))
+ else:
+ raise BzrError("Unable to unprefix path %s" % path)
+
+class NoBranchingScheme:
+ @staticmethod
+ def is_branch(name):
+ parts = name.split("/")
+ return len(parts) == 0
+
+ @staticmethod
+ def unprefix(path):
+ return ("", path)
+
--- /dev/null
+#!/usr/bin/env python2.4
+
+from distutils.core import setup
+
+setup(name='bzr-svn',
+ description='Support for Subversion branches in Bazaar-NG',
+ keywords='plugin bzr svn',
+ version='0.1',
+ url='http://bazaar-vcs.org/BzrForeignBranches/Subversion',
+ download_url='http://samba.org/~jelmer/bzr/svn',
+ license='GPL',
+ author='Jelmer Vernooij',
+ author_email='jelmer@samba.org',
+ long_description="""
+ This plugin adds support for branching off Subversion
+ repositories.
+ """,
+ package_dir={'bzrlib.plugins.svn':'.'},
+ packages=['bzrlib.plugins.svn'])
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+# cmd_submit() based on cmd_commit() from bzrlib.builtins
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from bzrlib.commands import Command, register_command
+from bzrlib.builtins import tree_files
+from bzrlib.bzrdir import BzrDir
+from bzrlib.branch import Branch
+
+class cmd_submit(Command):
+ """Submit a revision to another (related) branch.
+
+ This is basically a push to a Subversion repository,
+ without the guarantee that a pull from that same repository
+ is a no-op.
+ """
+
+ takes_args = ["location?"]
+ takes_options = ["revision", "verbose"]
+
+ def run(self, revid=None, verbose=True, location=None):
+ (branch, _) = Branch.open_containing(".")
+
+ if location is None:
+ location = branch.get_parent()
+
+ if location is None:
+ raise BzrError("No location specified and no default location set on branch")
+
+ parent_branch = Branch.open(location)
+
+ if revid is None:
+ revid = branch.last_revision()
+
+ parent_branch.submit(branch, revid)
+
+register_command(cmd_submit)
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import svn.repos
+import os
+from bzrlib import osutils
+from bzrlib.bzrdir import BzrDir
+from bzrlib.tests import TestCaseInTempDir
+
+def test_suite():
+ from unittest import TestSuite, TestLoader
+ import tests.test_repos, tests.test_branch
+
+ suite = TestSuite()
+
+ suite.addTest(TestLoader().loadTestsFromModule(tests.test_repos))
+ suite.addTest(TestLoader().loadTestsFromModule(tests.test_branch))
+
+ return suite
import svn.ra, svn.repos, svn.wc
-class TestCaseWithSubversionRepository(TestCaseInTempDir):
+class TestCaseWithSvnRepository(TestCaseInTempDir):
"""A test case that provides the ability to build Subversion
repositories."""
- def make_repository(self, relpath):
+ def make_repository(self):
"""Create a repository.
:return: Handle to the repository.
"""
- abspath = os.path.join(self.test_dir, relpath)
- repos_url = "file://%s" % abspath
+ pass
- repos = svn.repos.create(abspath, '', '', None, None)
+ def make_fs(self, relpath):
+ """Create repository in relpath.
- return repos_url
-
- def make_remote_bzrdir(self, relpath):
- """Create a repository."""
-
- repos_url = self.make_repository(relpath)
-
- return BzrDir.open(repos_url)
-
- def make_local_bzrdir(self, relpath):
- """Create a repository and checkout."""
-
- repos_url = self.make_repository(relpath)
-
- ctx = svn.client.create_context()
-
- rev = svn.core.svn_opt_revision_t()
- rev.kind = svn.core.svn_opt_revision_head
-
- svn.client.checkout2(repos_url, relpath,
- rev, rev, True, False, ctx)
+ :param relpath: Path to create repository in.
+ :return: Subversion fs handle.
+ """
+ raise NotImplementedError(self.make_fs)
- return BzrDir.open(relpath)
-
def wc_commit(self, relpaths):
"""Commit current changes in specified working copy.
"""
raise NotImplementedError(self.build_tree)
- def make_wc(self, relpath, repos_url=None):
+ def make_wc(self, relpath, reppath=""):
"""Create a repository and a checkout. Return the checkout.
:param relpath: Optional relpath to check out if not the full
repository.
:return: Subversion wc handle.
"""
- # FIXME
- raise NotImplementedError(self.make_wc)
+ raise NotImplementedError(self.make_fs_and_wc)
def make_ra(self, relpath):
"""Create a repository and a ra connection to it.
:param relpath: Path to create repository at.
:return: The ra connection.
"""
-
- repos_url = self.make_repository(relpath)
-
- return svn.ra.open2(repos_url, svn.ra.callbacks2_t(), None)
+ raise NotImplementedError(self.make_ra)
def dumpfile(self, repos):
"""Create a dumpfile for the specified repository.
"""
raise NotImplementedError(self.dumpfile)
+ def make_wc(self, relpath=""):
+ """Create a repository and a checkout. Return the checkout.
+
+ :param relpath: Optional relpath to check out if not the full
+ repository.
+ """
+ raise NotImplementedError(self.make_wc)
+
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import svn
+import format
+from svntest import TestCaseWithSubversionRepository
+from bzrlib.bzrdir import BzrDir, BzrDirTestProviderAdapter, BzrDirFormat
+
+class WorkingSubversionBranch(TestCaseWithSubversionRepository):
+ def test_num_revnums(self):
+ bzrdir = self.make_local_bzrdir('a', 'ac')
+ branch = bzrdir.open_branch()
+ self.assertEqual(None, branch.last_revision())
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import svn
+import format
+from svntest import TestCaseWithSubversionRepository
+from bzrlib.bzrdir import BzrDir
+from bzrlib.tests.repository_implementations.test_repository import TestCaseWithRepository
+
+class TestSubversionRepositoryWorks(TestCaseWithSubversionRepository):
+ def test_format(self):
+ """ Test repository format is correct """
+ bzrdir = self.make_local_bzrdir('a', 'ac')
+ self.assertEqual(bzrdir._format.get_format_string(), \
+ "Subversion Local Checkout")
+
+ self.assertEqual(bzrdir._format.get_format_description(), \
+ "Subversion Local Checkout")
+
+ def test_url(self):
+ """ Test repository URL is kept """
+
+ bzrdir = self.make_local_bzrdir('b', 'bc')
+ self.assertTrue(isinstance(bzrdir, BzrDir))
+
+ def test_uuid(self):
+ """ Test UUID is retrieved correctly """
+ bzrdir = self.make_local_bzrdir('c', 'cc')
+ self.assertTrue(isinstance(bzrdir, BzrDir))
+ repository = bzrdir.open_repository()
+ fs = self.open_fs('c')
+ self.assertEqual(svn.fs.get_uuid(fs), repository.uuid)
--- /dev/null
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from bzrlib.transport import Transport
+from cStringIO import StringIO
+import svn.ra
+import os
+from bzrlib.errors import NoSuchFile, NotBranchError
+from scheme import NoBranchingScheme
+
+def _create_auth_baton(pool):
+ """ Create a Subversion authentication baton.
+
+ :param pool: An APR memory pool
+ """
+ import svn.client
+ # Give the client context baton a suite of authentication
+ # providers.h
+ providers = [
+ svn.client.svn_client_get_simple_provider(pool),
+ svn.client.svn_client_get_ssl_client_cert_file_provider(pool),
+ svn.client.svn_client_get_ssl_client_cert_pw_file_provider(pool),
+ svn.client.svn_client_get_ssl_server_trust_file_provider(pool),
+ svn.client.svn_client_get_username_provider(pool),
+ ]
+ return svn.core.svn_auth_open(providers, pool)
+
+
+# Don't run any tests on SvnTransport as it is not intended to be
+# a full implementation of Transport
+def get_test_permutations():
+ return []
+
+class SvnRaCallbacks(svn.ra.callbacks2_t):
+ def __init__(self):
+ svn.ra.callbacks2_t.__init__(self)
+ from branch import _global_pool
+ self.auth_baton = _create_auth_baton(_global_pool)
+
+ def open_tmp_file(self):
+ print "foo"
+
+ def progress(self, f, c, pool):
+ print "%s: %d / %d" % (self, f, c)
+
+
+class SvnRaTransport(Transport):
+ """Fake transport for Subversion-related namespaces. This implements
+ just as much of Transport as is necessary to fool Bazaar-NG. """
+ def __init__(self, url="", ra=None, root_url=None, scheme=None):
+ Transport.__init__(self, url)
+
+ if url.startswith("svn://") or \
+ url.startswith("svn+ssh://"):
+ self.svn_url = url
+ else:
+ self.svn_url = url[4:] # Skip svn+
+
+ # The SVN libraries don't like trailing slashes...
+ self.svn_url = self.svn_url.rstrip('/')
+
+ callbacks = SvnRaCallbacks()
+
+ if not ra:
+ self.ra = svn.ra.open2(self.svn_url.encode('utf8'), callbacks, None, None)
+ self.svn_root_url = svn.ra.get_repos_root(self.ra)
+ if self.svn_root_url != self.svn_url:
+ svn.ra.reparent(self.ra, self.svn_root_url.encode('utf8'))
+ else:
+ self.ra = ra
+ self.svn_root_url = root_url
+
+ self.root_url = self.svn_root_url
+ if not self.root_url.startswith("svn+"):
+ self.root_url = "svn+%s" % self.root_url
+
+ # Browsed above this directory
+ if not self.svn_url.startswith(self.svn_root_url):
+ raise NotBranchError(url)
+
+ self.path = self.svn_url[len(self.svn_root_url)+1:]
+
+ if not scheme:
+ scheme = NoBranchingScheme()
+
+ self._scheme = scheme
+ self.is_branch_root = scheme.is_branch(self.path)
+
+ def has(self, relpath):
+ return False
+
+ def get(self, relpath):
+ raise NoSuchFile(relpath)
+
+ def stat(self, relpath):
+ return os.stat('.') #FIXME
+
+ def listable(self):
+ return False
+
+ def lock_read(self, relpath):
+ class PhonyLock:
+ def unlock(self):
+ pass
+ return PhonyLock()
+
+ def clone(self, path):
+ parts = self.svn_url.split("/")
+
+ # FIXME: Handle more complicated paths
+ if path == '..':
+ parts.pop()
+ elif path != '.':
+ parts.append(path)
+
+ return SvnRaTransport("/".join(parts),ra=self.ra,root_url=self.svn_root_url)
--- /dev/null
+# Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import bzrlib
+from bzrlib.bzrdir import BzrDirFormat
+from bzrlib.errors import NotBranchError
+from bzrlib.inventory import Inventory
+from bzrlib.lockable_files import TransportLock
+from bzrlib.progress import DummyProgress
+from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
+
+from branch import _global_pool
+from format import SvnRemoteAccess, SvnFormat
+from repository import SvnRepository
+from transport import SvnRaTransport
+
+import os
+
+import svn.core, svn.wc
+from libsvn.core import SubversionException
+
+class SvnWorkingTree(WorkingTree):
+ """Implementation of WorkingTree that uses a Subversion
+ Working Copy for storage."""
+ def __init__(self, wc, branch):
+ self._format = SvnWorkingTreeFormat()
+ self.wc = wc
+ self.basedir = svn.wc.adm_access_path(self.wc)
+ self._branch = branch
+
+ def _get_inventory(self):
+ return Inventory()
+ raise NotImplementedError(self._get_inventory)
+
+ inventory = property(_get_inventory,
+ doc="Inventory of this Tree")
+
+ def lock_write(self):
+ pass
+
+ def lock_read(self):
+ pass
+
+ def unlock(self):
+ pass
+
+ def is_control_filename(self, path):
+ return path == '.svn'
+
+ def get_file_by_path(self, path):
+ raise NotImplementedError(self.get_file_by_path)
+
+ def get_file_lines(self, file_id):
+ raise NotImplementedError(self.get_file_lines)
+
+ def remove(self, files, verbose=False, to_file=None):
+ for file in files:
+ svn.wc.delete2(os.path.join(self.basedir, file), self.wc, None,
+ None, None)
+
+ def revert(self, files, old_tree=None, backups=True, pb=DummyProgress()):
+ if old_tree is not None:
+ # TODO: Also make sure old_tree != basis_tree
+ super(SvnWorkingTree, self).revert(files, old_tree, backups, pb)
+ return
+
+ svn.wc.revert([os.path.join(self.basedir, f) for f in files],
+ self.wc, False, False, None, None)
+
+ def move(self, from_paths, to_name):
+ revt = svn.core.svn_opt_revision_t()
+ revt.kind = svn.core.svn_opt_revision_unspecified
+ for entry in from_paths:
+ svn.wc.move(entry, revt, to_name, False, self.wc)
+
+ def rename_one(self, from_rel, to_rel):
+ # There is no difference between rename and move in SVN
+ self.move([from_rel], to_rel)
+
+ def read_working_inventory(self):
+ return self.inventory
+
+ def add(self, files, ids=None):
+ for f in files:
+ svn.wc.add2(f, False, self.wc)
+ if ids:
+ id = ids.pop()
+ if id:
+ svn.wc.prop_set2('bzr:id', id, f, False)
+
+ def pending_merges(self):
+ return []
+
+ def set_pending_merges(self):
+ raise NotImplementedError(self.set_pending_merges)
+
+ def unknowns(self):
+ raise NotImplementedError(self.unknowns)
+
+ def basis_tree(self):
+ raise NotImplementedError(self.basis_tree)
+
+ def pull(self, source, overwrite=False, stop_revision=None):
+ raise NotImplementedError(self.pull)
+
+ def extras(self):
+ raise NotImplementedError(self.extras)
+
+class SvnWorkingTreeFormat(WorkingTreeFormat):
+ def get_format_description(self):
+ return "Subversion Working Copy"
+
+ def initialize(self, a_bzrdir, revision_id=None):
+ # FIXME
+ raise NotImplementedError(self.initialize)
+
+ def open(self, a_bzrdir):
+ # FIXME
+ raise NotImplementedError(self.initialize)
+
+class OptimizedRepository(SvnRepository):
+ def revision_tree(self, revision_id):
+ # TODO: if revision id matches base revno,
+ # return working_tree.basis_tree()
+ return super(OptimizedRepository, self).revision_tree(revision_id)
+
+class SvnLocalAccess(SvnRemoteAccess):
+ def __init__(self, transport, format):
+ self.local_path = transport.base.rstrip("/")
+ if self.local_path.startswith("file://"):
+ self.local_path = self.local_path[len("file://"):]
+
+ self.wc = svn.wc.adm_open3(None, self.local_path, True, 100, None)
+ self.transport = transport
+
+ # Open related remote repository + branch
+ url, self.base_revno = svn.wc.get_ancestry(self.local_path, self.wc)
+ if not url.startswith("svn"):
+ url = "svn+" + url
+
+ remote_transport = SvnRaTransport(url)
+
+ super(SvnLocalAccess, self).__init__(remote_transport, format)
+
+ def __del__(self):
+ svn.wc.adm_close(self.wc)
+
+ def open_repository(self):
+ repos = OptimizedRepository(self, self.transport.root_url)
+ repos._format = self._format
+ return repos
+
+ def clone(self, path):
+ raise NotImplementedError(self.clone)
+
+ # Subversion has all-in-one, so a repository is always present
+ find_repository = open_repository
+
+ # Working trees never exist on Subversion repositories
+ def open_workingtree(self, _unsupported=False):
+ return SvnWorkingTree(self.wc, self.open_branch())
+
+ def create_workingtree(self):
+ raise NotImplementedError(SvnRemoteAccess.create_workingtree)
+
+
+class SvnWorkingTreeDirFormat(BzrDirFormat):
+ _lock_class = TransportLock
+
+ @classmethod
+ def probe_transport(klass, transport):
+ format = klass()
+
+ if transport.has('.svn'):
+ return format
+
+ raise NotBranchError(path=transport.base)
+
+ def _open(self, transport):
+ return SvnLocalAccess(transport, self)
+
+ def get_format_string(self):
+ return 'Subversion Local Checkout'
+
+ def get_format_description(self):
+ return 'Subversion Local Checkout'
+
+ def initialize(self,url):
+ raise NotImplementedError(self.initialize)