115
119
self.mapping = mapping
118
def show_foreign_properties(rev):
119
"""Custom log displayer for foreign revision identifiers.
121
:param rev: Revision object.
123
# Revision comes directly from a foreign repository
124
if isinstance(rev, ForeignRevision):
125
return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid)
127
# Revision was once imported from a foreign repository
129
foreign_revid, mapping = \
130
foreign_vcs_registry.parse_revision_id(rev.revision_id)
131
except errors.InvalidRevisionId:
134
return mapping.vcs.show_foreign_revid(foreign_revid)
137
122
class ForeignVcs(object):
138
123
"""A foreign version control system."""
140
def __init__(self, mapping_registry):
127
repository_format = None
129
def __init__(self, mapping_registry, abbreviation=None):
130
"""Create a new foreign vcs instance.
132
:param mapping_registry: Registry with mappings for this VCS.
133
:param abbreviation: Optional abbreviation ('bzr', 'svn', 'git', etc)
135
self.abbreviation = abbreviation
141
136
self.mapping_registry = mapping_registry
143
138
def show_foreign_revid(self, foreign_revid):
144
139
"""Prepare a foreign revision id for formatting using bzr log.
146
141
:param foreign_revid: Foreign revision id.
147
142
:return: Dictionary mapping string keys to string values.
146
def serialize_foreign_revid(self, foreign_revid):
147
"""Serialize a foreign revision id for this VCS.
149
:param foreign_revid: Foreign revision id
150
:return: Bytestring with serialized revid, will not contain any
153
raise NotImplementedError(self.serialize_foreign_revid)
152
156
class ForeignVcsRegistry(registry.Registry):
153
157
"""Registry for Foreign VCSes.
155
There should be one entry per foreign VCS. Example entries would be
159
There should be one entry per foreign VCS. Example entries would be
156
160
"git", "svn", "hg", "darcs", etc.
160
164
def register(self, key, foreign_vcs, help):
245
249
self.get_revision(revision_id))
252
class ForeignBranch(Branch):
253
"""Branch that exists in a foreign version control system."""
255
def __init__(self, mapping):
256
self.mapping = mapping
257
super(ForeignBranch, self).__init__()
260
def update_workingtree_fileids(wt, target_tree):
261
"""Update the file ids in a working tree based on another tree.
263
:param wt: Working tree in which to update file ids
264
:param target_tree: Tree to retrieve new file ids from, based on path
266
tt = transform.TreeTransform(wt)
268
for f, p, c, v, d, n, k, e in target_tree.iter_changes(wt):
269
if v == (True, False):
270
trans_id = tt.trans_id_tree_path(p[0])
271
tt.unversion_file(trans_id)
272
elif v == (False, True):
273
trans_id = tt.trans_id_tree_path(p[1])
274
tt.version_file(f, trans_id)
278
if len(wt.get_parent_ids()) == 1:
279
wt.set_parent_trees([(target_tree.get_revision_id(), target_tree)])
281
wt.set_last_revision(target_tree.get_revision_id())
284
class cmd_dpush(Command):
285
"""Push into a different VCS without any custom bzr metadata.
287
This will afterwards rebase the local branch on the remote
288
branch unless the --no-rebase option is used, in which case
289
the two branches will be out of sync after the push.
292
takes_args = ['location?']
296
help='Branch to push from, '
297
'rather than the one containing the working directory.',
301
Option('no-rebase', help="Do not rebase after push."),
303
help='Refuse to push if there are uncommitted changes in'
304
' the working tree, --no-strict disables the check.'),
307
def run(self, location=None, remember=False, directory=None,
308
no_rebase=False, strict=None):
309
from bzrlib import urlutils
310
from bzrlib.bzrdir import BzrDir
311
from bzrlib.errors import BzrCommandError, NoWorkingTree
312
from bzrlib.workingtree import WorkingTree
314
if directory is None:
317
source_wt = WorkingTree.open_containing(directory)[0]
318
source_branch = source_wt.branch
319
except NoWorkingTree:
320
source_branch = Branch.open(directory)
323
strict = source_branch.get_config(
324
).get_user_option_as_bool('dpush_strict')
325
if strict is None: strict = True # default value
326
if strict and source_wt is not None:
327
if (source_wt.has_changes()):
328
raise errors.UncommittedChanges(
329
source_wt, more='Use --no-strict to force the push.')
330
if source_wt.last_revision() != source_wt.branch.last_revision():
331
# The tree has lost sync with its branch, there is little
332
# chance that the user is aware of it but he can still force
333
# the push with --no-strict
334
raise errors.OutOfDateTree(
335
source_wt, more='Use --no-strict to force the push.')
336
stored_loc = source_branch.get_push_location()
338
if stored_loc is None:
339
raise BzrCommandError("No push location known or specified.")
341
display_url = urlutils.unescape_for_display(stored_loc,
343
self.outf.write("Using saved location: %s\n" % display_url)
344
location = stored_loc
346
bzrdir = BzrDir.open(location)
347
target_branch = bzrdir.open_branch()
348
target_branch.lock_write()
351
push_result = source_branch.lossy_push(target_branch)
352
except errors.LossyPushToSameVCS:
353
raise BzrCommandError("%r and %r are in the same VCS, lossy "
354
"push not necessary. Please use regular push." %
355
(source_branch, target_branch))
356
# We successfully created the target, remember it
357
if source_branch.get_push_location() is None or remember:
358
source_branch.set_push_location(target_branch.base)
360
old_last_revid = source_branch.last_revision()
361
source_branch.pull(target_branch, overwrite=True)
362
new_last_revid = source_branch.last_revision()
363
if source_wt is not None and old_last_revid != new_last_revid:
364
source_wt.lock_write()
366
target = source_wt.branch.repository.revision_tree(
368
update_workingtree_fileids(source_wt, target)
371
push_result.report(self.outf)
373
target_branch.unlock()
376
class InterToForeignBranch(InterBranch):
378
def lossy_push(self, stop_revision=None):
379
"""Push deltas into another branch.
381
:note: This does not, like push, retain the revision ids from
382
the source branch and will, rather than adding bzr-specific
383
metadata, push only those semantics of the revision that can be
384
natively represented by this branch' VCS.
386
:param target: Target branch
387
:param stop_revision: Revision to push, defaults to last revision.
388
:return: BranchPushResult with an extra member revidmap:
389
A dictionary mapping revision ids from the target branch
390
to new revision ids in the target branch, for each
391
revision that was pushed.
393
raise NotImplementedError(self.lossy_push)