Add note about pushing new branches.
[jelmer/subvertpy.git] / format.py
1 # Copyright (C) 2006-2007 Jelmer Vernooij <jelmer@samba.org>
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16 """Subversion BzrDir formats."""
17
18 from bzrlib import urlutils
19 from bzrlib.bzrdir import BzrDirFormat, BzrDir, format_registry
20 from bzrlib.errors import (NotBranchError, NotLocalUrl, NoRepositoryPresent,
21                            NoWorkingTree, AlreadyBranchError)
22 from bzrlib.lockable_files import TransportLock
23 from bzrlib.transport.local import LocalTransport
24
25 from svn.core import SubversionException
26 import svn.core, svn.repos
27
28 from errors import NoSvnRepositoryPresent
29 from repository import SvnRepository
30 from transport import SvnRaTransport, bzr_to_svn_url, get_svn_ra_transport
31
32 def get_rich_root_format():
33     format = BzrDirFormat.get_default_format()
34     if format.repository_format.rich_root_data:
35         return format
36     # Default format does not support rich root data, 
37     # fall back to dirstate-with-subtree
38     format = format_registry.make_bzrdir('dirstate-with-subtree')
39     assert format.repository_format.rich_root_data
40     return format
41
42
43 class SvnRemoteAccess(BzrDir):
44     """BzrDir implementation for Subversion connections.
45     
46     This is used for all non-checkout connections 
47     to Subversion repositories.
48     """
49     def __init__(self, _transport, _format):
50         """See BzrDir.__init__()."""
51         _transport = get_svn_ra_transport(_transport)
52         self._format = _format
53         self.transport = None
54         self.root_transport = _transport
55
56         svn_url = bzr_to_svn_url(self.root_transport.base)
57         self.svn_root_url = _transport.get_repos_root()
58
59         assert svn_url.startswith(self.svn_root_url)
60         self.branch_path = svn_url[len(self.svn_root_url):]
61
62     def clone(self, url, revision_id=None, force_new_repo=False):
63         """See BzrDir.clone().
64
65         Not supported on Subversion connections.
66         """
67         raise NotImplementedError(SvnRemoteAccess.clone)
68
69     def sprout(self, url, revision_id=None, force_new_repo=False,
70             recurse='down', possible_transports=None):
71         """See BzrDir.sprout()."""
72         # FIXME: Use possible_transports
73         # FIXME: Use recurse
74         format = get_rich_root_format()
75         result = format.initialize(url)
76         repo = self.find_repository()
77         if force_new_repo:
78             result_repo = repo.clone(result, revision_id)
79         else:
80             try:
81                 result_repo = result.find_repository()
82                 result_repo.fetch(repo, revision_id=revision_id)
83             except NoRepositoryPresent:
84                 result_repo = repo.clone(result, revision_id)
85         branch = self.open_branch()
86         result_branch = branch.sprout(result, revision_id)
87         if result_branch.repository.make_working_trees():
88             result.create_workingtree()
89         return result
90
91     def open_repository(self, _unsupported=False):
92         """Open the repository associated with this BzrDir.
93         
94         :return: instance of SvnRepository.
95         """
96         if self.branch_path == "":
97             return SvnRepository(self, self.root_transport)
98         raise NoSvnRepositoryPresent(self.root_transport.base)
99
100     def find_repository(self):
101         """Open the repository associated with this BzrDir.
102         
103         :return: instance of SvnRepository.
104         """
105         transport = self.root_transport
106         if self.svn_root_url != transport.base:
107             transport = transport.clone_root()
108         return SvnRepository(self, transport, self.branch_path)
109
110     def open_workingtree(self, _unsupported=False,
111             recommend_upgrade=True):
112         """See BzrDir.open_workingtree().
113
114         Will always raise NotLocalUrl as this 
115         BzrDir can not be associated with working trees.
116         """
117         # Working trees never exist on remote Subversion repositories
118         raise NoWorkingTree(self.root_transport.base)
119
120     def create_workingtree(self, revision_id=None):
121         """See BzrDir.create_workingtree().
122
123         Will always raise NotLocalUrl as this 
124         BzrDir can not be associated with working trees.
125         """
126         raise NotLocalUrl(self.root_transport.base)
127
128     def needs_format_conversion(self, format=None):
129         """See BzrDir.needs_format_conversion()."""
130         # if the format is not the same as the system default,
131         # an upgrade is needed.
132         if format is None:
133             format = BzrDirFormat.get_default_format()
134         return not isinstance(self._format, format.__class__)
135
136     def import_branch(self, source, stop_revision=None):
137         """Create a new branch in this repository, possibly 
138         with the specified history, optionally importing revisions.
139         
140         :param source: Source branch
141         :param stop_revision: Tip of new branch
142         :return: Branch object
143         """
144         from commit import push_new
145         if stop_revision is None:
146             stop_revision = source.last_revision()
147         target_branch_path = self.branch_path.strip("/")
148         repos = self.find_repository()
149         full_branch_url = urlutils.join(repos.transport.base, 
150                                         target_branch_path)
151         if repos.transport.check_path(target_branch_path,
152             repos.transport.get_latest_revnum()) != svn.core.svn_node_none:
153             raise AlreadyBranchError(full_branch_url)
154         push_new(repos, target_branch_path, source, stop_revision)
155         branch = self.open_branch()
156         branch.pull(source, stop_revision=stop_revision)
157         return branch
158
159     def create_branch(self):
160         """See BzrDir.create_branch()."""
161         from branch import SvnBranch
162         repos = self.find_repository()
163
164         if self.branch_path != "":
165             # TODO: Set NULL_REVISION in SVN_PROP_BZR_BRANCHING_SCHEME
166             repos.transport.mkdir(self.branch_path.strip("/"))
167         elif repos.transport.get_latest_revnum() > 0:
168             # Bail out if there are already revisions in this repository
169             raise AlreadyBranchError(self.root_transport.base)
170         branch = SvnBranch(self.root_transport.base, repos, self.branch_path)
171         branch.bzrdir = self
172         return branch
173
174     def open_branch(self, unsupported=True):
175         """See BzrDir.open_branch()."""
176         from branch import SvnBranch
177         repos = self.find_repository()
178         branch = SvnBranch(self.root_transport.base, repos, self.branch_path)
179         branch.bzrdir = self
180         return branch
181
182     def create_repository(self, shared=False, format=None):
183         """See BzrDir.create_repository."""
184         return self.open_repository()
185
186
187 class SvnFormat(BzrDirFormat):
188     """Format for the Subversion smart server."""
189     _lock_class = TransportLock
190
191     def __init__(self):
192         super(SvnFormat, self).__init__()
193         from repository import SvnRepositoryFormat
194         self.repository_format = SvnRepositoryFormat()
195
196     @classmethod
197     def probe_transport(klass, transport):
198         format = klass()
199
200         try:
201             transport = get_svn_ra_transport(transport)
202         except SubversionException, (_, num):
203             if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL, \
204                        svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, \
205                        svn.core.SVN_ERR_BAD_URL):
206                 raise NotBranchError(path=transport.base)
207
208         if isinstance(transport, SvnRaTransport):
209             return format
210
211         raise NotBranchError(path=transport.base)
212
213     def _open(self, transport):
214         try: 
215             return SvnRemoteAccess(transport, self)
216         except SubversionException, (_, num):
217             if num == svn.core.SVN_ERR_RA_DAV_REQUEST_FAILED:
218                 raise NotBranchError(transport.base)
219             raise
220
221     def get_format_string(self):
222         return 'Subversion Smart Server'
223
224     def get_format_description(self):
225         return 'Subversion Smart Server'
226
227     def initialize_on_transport(self, transport):
228         """See BzrDir.initialize_on_transport()."""
229         if not isinstance(transport, LocalTransport):
230             raise NotImplementedError(self.initialize, 
231                 "Can't create Subversion Repositories/branches on "
232                 "non-local transports")
233
234         local_path = transport._local_base.rstrip("/")
235         svn.repos.create(local_path, '', '', None, None)
236         return self.open(get_svn_ra_transport(transport), _found=True)
237
238     def is_supported(self):
239         """See BzrDir.is_supported()."""
240         return True
241