1
# Copyright (C) 2008 Canonical Ltd
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
"""Foreign branch utilities."""
21
from bzrlib.branch import (
25
from bzrlib.commands import Command, Option
26
from bzrlib.repository import Repository
27
from bzrlib.revision import Revision
28
from bzrlib.lazy_import import lazy_import
29
lazy_import(globals(), """
38
class VcsMapping(object):
39
"""Describes the mapping between the semantics of Bazaar and a foreign vcs.
42
# Whether this is an experimental mapping that is still open to changes.
45
# Whether this mapping supports exporting and importing all bzr semantics.
48
# Prefix used when importing revisions native to the foreign VCS (as
49
# opposed to roundtripping bzr-native revisions) using this mapping.
52
def __init__(self, vcs):
53
"""Create a new VcsMapping.
55
:param vcs: VCS that this mapping maps to Bazaar
59
def revision_id_bzr_to_foreign(self, bzr_revid):
60
"""Parse a bzr revision id and convert it to a foreign revid.
62
:param bzr_revid: The bzr revision id (a string).
63
:return: A foreign revision id, can be any sort of object.
65
raise NotImplementedError(self.revision_id_bzr_to_foreign)
67
def revision_id_foreign_to_bzr(self, foreign_revid):
68
"""Parse a foreign revision id and convert it to a bzr revid.
70
:param foreign_revid: Foreign revision id, can be any sort of object.
71
:return: A bzr revision id.
73
raise NotImplementedError(self.revision_id_foreign_to_bzr)
76
class VcsMappingRegistry(registry.Registry):
77
"""Registry for Bazaar<->foreign VCS mappings.
79
There should be one instance of this registry for every foreign VCS.
82
def register(self, key, factory, help):
83
"""Register a mapping between Bazaar and foreign VCS semantics.
85
The factory must be a callable that takes one parameter: the key.
86
It must produce an instance of VcsMapping when called.
89
raise ValueError("mapping name can not contain colon (:)")
90
registry.Registry.register(self, key, factory, help)
92
def set_default(self, key):
93
"""Set the 'default' key to be a clone of the supplied key.
95
This method must be called once and only once.
97
self._set_default_key(key)
99
def get_default(self):
100
"""Convenience function for obtaining the default mapping to use."""
101
return self.get(self._get_default_key())
103
def revision_id_bzr_to_foreign(self, revid):
104
"""Convert a bzr revision id to a foreign revid."""
105
raise NotImplementedError(self.revision_id_bzr_to_foreign)
108
class ForeignRevision(Revision):
109
"""A Revision from a Foreign repository. Remembers
110
information about foreign revision id and mapping.
114
def __init__(self, foreign_revid, mapping, *args, **kwargs):
115
if not "inventory_sha1" in kwargs:
116
kwargs["inventory_sha1"] = ""
117
super(ForeignRevision, self).__init__(*args, **kwargs)
118
self.foreign_revid = foreign_revid
119
self.mapping = mapping
122
class ForeignVcs(object):
123
"""A foreign version control system."""
125
def __init__(self, mapping_registry):
126
self.mapping_registry = mapping_registry
128
def show_foreign_revid(self, foreign_revid):
129
"""Prepare a foreign revision id for formatting using bzr log.
131
:param foreign_revid: Foreign revision id.
132
:return: Dictionary mapping string keys to string values.
137
class ForeignVcsRegistry(registry.Registry):
138
"""Registry for Foreign VCSes.
140
There should be one entry per foreign VCS. Example entries would be
141
"git", "svn", "hg", "darcs", etc.
145
def register(self, key, foreign_vcs, help):
146
"""Register a foreign VCS.
148
:param key: Prefix of the foreign VCS in revision ids
149
:param foreign_vcs: ForeignVCS instance
150
:param help: Description of the foreign VCS
152
if ":" in key or "-" in key:
153
raise ValueError("vcs name can not contain : or -")
154
registry.Registry.register(self, key, foreign_vcs, help)
156
def parse_revision_id(self, revid):
157
"""Parse a bzr revision and return the matching mapping and foreign
160
:param revid: The bzr revision id
161
:return: tuple with foreign revid and vcs mapping
163
if not ":" in revid or not "-" in revid:
164
raise errors.InvalidRevisionId(revid, None)
166
foreign_vcs = self.get(revid.split("-")[0])
168
raise errors.InvalidRevisionId(revid, None)
169
return foreign_vcs.mapping_registry.revision_id_bzr_to_foreign(revid)
172
foreign_vcs_registry = ForeignVcsRegistry()
175
class ForeignRepository(Repository):
176
"""A Repository that exists in a foreign version control system.
178
The data in this repository can not be represented natively using
179
Bazaars internal datastructures, but have to converted using a VcsMapping.
182
# This repository's native version control system
185
def has_foreign_revision(self, foreign_revid):
186
"""Check whether the specified foreign revision is present.
188
:param foreign_revid: A foreign revision id, in the format used
189
by this Repository's VCS.
191
raise NotImplementedError(self.has_foreign_revision)
193
def lookup_bzr_revision_id(self, revid):
194
"""Lookup a mapped or roundtripped revision by revision id.
196
:param revid: Bazaar revision id
197
:return: Tuple with foreign revision id and mapping.
199
raise NotImplementedError(self.lookup_revision_id)
201
def all_revision_ids(self, mapping=None):
202
"""See Repository.all_revision_ids()."""
203
raise NotImplementedError(self.all_revision_ids)
205
def get_default_mapping(self):
206
"""Get the default mapping for this repository."""
207
raise NotImplementedError(self.get_default_mapping)
209
def get_inventory_xml(self, revision_id):
210
"""See Repository.get_inventory_xml()."""
211
return self.serialise_inventory(self.get_inventory(revision_id))
213
def get_inventory_sha1(self, revision_id):
214
"""Get the sha1 for the XML representation of an inventory.
216
:param revision_id: Revision id of the inventory for which to return
221
return osutils.sha_string(self.get_inventory_xml(revision_id))
223
def get_revision_xml(self, revision_id):
224
"""Return the XML representation of a revision.
226
:param revision_id: Revision for which to return the XML.
229
return self._serializer.write_revision_to_string(
230
self.get_revision(revision_id))
233
class ForeignBranch(Branch):
234
"""Branch that exists in a foreign version control system."""
236
def __init__(self, mapping):
237
self.mapping = mapping
238
super(ForeignBranch, self).__init__()
241
def update_workingtree_fileids(wt, target_tree):
242
"""Update the file ids in a working tree based on another tree.
244
:param wt: Working tree in which to update file ids
245
:param target_tree: Tree to retrieve new file ids from, based on path
247
tt = transform.TreeTransform(wt)
249
for f, p, c, v, d, n, k, e in target_tree.iter_changes(wt):
250
if v == (True, False):
251
trans_id = tt.trans_id_tree_path(p[0])
252
tt.unversion_file(trans_id)
253
elif v == (False, True):
254
trans_id = tt.trans_id_tree_path(p[1])
255
tt.version_file(f, trans_id)
259
if len(wt.get_parent_ids()) == 1:
260
wt.set_parent_trees([(target_tree.get_revision_id(), target_tree)])
262
wt.set_last_revision(target_tree.get_revision_id())
265
class cmd_dpush(Command):
266
"""Push into a different VCS without any custom bzr metadata.
268
This will afterwards rebase the local branch on the remote
269
branch unless the --no-rebase option is used, in which case
270
the two branches will be out of sync after the push.
273
takes_args = ['location?']
274
takes_options = ['remember', Option('directory',
275
help='Branch to push from, '
276
'rather than the one containing the working directory.',
280
Option('no-rebase', help="Do not rebase after push.")]
282
def run(self, location=None, remember=False, directory=None,
284
from bzrlib import urlutils
285
from bzrlib.bzrdir import BzrDir
286
from bzrlib.errors import BzrCommandError, NoWorkingTree
287
from bzrlib.trace import info
288
from bzrlib.workingtree import WorkingTree
290
if directory is None:
293
source_wt = WorkingTree.open_containing(directory)[0]
294
source_branch = source_wt.branch
295
except NoWorkingTree:
296
source_branch = Branch.open(directory)
298
stored_loc = source_branch.get_push_location()
300
if stored_loc is None:
301
raise BzrCommandError("No push location known or specified.")
303
display_url = urlutils.unescape_for_display(stored_loc,
305
self.outf.write("Using saved location: %s\n" % display_url)
306
location = stored_loc
308
bzrdir = BzrDir.open(location)
309
target_branch = bzrdir.open_branch()
310
target_branch.lock_write()
313
push_result = source_branch.lossy_push(target_branch)
314
except errors.LossyPushToSameVCS:
315
raise BzrCommandError("%r and %r are in the same VCS, lossy "
316
"push not necessary. Please use regular push." %
317
(source_branch, target_branch))
318
# We successfully created the target, remember it
319
if source_branch.get_push_location() is None or remember:
320
source_branch.set_push_location(target_branch.base)
322
old_last_revid = source_branch.last_revision()
323
source_branch.pull(target_branch, overwrite=True)
324
new_last_revid = source_branch.last_revision()
325
if source_wt is not None and old_last_revid != new_last_revid:
326
source_wt.lock_write()
328
target = source_wt.branch.repository.revision_tree(
330
update_workingtree_fileids(source_wt, target)
333
push_result.report(self.outf)
335
target_branch.unlock()
338
class InterToForeignBranch(InterBranch):
340
def lossy_push(self, stop_revision=None):
341
"""Push deltas into another branch.
343
:note: This does not, like push, retain the revision ids from
344
the source branch and will, rather than adding bzr-specific
345
metadata, push only those semantics of the revision that can be
346
natively represented by this branch' VCS.
348
:param target: Target branch
349
:param stop_revision: Revision to push, defaults to last revision.
350
:return: BranchPushResult with an extra member revidmap:
351
A dictionary mapping revision ids from the target branch
352
to new revision ids in the target branch, for each
353
revision that was pushed.
355
raise NotImplementedError(self.lossy_push)