Use write groups (compatibility with packs branch).
[jelmer/subvertpy.git] / __init__.py
1 # Copyright (C) 2005-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
17 """
18 Support for Subversion branches
19 """
20 import bzrlib
21 from bzrlib.bzrdir import BzrDirFormat, format_registry
22 from bzrlib.commands import Command, register_command, display_command, Option
23 from bzrlib.help_topics import topic_registry
24 from bzrlib.lazy_import import lazy_import
25 from bzrlib.trace import warning, mutter
26 from bzrlib.transport import register_lazy_transport, register_transport_proto
27 from bzrlib.repository import InterRepository
28
29 lazy_import(globals(), """
30 import branch
31 import commit
32 import fetch 
33 import format
34 import workingtree
35 """)
36
37 # versions ending in 'exp' mean experimental mappings
38 # versions ending in 'dev' mean development version
39 # versions ending in 'final' mean release (well tested, etc)
40 version_info = (0, 4, 3, 'dev', 0)
41
42 if version_info[3] == 'final':
43     version_string = '%d.%d.%d' % version_info[:3]
44 else:
45     version_string = '%d.%d.%d%s%d' % version_info
46 __version__ = version_string
47
48 COMPATIBLE_BZR_VERSIONS = [(0, 90), (0, 91)]
49
50 def check_bzrlib_version(desired):
51     """Check that bzrlib is compatible.
52
53     If version is < all compatible version, assume incompatible.
54     If version is compatible version + 1, assume compatible, with deprecations
55     Otherwise, assume incompatible.
56     """
57     import bzrlib
58     bzrlib_version = bzrlib.version_info[:2]
59     if (bzrlib_version in desired or 
60         ((bzrlib_version[0], bzrlib_version[1]-1) in desired and 
61          bzrlib.version_info[3] == 'dev')):
62         return
63     if bzrlib_version < desired[0]:
64         warning('Installed bzr version %s is too old to be used with bzr-svn'
65                 ' %s.' % (bzrlib.__version__, __version__))
66         # Not using BzrNewError, because it may not exist.
67         raise Exception, ('Version mismatch', desired)
68     else:
69         warning('bzr-svn is not up to date with installed bzr version %s.'
70                 ' \nThere should be a newer version of bzr-svn available.' 
71                 % (bzrlib.__version__))
72         if not (bzrlib_version[0], bzrlib_version[1]-1) in desired:
73             raise Exception, 'Version mismatch'
74
75 def check_bzrsvn_version():
76     """Warn about use of experimental mappings."""
77     if version_info[3] == "exp":
78         warning('version of bzr-svn is experimental; output may change between revisions')
79
80 def check_subversion_version():
81     """Check that Subversion is compatible.
82
83     """
84     try:
85         import svn.delta
86     except ImportError:
87         warning('No Python bindings for Subversion installed. See the '
88                 'bzr-svn README for details.')
89         raise bzrlib.errors.BzrError("missing python subversion bindings")
90     if (not hasattr(svn.delta, 'svn_delta_invoke_txdelta_window_handler') and 
91         not hasattr(svn.delta, 'tx_invoke_window_handler')):
92         warning('Installed Subversion version does not have updated Python '
93                 'bindings. See the bzr-svn README for details.')
94         raise bzrlib.errors.BzrError("incompatible python subversion bindings")
95
96 check_subversion_version()
97
98 register_transport_proto('svn+ssh://', 
99     help="Access using the Subversion smart server tunneled over SSH.")
100 register_transport_proto('svn+file://', 
101     help="Access of local Subversion repositories.")
102 register_transport_proto('svn+http://',
103     help="Access of Subversion smart servers over HTTP.")
104 register_transport_proto('svn+https://',
105     help="Access of Subversion smart servers over secure HTTP.")
106 register_transport_proto('svn://', 
107     help="Access using the Subversion smart server.")
108 register_lazy_transport('svn://', 'bzrlib.plugins.svn.transport', 
109                         'SvnRaTransport')
110 register_lazy_transport('svn+', 'bzrlib.plugins.svn.transport', 
111                         'SvnRaTransport')
112 topic_registry.register_lazy('svn-branching-schemes', 
113                              'bzrlib.plugins.svn.scheme',
114                              'help_schemes', 'Subversion branching schemes')
115
116 BzrDirFormat.register_control_format(format.SvnFormat)
117 BzrDirFormat.register_control_format(workingtree.SvnWorkingTreeDirFormat)
118 format_registry.register("subversion", format.SvnFormat, 
119                          "Subversion repository. ", 
120                          native=False)
121
122 versions_checked = False
123 def lazy_check_versions():
124     global versions_checked
125     if versions_checked:
126         return
127     versions_checked = True
128     check_bzrlib_version(COMPATIBLE_BZR_VERSIONS)
129     check_bzrsvn_version()
130
131 InterRepository.register_optimiser(fetch.InterFromSvnRepository)
132 InterRepository.register_optimiser(commit.InterToSvnRepository)
133
134 def get_scheme(schemename):
135     """Parse scheme identifier and return a branching scheme."""
136     from scheme import BranchingScheme
137     from bzrlib.errors import BzrCommandError
138     
139     ret = BranchingScheme.find_scheme(schemename)
140     if ret is None:
141         raise BzrCommandError('No such branching scheme %r' % schemename)
142     return ret
143
144
145 class cmd_svn_import(Command):
146     """Convert a Subversion repository to a Bazaar repository.
147     
148     """
149     takes_args = ['from_location', 'to_location?']
150     takes_options = [Option('trees', help='Create working trees.'),
151                      Option('standalone', help='Create standalone branches.'),
152                      Option('all', 
153                          help='Convert all revisions, even those not in '
154                               'current branch history (forbids --standalone).'),
155                      Option('scheme', type=get_scheme,
156                          help='Branching scheme (none, trunk, etc). '
157                               'Default: auto.'),
158                      Option('prefix', type=str, 
159                          help='Only consider branches of which path starts '
160                               'with prefix.')
161                     ]
162
163     @display_command
164     def run(self, from_location, to_location=None, trees=False, 
165             standalone=False, scheme=None, all=False, prefix=None):
166         from bzrlib.errors import NoRepositoryPresent
167         from bzrlib.bzrdir import BzrDir
168         from convert import convert_repository
169         import os
170
171         if to_location is None:
172             to_location = os.path.basename(from_location.rstrip("/\\"))
173
174         if all:
175             # All implies shared repository 
176             # (otherwise there is no repository to store revisions in)
177             standalone = False
178
179         if os.path.isfile(from_location):
180             from convert import load_dumpfile
181             import tempfile
182             tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
183             mutter('loading dumpfile %r to %r' % (from_location, tmp_repos))
184             load_dumpfile(from_location, tmp_repos)
185             from_location = tmp_repos
186         else:
187             tmp_repos = None
188
189         from_dir = BzrDir.open(from_location)
190         try:
191             from_repos = from_dir.open_repository()
192         except NoRepositoryPresent, e:
193             from bzrlib.errors import BzrCommandError
194             raise BzrCommandError("No Repository found at %s. "
195                 "For individual branches, use 'bzr branch'." % from_location)
196
197         def filter_branch((branch_path, revnum, exists)):
198             if prefix is not None and not branch_path.startswith(prefix):
199                 return False
200             return exists
201
202         convert_repository(from_repos, to_location, scheme, not standalone, 
203                 trees, all, filter_branch=filter_branch)
204
205         if tmp_repos is not None:
206             from bzrlib import osutils
207             osutils.rmtree(tmp_repos)
208
209
210 register_command(cmd_svn_import)
211
212 class cmd_svn_upgrade(Command):
213     """Upgrade revisions mapped from Subversion in a Bazaar branch.
214     
215     This will change the revision ids of revisions whose parents 
216     were mapped from svn revisions.
217     """
218     takes_args = ['from_repository?']
219     takes_options = ['verbose']
220
221     @display_command
222     def run(self, from_repository=None, verbose=False):
223         from upgrade import upgrade_branch, upgrade_workingtree
224         from bzrlib.branch import Branch
225         from bzrlib.errors import NoWorkingTree, BzrCommandError
226         from bzrlib.repository import Repository
227         from bzrlib.workingtree import WorkingTree
228         try:
229             wt_to = WorkingTree.open(".")
230             branch_to = wt_to.branch
231         except NoWorkingTree:
232             wt_to = None
233             branch_to = Branch.open(".")
234
235         stored_loc = branch_to.get_parent()
236         if from_repository is None:
237             if stored_loc is None:
238                 raise BzrCommandError("No pull location known or"
239                                              " specified.")
240             else:
241                 import bzrlib.urlutils as urlutils
242                 display_url = urlutils.unescape_for_display(stored_loc,
243                         self.outf.encoding)
244                 self.outf.write("Using saved location: %s\n" % display_url)
245                 from_repository = Branch.open(stored_loc).repository
246         else:
247             from_repository = Repository.open(from_repository)
248
249         if wt_to is not None:
250             upgrade_workingtree(wt_to, from_repository, allow_changes=True,
251                                 verbose=verbose)
252         else:
253             upgrade_branch(branch_to, from_repository, allow_changes=True, 
254                            verbose=verbose)
255
256         if wt_to is not None:
257             wt_to.set_last_revision(branch_to.last_revision())
258
259 register_command(cmd_svn_upgrade)
260
261 class cmd_svn_push(Command):
262     """Push revisions to Subversion, creating a new branch if necessary.
263
264     The behaviour of this command is the same as that of "bzr push", except 
265     that it also creates new branches.
266     
267     This command is experimental and will be removed in the future when all 
268     functionality is included in "bzr push".
269     """
270     takes_args = ['location']
271     takes_options = ['revision']
272
273     def run(self, location, revision=None):
274         from bzrlib.bzrdir import BzrDir
275         from bzrlib.branch import Branch
276         from bzrlib.errors import NotBranchError, BzrCommandError
277         bzrdir = BzrDir.open(location)
278         source_branch = Branch.open_containing(".")[0]
279         if revision is not None:
280             if len(revision) > 1:
281                 raise BzrCommandError(
282                     'bzr svn-push --revision takes exactly one revision' 
283                     ' identifier')
284             revision_id = revision[0].in_history(source_branch).rev_id
285         else:
286             revision_id = None
287         try:
288             target_branch = bzrdir.open_branch()
289             target_branch.pull(source_branch, revision_id)
290         except NotBranchError:
291             target_branch = bzrdir.import_branch(source_branch, revision_id)
292
293 register_command(cmd_svn_push)
294
295
296 class cmd_svn_branching_scheme(Command):
297     """Show or change the branching scheme for a Subversion repository.
298
299     See 'bzr help svn-branching-scheme' for details.
300     """
301     takes_args = ['location?']
302     takes_options = [
303         Option('set', help="Change the branching scheme. "),
304         Option('repository-wide', 
305             help="Act on repository-wide setting rather than local.")
306         ]
307
308     def run(self, location=".", set=False, repository_wide=False):
309         from bzrlib.msgeditor import edit_commit_message
310         from bzrlib.repository import Repository
311         from bzrlib.trace import info
312         from scheme import scheme_from_branch_list
313         def scheme_str(scheme):
314             if scheme is None:
315                 return ""
316             return "".join(map(lambda x: x+"\n", scheme.to_lines()))
317         repos = Repository.open(location)
318         if repository_wide:
319             scheme = repos._get_property_scheme()
320         else:
321             scheme = repos.get_scheme()
322         if set:
323             schemestr = edit_commit_message("", 
324                                             start_message=scheme_str(scheme))
325             scheme = scheme_from_branch_list(
326                 map(lambda x:x.strip("\n"), schemestr.splitlines()))
327             if repository_wide:
328                 repos.set_property_scheme(scheme)
329             else:
330                 repos.set_branching_scheme(scheme)
331         elif scheme is not None:
332             info(scheme_str(scheme))
333
334
335 register_command(cmd_svn_branching_scheme)
336
337
338 def test_suite():
339     from unittest import TestSuite
340     import tests
341     suite = TestSuite()
342     suite.addTest(tests.test_suite())
343     return suite
344
345
346 if __name__ == '__main__':
347     print ("This is a Bazaar plugin. Copy this directory to ~/.bazaar/plugins "
348           "to use it.\n")
349 elif __name__ != 'bzrlib.plugins.svn':
350     raise ImportError('The Subversion plugin must be installed as'
351                       ' bzrlib.plugins.svn not %s' % __name__)
352 else:
353     import os, sys
354     sys.path.append(os.path.dirname(os.path.abspath(__file__)))