~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/foreign.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-04-10 19:37:20 UTC
  • mfrom: (4222.3.15 username)
  • Revision ID: pqm@pqm.ubuntu.com-20090410193720-nyej7ft1k2yoyhui
(Jelmer) Prompt for user names for http if they are not in the
        configuration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
"""Foreign branch utilities."""
19
19
 
20
20
 
21
 
from bzrlib.branch import (
22
 
    Branch,
23
 
    InterBranch,
24
 
    )
 
21
from bzrlib.branch import Branch
25
22
from bzrlib.commands import Command, Option
26
23
from bzrlib.repository import Repository
27
24
from bzrlib.revision import Revision
31
28
    errors,
32
29
    osutils,
33
30
    registry,
34
 
    transform,
35
31
    )
36
32
""")
37
33
 
38
34
class VcsMapping(object):
39
 
    """Describes the mapping between the semantics of Bazaar and a foreign VCS.
 
35
    """Describes the mapping between the semantics of Bazaar and a foreign vcs.
40
36
 
41
37
    """
42
38
    # Whether this is an experimental mapping that is still open to changes.
45
41
    # Whether this mapping supports exporting and importing all bzr semantics.
46
42
    roundtripping = False
47
43
 
48
 
    # Prefix used when importing revisions native to the foreign VCS (as
49
 
    # opposed to roundtripping bzr-native revisions) using this mapping.
 
44
    # Prefix used when importing native foreign revisions (not roundtripped)
 
45
    # using this mapping.
50
46
    revid_prefix = None
51
47
 
52
48
    def __init__(self, vcs):
119
115
        self.mapping = mapping
120
116
 
121
117
 
 
118
def show_foreign_properties(rev):
 
119
    """Custom log displayer for foreign revision identifiers.
 
120
 
 
121
    :param rev: Revision object.
 
122
    """
 
123
    # Revision comes directly from a foreign repository
 
124
    if isinstance(rev, ForeignRevision):
 
125
        return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid)
 
126
 
 
127
    # Revision was once imported from a foreign repository
 
128
    try:
 
129
        foreign_revid, mapping = \
 
130
            foreign_vcs_registry.parse_revision_id(rev.revision_id)
 
131
    except errors.InvalidRevisionId:
 
132
        return {}
 
133
 
 
134
    return mapping.vcs.show_foreign_revid(foreign_revid)
 
135
 
 
136
 
122
137
class ForeignVcs(object):
123
138
    """A foreign version control system."""
124
139
 
125
 
    branch_format = None
126
 
 
127
 
    repository_format = None
128
 
 
129
 
    def __init__(self, mapping_registry, abbreviation=None):
130
 
        """Create a new foreign vcs instance.
131
 
 
132
 
        :param mapping_registry: Registry with mappings for this VCS.
133
 
        :param abbreviation: Optional abbreviation ('bzr', 'svn', 'git', etc)
134
 
        """
135
 
        self.abbreviation = abbreviation
 
140
    def __init__(self, mapping_registry):
136
141
        self.mapping_registry = mapping_registry
137
142
 
138
143
    def show_foreign_revid(self, foreign_revid):
143
148
        """
144
149
        return { }
145
150
 
146
 
    def serialize_foreign_revid(self, foreign_revid):
147
 
        """Serialize a foreign revision id for this VCS.
148
 
 
149
 
        :param foreign_revid: Foreign revision id
150
 
        :return: Bytestring with serialized revid, will not contain any 
151
 
            newlines.
152
 
        """
153
 
        raise NotImplementedError(self.serialize_foreign_revid)
154
 
 
155
151
 
156
152
class ForeignVcsRegistry(registry.Registry):
157
153
    """Registry for Foreign VCSes.
179
175
        :param revid: The bzr revision id
180
176
        :return: tuple with foreign revid and vcs mapping
181
177
        """
182
 
        if not ":" in revid or not "-" in revid:
 
178
        if not "-" in revid:
183
179
            raise errors.InvalidRevisionId(revid, None)
184
180
        try:
185
181
            foreign_vcs = self.get(revid.split("-")[0])
225
221
        """Get the default mapping for this repository."""
226
222
        raise NotImplementedError(self.get_default_mapping)
227
223
 
 
224
    def get_inventory_xml(self, revision_id):
 
225
        """See Repository.get_inventory_xml()."""
 
226
        return self.serialise_inventory(self.get_inventory(revision_id))
 
227
 
 
228
    def get_inventory_sha1(self, revision_id):
 
229
        """Get the sha1 for the XML representation of an inventory.
 
230
 
 
231
        :param revision_id: Revision id of the inventory for which to return
 
232
         the SHA1.
 
233
        :return: XML string
 
234
        """
 
235
 
 
236
        return osutils.sha_string(self.get_inventory_xml(revision_id))
 
237
 
 
238
    def get_revision_xml(self, revision_id):
 
239
        """Return the XML representation of a revision.
 
