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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
"""Foreign branch utilities."""
18
from bzrlib.branch import Branch
19
from bzrlib.commands import Command, Option
20
from bzrlib.revision import Revision
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
29
class VcsMapping(object):
30
"""Describes the mapping between the semantics of Bazaar and a foreign vcs.
33
# Whether this is an experimental mapping that is still open to changes.
36
# Whether this mapping supports exporting and importing all bzr semantics.
39
# Prefix used when importing native foreign revisions (not roundtripped)
43
def revision_id_bzr_to_foreign(self, bzr_revid):
44
"""Parse a bzr revision id and convert it to a foreign revid.
46
:param bzr_revid: The bzr revision id (a string).
47
:return: A foreign revision id, can be any sort of object.
49
raise NotImplementedError(self.revision_id_bzr_to_foreign)
51
def revision_id_foreign_to_bzr(self, foreign_revid):
52
"""Parse a foreign revision id and convert it to a bzr revid.
54
:param foreign_revid: Foreign revision id, can be any sort of object.
55
:return: A bzr revision id.
57
raise NotImplementedError(self.revision_id_foreign_to_bzr)
59
def show_foreign_revid(self, foreign_revid):
60
"""Prepare a foreign revision id for formatting using bzr log.
62
:param foreign_revid: Foreign revision id.
63
:return: Dictionary mapping string keys to string values.
65
# TODO: This could be on ForeignVcs instead
69
class VcsMappingRegistry(registry.Registry):
70
"""Registry for Bazaar<->foreign VCS mappings.
72
There should be one instance of this registry for every foreign VCS.
75
def register(self, key, factory, help):
76
"""Register a mapping between Bazaar and foreign VCS semantics.
78
The factory must be a callable that takes one parameter: the key.
79
It must produce an instance of VcsMapping when called.
82
raise ValueError("mapping name can not contain colon (:)")
83
registry.Registry.register(self, key, factory, help)
85
def set_default(self, key):
86
"""Set the 'default' key to be a clone of the supplied key.
88
This method must be called once and only once.
90
self._set_default_key(key)
92
def get_default(self):
93
"""Convenience function for obtaining the default mapping to use."""
94
return self.get(self._get_default_key())
96
def revision_id_bzr_to_foreign(self, revid):
97
"""Convert a bzr revision id to a foreign revid."""
98
raise NotImplementedError(self.revision_id_bzr_to_foreign)
101
class ForeignBranch(Branch):
102
"""Branch that exists in a foreign version control system."""
104
def __init__(self, mapping):
105
self.mapping = mapping
106
super(ForeignBranch, self).__init__()
108
def dpull(self, source, stop_revision=None):
109
"""Pull deltas from another branch.
111
:note: This does not, like pull, retain the revision ids from
112
the source branch and will, rather than adding bzr-specific metadata,
113
push only those semantics of the revision that can be natively
114
represented in this branch.
116
:param source: Source branch
117
:param stop_revision: Revision to pull, defaults to last revision.
119
raise NotImplementedError(self.pull)
122
class cmd_dpush(Command):
123
"""Push diffs into a foreign version control system without any
124
Bazaar-specific metadata.
126
This will afterwards rebase the local Bazaar branch on the remote
127
branch unless the --no-rebase option is used, in which case
128
the two branches will be out of sync.
130
takes_args = ['location?']
131
takes_options = ['remember', Option('directory',
132
help='Branch to push from, '
133
'rather than the one containing the working directory.',
137
Option('no-rebase', help="Don't rebase after push")]
139
def run(self, location=None, remember=False, directory=None,
141
from bzrlib import urlutils
142
from bzrlib.bzrdir import BzrDir
143
from bzrlib.errors import BzrCommandError, NoWorkingTree
144
from bzrlib.trace import info
145
from bzrlib.workingtree import WorkingTree
147
if directory is None:
150
source_wt = WorkingTree.open_containing(directory)[0]
151
source_branch = source_wt.branch
152
except NoWorkingTree:
153
source_branch = Branch.open_containing(directory)[0]
155
stored_loc = source_branch.get_push_location()
157
if stored_loc is None:
158
raise BzrCommandError("No push location known or specified.")
160
display_url = urlutils.unescape_for_display(stored_loc,
162
self.outf.write("Using saved location: %s\n" % display_url)
163
location = stored_loc
165
bzrdir = BzrDir.open(location)
166
target_branch = bzrdir.open_branch()
167
target_branch.lock_write()
168
if not isinstance(target_branch, ForeignBranch):
169
info("target branch is not a foreign branch, using regular push.")
170
target_branch.pull(source_branch)
173
revid_map = target_branch.dpull(source_branch)
174
# We successfully created the target, remember it
175
if source_branch.get_push_location() is None or remember:
176
source_branch.set_push_location(target_branch.base)
178
_, old_last_revid = source_branch.last_revision_info()
179
new_last_revid = revid_map[old_last_revid]
180
if source_wt is not None:
181
source_wt.pull(target_branch, overwrite=True,
182
stop_revision=new_last_revid)
184
source_branch.pull(target_branch, overwrite=True,
185
stop_revision=new_last_revid)
188
class ForeignRevision(Revision):
189
"""A Revision from a Foreign repository. Remembers
190
information about foreign revision id and mapping.
194
def __init__(self, foreign_revid, mapping, *args, **kwargs):
195
if not "inventory_sha1" in kwargs:
196
kwargs["inventory_sha1"] = ""
197
super(ForeignRevision, self).__init__(*args, **kwargs)
198
self.foreign_revid = foreign_revid
199
self.mapping = mapping
202
def show_foreign_properties(rev):
203
"""Custom log displayer for foreign revision identifiers.
205
:param rev: Revision object.
207
# Revision comes directly from a foreign repository
208
if isinstance(rev, ForeignRevision):
209
return rev.mapping.show_foreign_revid(rev.foreign_revid)
211
# Revision was once imported from a foreign repository
213
foreign_revid, mapping = \
214
foreign_vcs_registry.parse_revision_id(rev.revision_id)
215
except errors.InvalidRevisionId:
218
return mapping.show_foreign_revid(foreign_revid)
221
class ForeignVcs(object):
222
"""A foreign version control system."""
224
def __init__(self, mapping_registry):
225
self.mapping_registry = mapping_registry
228
class ForeignVcsRegistry(registry.Registry):
229
"""Registry for Foreign VCSes.
231
There should be one entry per foreign VCS. Example entries would be
232
"git", "svn", "hg", "darcs", etc.
236
def register(self, key, foreign_vcs, help):
237
"""Register a foreign VCS.
239
:param key: Prefix of the foreign VCS in revision ids
240
:param foreign_vcs: ForeignVCS instance
241
:param help: Description of the foreign VCS
243
if ":" in key or "-" in key:
244
raise ValueError("vcs name can not contain : or -")
245
registry.Registry.register(self, key, foreign_vcs, help)
247
def parse_revision_id(self, revid):
248
"""Parse a bzr revision and return the matching mapping and foreign
251
:param revid: The bzr revision id
252
:return: tuple with foreign revid and vcs mapping
255
raise errors.InvalidRevisionId(revid, None)
257
foreign_vcs = self.get(revid.split("-")[0])
259
raise errors.InvalidRevisionId(revid, None)
260
return foreign_vcs.mapping_registry.revision_id_bzr_to_foreign(revid)
263
foreign_vcs_registry = ForeignVcsRegistry()