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 Branch
22
from bzrlib.commands import Command, Option
23
from bzrlib.repository import Repository
24
from bzrlib.revision import Revision
25
from bzrlib.lazy_import import lazy_import
26
lazy_import(globals(), """
35
class VcsMapping(object):
36
"""Describes the mapping between the semantics of Bazaar and a foreign vcs.
39
# Whether this is an experimental mapping that is still open to changes.
42
# Whether this mapping supports exporting and importing all bzr semantics.
45
# Prefix used when importing native foreign revisions (not roundtripped)
49
def __init__(self, vcs):
50
"""Create a new VcsMapping.
52
:param vcs: VCS that this mapping maps to Bazaar
56
def revision_id_bzr_to_foreign(self, bzr_revid):
57
"""Parse a bzr revision id and convert it to a foreign revid.
59
:param bzr_revid: The bzr revision id (a string).
60
:return: A foreign revision id, can be any sort of object.
62
raise NotImplementedError(self.revision_id_bzr_to_foreign)
64
def revision_id_foreign_to_bzr(self, foreign_revid):
65
"""Parse a foreign revision id and convert it to a bzr revid.
67
:param foreign_revid: Foreign revision id, can be any sort of object.
68
:return: A bzr revision id.
70
raise NotImplementedError(self.revision_id_foreign_to_bzr)
73
class VcsMappingRegistry(registry.Registry):
74
"""Registry for Bazaar<->foreign VCS mappings.
76
There should be one instance of this registry for every foreign VCS.
79
def register(self, key, factory, help):
80
"""Register a mapping between Bazaar and foreign VCS semantics.
82
The factory must be a callable that takes one parameter: the key.
83
It must produce an instance of VcsMapping when called.
86
raise ValueError("mapping name can not contain colon (:)")
87
registry.Registry.register(self, key, factory, help)
89
def set_default(self, key):
90
"""Set the 'default' key to be a clone of the supplied key.
92
This method must be called once and only once.
94
self._set_default_key(key)
96
def get_default(self):
97
"""Convenience function for obtaining the default mapping to use."""
98
return self.get(self._get_default_key())
100
def revision_id_bzr_to_foreign(self, revid):
101
"""Convert a bzr revision id to a foreign revid."""
102
raise NotImplementedError(self.revision_id_bzr_to_foreign)
105
class ForeignRevision(Revision):
106
"""A Revision from a Foreign repository. Remembers
107
information about foreign revision id and mapping.
111
def __init__(self, foreign_revid, mapping, *args, **kwargs):
112
if not "inventory_sha1" in kwargs:
113
kwargs["inventory_sha1"] = ""
114
super(ForeignRevision, self).__init__(*args, **kwargs)
115
self.foreign_revid = foreign_revid
116
self.mapping = mapping
119
def show_foreign_properties(rev):
120
"""Custom log displayer for foreign revision identifiers.
122
:param rev: Revision object.
124
# Revision comes directly from a foreign repository
125
if isinstance(rev, ForeignRevision):
126
return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid)
128
# Revision was once imported from a foreign repository
130
foreign_revid, mapping = \
131
foreign_vcs_registry.parse_revision_id(rev.revision_id)
132
except errors.InvalidRevisionId:
135
return mapping.vcs.show_foreign_revid(foreign_revid)
138
class ForeignVcs(object):
139
"""A foreign version control system."""
141
def __init__(self, mapping_registry):
142
self.mapping_registry = mapping_registry
144
def show_foreign_revid(self, foreign_revid):
145
"""Prepare a foreign revision id for formatting using bzr log.
147
:param foreign_revid: Foreign revision id.
148
:return: Dictionary mapping string keys to string values.
153
class ForeignVcsRegistry(registry.Registry):
154
"""Registry for Foreign VCSes.
156
There should be one entry per foreign VCS. Example entries would be
157
"git", "svn", "hg", "darcs", etc.
161
def register(self, key, foreign_vcs, help):
162
"""Register a foreign VCS.
164
:param key: Prefix of the foreign VCS in revision ids
165
:param foreign_vcs: ForeignVCS instance
166
:param help: Description of the foreign VCS
168
if ":" in key or "-" in key:
169
raise ValueError("vcs name can not contain : or -")
170
registry.Registry.register(self, key, foreign_vcs, help)
172
def parse_revision_id(self, revid):
173
"""Parse a bzr revision and return the matching mapping and foreign
176
:param revid: The bzr revision id
177
:return: tuple with foreign revid and vcs mapping
180
raise errors.InvalidRevisionId(revid, None)
182
foreign_vcs = self.get(revid.split("-")[0])
184
raise errors.InvalidRevisionId(revid, None)
185
return foreign_vcs.mapping_registry.revision_id_bzr_to_foreign(revid)
188
foreign_vcs_registry = ForeignVcsRegistry()
191
class ForeignRepository(Repository):
192
"""A Repository that exists in a foreign version control system.
194
The data in this repository can not be represented natively using
195
Bazaars internal datastructures, but have to converted using a VcsMapping.
198
# This repository's native version control system
201
def has_foreign_revision(self, foreign_revid):
202
"""Check whether the specified foreign revision is present.
204
:param foreign_revid: A foreign revision id, in the format used
205
by this Repository's VCS.
207
raise NotImplementedError(self.has_foreign_revision)
209
def lookup_bzr_revision_id(self, revid):
210
"""Lookup a mapped or roundtripped revision by revision id.
212
:param revid: Bazaar revision id
213
:return: Tuple with foreign revision id and mapping.
215
raise NotImplementedError(self.lookup_revision_id)
217
def all_revision_ids(self, mapping=None):
218
"""See Repository.all_revision_ids()."""
219
raise NotImplementedError(self.all_revision_ids)
221
def get_default_mapping(self):
222
"""Get the default mapping for this repository."""
223
raise NotImplementedError(self.get_default_mapping)
225
def get_inventory_xml(self, revision_id):
226
"""See Repository.get_inventory_xml()."""
227
return self.serialise_inventory(self.get_inventory(revision_id))
229
def get_inventory_sha1(self, revision_id):
230
"""Get the sha1 for the XML representation of an inventory.
232
:param revision_id: Revision id of the inventory for which to return
237
return osutils.sha_string(self.get_inventory_xml(revision_id))
239
def get_revision_xml(self, revision_id):
240
"""Return the XML representation of a revision.
242
:param revision_id: Revision for which to return the XML.
245
return self._serializer.write_revision_to_string(
246
self.get_revision(revision_id))
249
class ForeignBranch(Branch):
250
"""Branch that exists in a foreign version control system."""
252
def __init__(self, mapping):
253
self.mapping = mapping
254
super(ForeignBranch, self).__init__()
256
def dpull(self, source, stop_revision=None):
257
"""Pull deltas from another branch.
259
:note: This does not, like pull, retain the revision ids from
260
the source branch and will, rather than adding bzr-specific
261
metadata, push only those semantics of the revision that can be
262
natively represented by this branch' VCS.
264
:param source: Source branch
265
:param stop_revision: Revision to pull, defaults to last revision.
266
:return: Dictionary mapping revision ids from the source branch
267
to new revision ids in the target branch, for each
268
revision that was pull.
270
raise NotImplementedError(self.dpull)
273
def update_workingtree_fileids(wt, target_tree):
274
"""Update the file ids in a working tree based on another tree.
276
:param wt: Working tree in which to update file ids
277
:param target_tree: Tree to retrieve new file ids from, based on path
279
tt = transform.TreeTransform(wt)
281
for f, p, c, v, d, n, k, e in target_tree.iter_changes(wt):
282
if v == (True, False):
283
trans_id = tt.trans_id_tree_path(p[0])
284
tt.unversion_file(trans_id)
285
elif v == (False, True):
286
trans_id = tt.trans_id_tree_path(p[1])
287
tt.version_file(f, trans_id)
291
if len(wt.get_parent_ids()) == 1:
292
wt.set_parent_trees([(target_tree.get_revision_id(), target_tree)])
294
wt.set_last_revision(target_tree.get_revision_id())
297
class cmd_dpush(Command):
298
"""Push diffs into a foreign version control system without any
299
Bazaar-specific metadata.
301
This will afterwards rebase the local Bazaar branch on the remote
302
branch unless the --no-rebase option is used, in which case
303
the two branches will be out of sync.
306
takes_args = ['location?']
307
takes_options = ['remember', Option('directory',
308
help='Branch to push from, '
309
'rather than the one containing the working directory.',
313
Option('no-rebase', help="Do not rebase after push.")]
315
def run(self, location=None, remember=False, directory=None,
317
from bzrlib import urlutils
318
from bzrlib.bzrdir import BzrDir
319
from bzrlib.errors import BzrCommandError, NoWorkingTree
320
from bzrlib.trace import info
321
from bzrlib.workingtree import WorkingTree
323
if directory is None:
326
source_wt = WorkingTree.open_containing(directory)[0]
327
source_branch = source_wt.branch
328
except NoWorkingTree:
329
source_branch = Branch.open(directory)
331
stored_loc = source_branch.get_push_location()
333
if stored_loc is None:
334
raise BzrCommandError("No push location known or specified.")
336
display_url = urlutils.unescape_for_display(stored_loc,
338
self.outf.write("Using saved location: %s\n" % display_url)
339
location = stored_loc
341
bzrdir = BzrDir.open(location)
342
target_branch = bzrdir.open_branch()
343
dpull = getattr(target_branch, "dpull", None)
345
raise BzrCommandError("%r is not a foreign branch, use "
346
"regular push." % target_branch)
347
target_branch.lock_write()
349
revid_map = dpull(source_branch)
350
# We successfully created the target, remember it
351
if source_branch.get_push_location() is None or remember:
352
source_branch.set_push_location(target_branch.base)
354
old_last_revid = source_branch.last_revision()
355
source_branch.pull(target_branch, overwrite=True)
356
new_last_revid = source_branch.last_revision()
357
if source_wt is not None and old_last_revid != new_last_revid:
358
source_wt.lock_write()
360
target = source_wt.branch.repository.revision_tree(
362
update_workingtree_fileids(source_wt, target)
366
target_branch.unlock()