1
# Copyright (C) 2008 Jelmer Vernooij <jelmer@samba.org>
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.
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.
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
17
"""Foreign branch utilities."""
19
from bzrlib import errors, registry
20
from bzrlib.branch import Branch
21
from bzrlib.commands import Command, Option
22
from bzrlib.errors import InvalidRevisionId
23
from bzrlib.revision import Revision
24
from bzrlib.trace import info
26
class VcsMapping(object):
27
"""Describes the mapping between the semantics of Bazaar and a foreign vcs.
30
# Whether this is an experimental mapping that is still open to changes.
33
# Whether this mapping supports exporting and importing all bzr semantics.
36
# Prefix used when importing native foreign revisions (not roundtripped)
40
def revision_id_bzr_to_foreign(self, bzr_revid):
41
"""Parse a bzr revision id and convert it to a foreign revid.
43
:param bzr_revid: The bzr revision id (a string).
44
:return: A foreign revision id, can be any sort of object.
46
raise NotImplementedError(self.revision_id_bzr_to_foreign)
48
def revision_id_foreign_to_bzr(self, foreign_revid):
49
"""Parse a foreign revision id and convert it to a bzr revid.
51
:param foreign_revid: Foreign revision id, can be any sort of object.
52
:return: A bzr revision id.
54
raise NotImplementedError(self.revision_id_foreign_to_bzr)
56
def show_foreign_revid(self, foreign_revid):
57
"""Prepare a foreign revision id for formatting using bzr log.
59
:param foreign_revid: Foreign revision id.
60
:return: Dictionary mapping string keys to string values.
65
class VcsMappingRegistry(registry.Registry):
66
"""Registry for Bazaar<->foreign VCS mappings.
68
There should be one instance of this registry for every foreign VCS.
71
def register(self, key, factory, help):
72
"""Register a mapping between Bazaar and foreign VCS semantics.
74
The factory must be a callable that takes one parameter: the key.
75
It must produce an instance of VcsMapping when called.
77
registry.Registry.register(self, key, factory, help)
79
def set_default(self, key):
80
"""Set the 'default' key to be a clone of the supplied key.
82
This method must be called once and only once.
84
self._set_default_key(key)
86
def get_default(self):
87
"""Convenience function for obtaining the default mapping to use."""
88
return self.get(self._get_default_key())
91
class ForeignBranch(Branch):
92
"""Branch that exists in a foreign version control system."""
94
def __init__(self, mapping):
95
super(ForeignBranch, self).__init__()
96
self.mapping = mapping
98
def dpull(self, source, stop_revision=None):
99
"""Pull deltas from another branch.
101
:note: This does not, like pull, retain the revision ids from
104
:param source: Source branch
105
:param stop_revision: Revision to pull, defaults to last revision.
107
raise NotImplementedError(self.pull)
110
class FakeControlFiles(object):
111
"""Dummy implementation of ControlFiles.
113
This is required as some code relies on controlfiles being
115
def get_utf8(self, name):
116
raise errors.NoSuchFile(name)
119
raise errors.NoSuchFile(name)
121
def break_lock(self):
125
class cmd_dpush(Command):
126
"""Push diffs into a foreign version control system without any
127
Bazaar-specific metadata.
129
This will afterwards rebase the local Bazaar branch on the remote
130
branch unless the --no-rebase option is used, in which case
131
the two branches will be out of sync.
133
takes_args = ['location?']
134
takes_options = ['remember', Option('directory',
135
help='Branch to push from, '
136
'rather than the one containing the working directory.',
140
Option('no-rebase', help="Don't rebase after push")]
142
def run(self, location=None, remember=False, directory=None,
144
from bzrlib import urlutils
145
from bzrlib.bzrdir import BzrDir
146
from bzrlib.errors import BzrCommandError, NoWorkingTree
147
from bzrlib.workingtree import WorkingTree
149
if directory is None:
152
source_wt = WorkingTree.open_containing(directory)[0]
153
source_branch = source_wt.branch
154
except NoWorkingTree:
155
source_branch = Branch.open_containing(directory)[0]
157
stored_loc = source_branch.get_push_location()
159
if stored_loc is None:
160
raise BzrCommandError("No push location known or specified.")
162
display_url = urlutils.unescape_for_display(stored_loc,
164
self.outf.write("Using saved location: %s\n" % display_url)
165
location = stored_loc
167
bzrdir = BzrDir.open(location)
168
target_branch = bzrdir.open_branch()
169
target_branch.lock_write()
170
if not isinstance(target_branch, ForeignBranch):
171
info("target branch is not a foreign branch, using regular push.")
172
target_branch.pull(source_branch)
175
revid_map = target_branch.dpull(source_branch)
176
# We successfully created the target, remember it
177
if source_branch.get_push_location() is None or remember:
178
source_branch.set_push_location(target_branch.base)
180
_, old_last_revid = source_branch.last_revision_info()
181
new_last_revid = revid_map[old_last_revid]
182
if source_wt is not None:
183
source_wt.pull(target_branch, overwrite=True,
184
stop_revision=new_last_revid)
186
source_branch.pull(target_branch, overwrite=True,
187
stop_revision=new_last_revid)
190
from unittest import TestSuite
191
from bzrlib.tests import TestUtil
192
loader = TestUtil.TestLoader()
194
testmod_names = ['test_versionedfiles', ]
195
suite.addTest(loader.loadTestsFromModuleNames(testmod_names))
199
def escape_commit_message(message):
200
"""Replace xml-incompatible control characters."""
204
# FIXME: RBC 20060419 this should be done by the revision
205
# serialiser not by commit. Then we can also add an unescaper
206
# in the deserializer and start roundtripping revision messages
207
# precisely. See repository_implementations/test_repository.py
209
# Python strings can include characters that can't be
210
# represented in well-formed XML; escape characters that
211
# aren't listed in the XML specification
212
# (http://www.w3.org/TR/REC-xml/#NT-Char).
213
message, _ = re.subn(
214
u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
215
lambda match: match.group(0).encode('unicode_escape'),
220
class ForeignRevision(Revision):
221
"""A Revision from a Foreign repository. Remembers
222
information about foreign revision id and mapping.
226
def __init__(self, foreign_revid, mapping, *args, **kwargs):
227
super(ForeignRevision, self).__init__(*args, **kwargs)
228
self.foreign_revid = foreign_revid
229
self.mapping = mapping
232
def show_foreign_properties(mapping_registry, rev):
233
"""Custom log displayer for foreign revision identifiers.
235
:param rev: Revision object.
237
# Revision comes directly from a foreign repository
238
if isinstance(rev, ForeignRevision):
239
return rev.mapping.show_foreign_revid(rev.foreign_revid)
241
# Revision was once imported from a foreign repository
243
foreign_revid, mapping = mapping_registry.parse_revision_id(rev.revision_id)
244
except InvalidRevisionId:
247
return mapping.show_foreign_revid(foreign_revid)