~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/foreign.py

  • Committer: Martin Pool
  • Date: 2009-01-13 03:11:04 UTC
  • mto: This revision was merged to the branch mainline in revision 3937.
  • Revision ID: mbp@sourcefrog.net-20090113031104-03my054s02i9l2pe
Bump version to 1.12 and add news template

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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
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
 
    def __init__(self, vcs):
53
 
        """Create a new VcsMapping.
54
 
 
55
 
        :param vcs: VCS that this mapping maps to Bazaar
56
 
        """
57
 
        self.vcs = vcs
58
 
 
59
48
    def revision_id_bzr_to_foreign(self, bzr_revid):
60
49
        """Parse a bzr revision id and convert it to a foreign revid.
61
50
 
72
61
        """
73
62
        raise NotImplementedError(self.revision_id_foreign_to_bzr)
74
63
 
 
64
    def show_foreign_revid(self, foreign_revid):
 
65
        """Prepare a foreign revision id for formatting using bzr log.
 
66
        
 
67
        :param foreign_revid: Foreign revision id.
 
68
        :return: Dictionary mapping string keys to string values.
 
69
        """
 
70
        # TODO: This could be on ForeignVcs instead
 
71
        return { }
 
72
 
75
73
 
76
74
class VcsMappingRegistry(registry.Registry):
77
75
    """Registry for Bazaar<->foreign VCS mappings.
78
 
 
 
76
    
79
77
    There should be one instance of this registry for every foreign VCS.
80
78
    """
81
79
 
106
104
 
107
105
 
108
106
class ForeignRevision(Revision):
109
 
    """A Revision from a Foreign repository. Remembers
 
107
    """A Revision from a Foreign repository. Remembers 
110
108
    information about foreign revision id and mapping.
111
109
 
112
110
    """
119
117
        self.mapping = mapping
120
118
 
121
119
 
 
120
def show_foreign_properties(rev):
 
121
    """Custom log displayer for foreign revision identifiers.
 
122
 
 
123
    :param rev: Revision object.
 
124
    """
 
125
    # Revision comes directly from a foreign repository
 
126
    if isinstance(rev, ForeignRevision):
 
127
        return rev.mapping.show_foreign_revid(rev.foreign_revid)
 
128
 
 
129
    # Revision was once imported from a foreign repository
 
130
    try:
 
131
        foreign_revid, mapping = \
 
132
            foreign_vcs_registry.parse_revision_id(rev.revision_id)
 
133
    except errors.InvalidRevisionId:
 
134
        return {}
 
135
 
 
136
    return mapping.show_foreign_revid(foreign_revid)
 
137
 
 
138
 
122
139
class ForeignVcs(object):
123
140
    """A foreign version control system."""
124
141
 
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
 
142
    def __init__(self, mapping_registry):
136
143
        self.mapping_registry = mapping_registry
137
144
 
138
 
    def show_foreign_revid(self, foreign_revid):
139
 
        """Prepare a foreign revision id for formatting using bzr log.
140
 
 
141
 
        :param foreign_revid: Foreign revision id.
142
 
        :return: Dictionary mapping string keys to string values.
143
 
        """
144
 
        return { }
145
 
 
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
145
 
156
146
class ForeignVcsRegistry(registry.Registry):
157
147
    """Registry for Foreign VCSes.
158
148
 
159
 
    There should be one entry per foreign VCS. Example entries would be
 
149
    There should be one entry per foreign VCS. Example entries would be 
160
150
    "git", "svn", "hg", "darcs", etc.
161
 
 
 
151
    
162
152
    """
163
153
 
164
154
    def register(self, key, foreign_vcs, help):
173
163
        registry.Registry.register(self, key, foreign_vcs, help)
174
164
 
175
165
    def parse_revision_id(self, revid):
176
 
        """Parse a bzr revision and return the matching mapping and foreign
 
166
        """Parse a bzr revision and return the matching mapping and foreign 
177
167
        revid.
178
 
 
 
168
        
179
169
        :param revid: The bzr revision id
180
170
        :return: tuple with foreign revid and vcs mapping
181
171
        """
182
 
        if not ":" in revid or not "-" in revid:
 
172
        if not "-" in revid:
183
173
            raise errors.InvalidRevisionId(revid, None)
184
174
        try:
185
175
            foreign_vcs = self.get(revid.split("-")[0])
194
184
class ForeignRepository(Repository):
195
185
    """A Repository that exists in a foreign version control system.
196
186
 
197
 
    The data in this repository can not be represented natively using
 
187
    The data in this repository can not be represented natively using 
198
188
    Bazaars internal datastructures, but have to converted using a VcsMapping.
199
189
    """