240
 
 
241
        :param revision_id: Revision for which to return the XML.
 
242
        :return: XML string
 
243
        """
 
244
        return self._serializer.write_revision_to_string(
 
245
            self.get_revision(revision_id))
 
246
 
228
247
 
229
248
class ForeignBranch(Branch):
230
249
    """Branch that exists in a foreign version control system."""
233
252
        self.mapping = mapping
234
253
        super(ForeignBranch, self).__init__()
235
254
 
236
 
 
237
 
def update_workingtree_fileids(wt, target_tree):
238
 
    """Update the file ids in a working tree based on another tree.
239
 
 
240
 
    :param wt: Working tree in which to update file ids
241
 
    :param target_tree: Tree to retrieve new file ids from, based on path
242
 
    """
243
 
    tt = transform.TreeTransform(wt)
244
 
    try:
245
 
        for f, p, c, v, d, n, k, e in target_tree.iter_changes(wt):
246
 
            if v == (True, False):
247
 
                trans_id = tt.trans_id_tree_path(p[0])
248
 
                tt.unversion_file(trans_id)
249
 
            elif v == (False, True):
250
 
                trans_id = tt.trans_id_tree_path(p[1])
251
 
                tt.version_file(f, trans_id)
252
 
        tt.apply()
253
 
    finally:
254
 
        tt.finalize()
255
 
    if len(wt.get_parent_ids()) == 1:
256
 
        wt.set_parent_trees([(target_tree.get_revision_id(), target_tree)])
257
 
    else:
258
 
        wt.set_last_revision(target_tree.get_revision_id())
 
255
    def dpull(self, source, stop_revision=None):
 
256
        """Pull deltas from another branch.
 
257
 
 
258
        :note: This does not, like pull, retain the revision ids from 
 
259
            the source branch and will, rather than adding bzr-specific 
 
260
            metadata, push only those semantics of the revision that can be 
 
261
            natively represented by this branch' VCS.
 
262
 
 
263
        :param source: Source branch
 
264
        :param stop_revision: Revision to pull, defaults to last revision.
 
265
        :return: Dictionary mapping revision ids from the source branch 
 
266
            to new revision ids in the target branch, for each 
 
267
            revision that was pull.
 
268
        """
 
269
        raise NotImplementedError(self.dpull)
 
270
 
 
271
 
 
272
def _determine_fileid_renames(old_inv, new_inv):
 
273
    """Determine the file ids based on a old and a new inventory that 
 
274
    are equal in content.
 
275
 
 
276
    :param old_inv: Old inventory
 
277
    :param new_inv: New inventory
 
278
    :return: Dictionary a (old_id, new_id) tuple for each path in the 
 
279
        inventories.
 
280
    """
 
281
    ret = {}
 
282
    if len(old_inv) != len(new_inv):
 
283
        raise AssertionError("Inventories are not of the same size")
 
284
    for old_file_id in old_inv:
 
285
        path = old_inv.id2path(old_file_id)
 
286
        new_file_id = new_inv.path2id(path)
 
287
        if new_file_id is None:
 
288
            raise AssertionError(
 
289
                "Unable to find %s in new inventory" % old_file_id)
 
290
        ret[path] = (old_file_id, new_file_id)
 
291
    return ret
 
292
 
 
293
 
 
294
def update_workinginv_fileids(wt, old_inv, new_inv):
 
295
    """Update all file ids in wt according to old_tree/new_tree. 
 
296
 
 
297
    old_tree and new_tree should be two RevisionTree's that differ only
 
298
    in file ids.
 
299
    """
 
300
    fileid_renames = _determine_fileid_renames(old_inv, new_inv)
 
301
    old_fileids = []
 
302
    new_fileids = []
 
303
    new_root_id = None
 
304
    # Adjust file ids in working tree
 
305
    # Sorted, so we process parents before children
 
306
    for path in sorted(fileid_renames.keys()):
 
307
        (old_fileid, new_fileid) = fileid_renames[path]
 
308
        if path != "":
 
309
            new_fileids.append((path, new_fileid))
 
310
            # unversion() works recursively so we only have to unversion the 
 
311
            # top-level. Unfortunately unversioning / is not supported yet, 
 
312
            # so unversion its children instead and use set_root_id() for /
 
313
            if old_inv[old_fileid].parent_id == old_inv.root.file_id:
 
314
                old_fileids.append(old_fileid)
 
315
        else:
 
316
            new_root_id = new_fileid
 
317
    new_fileids.reverse()
 
318
    wt.unversion(old_fileids)
 
319
    if new_root_id is not None:
 
320
        wt.set_root_id(new_root_id)
 
321
    wt.add([x[0] for x in new_fileids], [x[1] for x in new_fileids])
 
322
    wt.set_last_revision(new_inv.revision_id)
259
323
 
260
324
 
261
325
class cmd_dpush(Command):
262
 
    __doc__ = """Push into a different VCS without any custom bzr metadata.
 