200
190
 
204
194
    def has_foreign_revision(self, foreign_revid):
205
195
        """Check whether the specified foreign revision is present.
206
196
 
207
 
        :param foreign_revid: A foreign revision id, in the format used
 
197
        :param foreign_revid: A foreign revision id, in the format used 
208
198
                              by this Repository's VCS.
209
199
        """
210
200
        raise NotImplementedError(self.has_foreign_revision)
225
215
        """Get the default mapping for this repository."""
226
216
        raise NotImplementedError(self.get_default_mapping)
227
217
 
228
 
 
229
 
class ForeignBranch(Branch):
230
 
    """Branch that exists in a foreign version control system."""
231
 
 
232
 
    def __init__(self, mapping):
233
 
        self.mapping = mapping
234
 
        super(ForeignBranch, self).__init__()
235
 
 
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())
259
 
 
260
 
 
261
 
class cmd_dpush(Command):
262
 
    __doc__ = """Push into a different VCS without any custom bzr metadata.
263
 
 
264
 
    This will afterwards rebase the local branch on the remote
265
 
    branch unless the --no-rebase option is used, in which case 
266
 
    the two branches will be out of sync after the push. 
267
 
    """
268
 
    hidden = True
269
 
    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
 
        ]
283
 
 
284
 
    def run(self, location=None, remember=False, directory=None,
285
 
            no_rebase=False, strict=None):
286
 
        from bzrlib import urlutils
287
 
        from bzrlib.bzrdir import BzrDir
288
 
        from bzrlib.errors import BzrCommandError, NoWorkingTree
289
 
        from bzrlib.workingtree import WorkingTree
290
 
 
291
 
        if directory is None:
292
 
            directory = "."
293
 
        try:
294
 
            source_wt = WorkingTree.open_containing(directory)[0]
295
 
            source_branch = source_wt.branch
296
 
        except NoWorkingTree:
297
 
            source_branch = Branch.open(directory)
298
 
            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
 
        stored_loc = source_branch.get_push_location()
305
 
        if location is None:
306
 
            if stored_loc is None:
307
 
                raise BzrCommandError("No push location known or specified.")
308
 
            else:
309
 
                display_url = urlutils.unescape_for_display(stored_loc,
310
 
                        self.outf.encoding)
311
 
                self.outf.write("Using saved location: %s\n" % display_url)
312
 
                location = stored_loc
313
 
 
314
 
        bzrdir = BzrDir.open(location)
315
 
        target_branch = bzrdir.open_branch()
316
 
        target_branch.lock_write()
317
 
        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))
324
 
            # We successfully created the target, remember it
325
 
            if source_branch.get_push_location() is None or remember:
326
 
                source_branch.set_push_location(target_branch.base)
327
 
            if not no_rebase:
328
 
                old_last_revid = source_branch.last_revision()
329
 
                source_branch.pull(target_branch, overwrite=True)
330
 
                new_last_revid = source_branch.last_revision()
331
 
                if source_wt is not None and old_last_revid != new_last_revid:
332
 
                    source_wt.lock_write()
333
 
                    try:
334
 
                        target = source_wt.branch.repository.revision_tree(
335
 
                            new_last_revid)
336
 
                        update_workingtree_fileids(source_wt, target)
337
 
                    finally:
338
 
                        source_wt.unlock()
339
 
            push_result.report(self.outf)
340
 
        finally:
341
 
            target_branch.unlock()
342
 
 
343
 
 
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)
 
218
    def get_inventory_xml(self, revision_id):
 
219
        """See Repository.get_inventory_xml()."""
 
220
        return self.serialise_inventory(self.get_inventory(revision_id))
 
221
 
 
222
    def get_inventory_sha1(self, revision_id):
 
223
        """Get the sha1 for the XML representation of an inventory.
 
224
 
 
225
        :param revision_id: Revision id of the inventory for which to return 
 
226
         the SHA1.
 
227
        :return: XML string
 
228
        """
 
229
 
 
230
        return osutils.sha_string(self.get_inventory_xml(revision_id))
 
231
 
 
232
    def get_revision_xml(self, revision_id):
 
233
        """Return the XML representation of a revision.
 
234
 
 
235
        :param revision_id: Revision for which to return the XML.
 
236
        :return: XML string
 
237
        """
 
238
        return self._serializer.write_revision_to_string(
 
239
            self.get_revision(revision_id))
 
240
 
 
241