326
    """Push diffs into a foreign version control system without any 
 
327
    Bazaar-specific metadata.
263
328
 
264
 
    This will afterwards rebase the local branch on the remote
 
329
    This will afterwards rebase the local Bazaar branch on the remote
265
330
    branch unless the --no-rebase option is used, in which case 
266
 
    the two branches will be out of sync after the push. 
 
331
    the two branches will be out of sync. 
267
332
    """
268
333
    hidden = True
269
334
    takes_args = ['location?']
270
 
    takes_options = [
271
 
        'remember',
272
 
        Option('directory',
273
 
               help='Branch to push from, '
274
 
               'rather than the one containing the working directory.',
275
 
               short_name='d',
276
 
               type=unicode,
277
 
               ),
278
 
        Option('no-rebase', help="Do not rebase after push."),
279
 
        Option('strict',
280
 
               help='Refuse to push if there are uncommitted changes in'
281
 
               ' the working tree, --no-strict disables the check.'),
282
 
        ]
 
335
    takes_options = ['remember', Option('directory',
 
336
            help='Branch to push from, '
 
337
                 'rather than the one containing the working directory.',
 
338
            short_name='d',
 
339
            type=unicode,
 
340
            ),
 
341
            Option('no-rebase', help="Do not rebase after push.")]
283
342
 
284
 
    def run(self, location=None, remember=False, directory=None,
285
 
            no_rebase=False, strict=None):
 
343
    def run(self, location=None, remember=False, directory=None, 
 
344
            no_rebase=False):
286
345
        from bzrlib import urlutils
287
346
        from bzrlib.bzrdir import BzrDir
288
347
        from bzrlib.errors import BzrCommandError, NoWorkingTree
 
348
        from bzrlib.trace import info
289
349
        from bzrlib.workingtree import WorkingTree
290
350
 
291
351
        if directory is None:
296
356
        except NoWorkingTree:
297
357
            source_branch = Branch.open(directory)
298
358
            source_wt = None
299
 
        if source_wt is not None:
300
 
            source_wt.check_changed_or_out_of_date(
301
 
                strict, 'dpush_strict',
302
 
                more_error='Use --no-strict to force the push.',
303
 
                more_warning='Uncommitted changes will not be pushed.')
304
359
        stored_loc = source_branch.get_push_location()
305
360
        if location is None:
306
361
            if stored_loc is None:
313
368
 
314
369
        bzrdir = BzrDir.open(location)
315
370
        target_branch = bzrdir.open_branch()
 
371
        dpull = getattr(target_branch, "dpull", None)
 
372
        if dpull is None:
 
373
            raise BzrCommandError("%r is not a foreign branch, use "
 
374
                                  "regular push." % target_branch)
316
375
        target_branch.lock_write()
317
376
        try:
318
 
            try:
319
 
                push_result = source_branch.lossy_push(target_branch)
320
 
            except errors.LossyPushToSameVCS:
321
 
                raise BzrCommandError("%r and %r are in the same VCS, lossy "
322
 
                    "push not necessary. Please use regular push." %
323
 
                    (source_branch, target_branch))
 
377
            revid_map = dpull(source_branch)
324
378
            # We successfully created the target, remember it
325
379
            if source_branch.get_push_location() is None or remember:
326
380
                source_branch.set_push_location(target_branch.base)
331
385
                if source_wt is not None and old_last_revid != new_last_revid:
332
386
                    source_wt.lock_write()
333
387
                    try:
334
 
                        target = source_wt.branch.repository.revision_tree(
335
 
                            new_last_revid)
336
 
                        update_workingtree_fileids(source_wt, target)
 
388
                        update_workinginv_fileids(source_wt, 
 
389
                            source_wt.branch.repository.get_inventory(
 
390
                                old_last_revid),
 
391
                            source_wt.branch.repository.get_inventory(
 
392
                                new_last_revid))
337
393
                    finally:
338
394
                        source_wt.unlock()
339
 
            push_result.report(self.outf)
340
395
        finally:
341
396
            target_branch.unlock()
342
397
 
343
398
 
344
 
class InterToForeignBranch(InterBranch):
345
 
 
346
 
    def lossy_push(self, stop_revision=None):
347
 
        """Push deltas into another branch.
348
 
 
349
 
        :note: This does not, like push, retain the revision ids from 
350
 
            the source branch and will, rather than adding bzr-specific 
351
 
            metadata, push only those semantics of the revision that can be 
352
 
            natively represented by this branch' VCS.
353
 
 
354
 
        :param target: Target branch
355
 
        :param stop_revision: Revision to push, defaults to last revision.
356
 
        :return: BranchPushResult with an extra member revidmap: 
357
 
            A dictionary mapping revision ids from the target branch 
358
 
            to new revision ids in the target branch, for each 
359
 
            revision that was pushed.
360
 
        """
361
 
        raise NotImplementedError(self.lossy_push)