/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to __init__.py

  • Committer: Jelmer Vernooij
  • Date: 2008-11-25 00:33:32 UTC
  • mto: (0.219.2 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20081125003332-60b0uol0fd7436gt
Merge improvements from bzr-svn.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Jelmer Vernooij <jelmer@samba.org>
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
 
17
"""Foreign branch utilities."""
 
18
 
 
19
from bzrlib import errors, log, osutils, registry
 
20
from bzrlib.branch import Branch
 
21
from bzrlib.commands import Command, Option
 
22
from bzrlib.errors import InvalidRevisionId
 
23
from bzrlib.repository import Repository
 
24
from bzrlib.revision import Revision
 
25
from bzrlib.trace import info
 
26
 
 
27
class VcsMapping(object):
 
28
    """Describes the mapping between the semantics of Bazaar and a foreign vcs.
 
29
 
 
30
    """
 
31
    # Whether this is an experimental mapping that is still open to changes.
 
32
    experimental = False
 
33
 
 
34
    # Whether this mapping supports exporting and importing all bzr semantics.
 
35
    roundtripping = False
 
36
 
 
37
    # Prefix used when importing native foreign revisions (not roundtripped) 
 
38
    # using this mapping.
 
39
    revid_prefix = None
 
40
 
 
41
    def revision_id_bzr_to_foreign(self, bzr_revid):
 
42
        """Parse a bzr revision id and convert it to a foreign revid.
 
43
 
 
44
        :param bzr_revid: The bzr revision id (a string).
 
45
        :return: A foreign revision id, can be any sort of object.
 
46
        """
 
47
        raise NotImplementedError(self.revision_id_bzr_to_foreign)
 
48
 
 
49
    def revision_id_foreign_to_bzr(self, foreign_revid):
 
50
        """Parse a foreign revision id and convert it to a bzr revid.
 
51
 
 
52
        :param foreign_revid: Foreign revision id, can be any sort of object.
 
53
        :return: A bzr revision id.
 
54
        """
 
55
        raise NotImplementedError(self.revision_id_foreign_to_bzr)
 
56
 
 
57
    def show_foreign_revid(self, foreign_revid):
 
58
        """Prepare a foreign revision id for formatting using bzr log.
 
59
        
 
60
        :param foreign_revid: Foreign revision id.
 
61
        :return: Dictionary mapping string keys to string values.
 
62
        """
 
63
        return { }
 
64
 
 
65
 
 
66
class VcsMappingRegistry(registry.Registry):
 
67
    """Registry for Bazaar<->foreign VCS mappings.
 
68
    
 
69
    There should be one instance of this registry for every foreign VCS.
 
70
    """
 
71
 
 
72
    def register(self, key, factory, help):
 
73
        """Register a mapping between Bazaar and foreign VCS semantics.
 
74
 
 
75
        The factory must be a callable that takes one parameter: the key.
 
76
        It must produce an instance of VcsMapping when called.
 
77
        """
 
78
        if ":" in key:
 
79
            raise ValueError("mapping name can not contain colon (:)")
 
80
        registry.Registry.register(self, key, factory, help)
 
81
 
 
82
    def set_default(self, key):
 
83
        """Set the 'default' key to be a clone of the supplied key.
 
84
 
 
85
        This method must be called once and only once.
 
86
        """
 
87
        self._set_default_key(key)
 
88
 
 
89
    def get_default(self):
 
90
        """Convenience function for obtaining the default mapping to use."""
 
91
        return self.get(self._get_default_key())
 
92
 
 
93
    def revision_id_bzr_to_foreign(self, revid):
 
94
        """Convert a bzr revision id to a foreign revid."""
 
95
        raise NotImplementedError(self.revision_id_bzr_to_foreign)
 
96
 
 
97
 
 
98
class ForeignVcs(object):
 
99
    """A foreign version control system."""
 
100
 
 
101
    def __init__(self, mapping_registry):
 
102
        self.mapping_registry = mapping_registry
 
103
 
 
104
 
 
105
class ForeignVcsRegistry(registry.Registry):
 
106
    """Registry for Foreign VCSes.
 
107
    
 
108
    """
 
109
 
 
110
    def register(self, key, foreign_vcs, help):
 
111
        """Register a foreign VCS.
 
112
 
 
113
        """
 
114
        if ":" in key or "-" in key:
 
115
            raise ValueError("vcs name can not contain : or -")
 
116
        registry.Registry.register(self, key, foreign_vcs, help)
 
117
 
 
118
    def parse_revision_id(self, revid):
 
119
        if not "-" in revid:
 
120
            raise InvalidRevisionId(revid, None)
 
121
        try:
 
122
            foreign_vcs = self.get(revid.split("-")[0])
 
123
        except KeyError:
 
124
            raise InvalidRevisionId(revid, None)
 
125
        return foreign_vcs.mapping_registry.revision_id_bzr_to_foreign(revid)
 
126
 
 
127
 
 
128
class ForeignBranch(Branch):
 
129
    """Branch that exists in a foreign version control system."""
 
130
 
 
131
    def __init__(self, mapping):
 
132
        self.mapping = mapping
 
133
        super(ForeignBranch, self).__init__()
 
134
 
 
135
    def dpull(self, source, stop_revision=None):
 
136
        """Pull deltas from another branch.
 
137
 
 
138
        :note: This does not, like pull, retain the revision ids from 
 
139
        the source branch.
 
140
 
 
141
        :param source: Source branch
 
142
        :param stop_revision: Revision to pull, defaults to last revision.
 
143
        """
 
144
        raise NotImplementedError(self.pull)
 
145
 
 
146
 
 
147
class ForeignRepository(Repository):
 
148
 
 
149
    def has_foreign_revision(self, foreign_revid):
 
150
        raise NotImplementedError(self.has_foreign_revision)
 
151
 
 
152
    def all_revision_ids(self, mapping=None):
 
153
        raise NotImplementedError(self.all_revision_ids)
 
154
 
 
155
    def get_mapping(self):
 
156
        raise NotImplementedError(self.get_default_mapping)
 
157
 
 
158
    def get_inventory_xml(self, revision_id):
 
159
        """See Repository.get_inventory_xml()."""
 
160
        return self.serialise_inventory(self.get_inventory(revision_id))
 
161
 
 
162
    def get_inventory_sha1(self, revision_id):
 
163
        """Get the sha1 for the XML representation of an inventory.
 
164
 
 
165
        :param revision_id: Revision id of the inventory for which to return 
 
166
         the SHA1.
 
167
        :return: XML string
 
168
        """
 
169
 
 
170
        return osutils.sha_string(self.get_inventory_xml(revision_id))
 
171
 
 
172
    def get_revision_xml(self, revision_id):
 
173
        """Return the XML representation of a revision.
 
174
 
 
175
        :param revision_id: Revision for which to return the XML.
 
176
        :return: XML string
 
177
        """
 
178
        return self._serializer.write_revision_to_string(self.get_revision(revision_id))
 
179
 
 
180
 
 
181
 
 
182
class FakeControlFiles(object):
 
183
    """Dummy implementation of ControlFiles.
 
184
    
 
185
    This is required as some code relies on controlfiles being 
 
186
    available."""
 
187
    def get_utf8(self, name):
 
188
        raise errors.NoSuchFile(name)
 
189
 
 
190
    def get(self, name):
 
191
        raise errors.NoSuchFile(name)
 
192
 
 
193
    def break_lock(self):
 
194
        pass
 
195
 
 
196
 
 
197
class cmd_dpush(Command):
 
198
    """Push diffs into a foreign version control system without any 
 
199
    Bazaar-specific metadata.
 
200
 
 
201
    This will afterwards rebase the local Bazaar branch on the remote
 
202
    branch unless the --no-rebase option is used, in which case 
 
203
    the two branches will be out of sync. 
 
204
    """
 
205
    takes_args = ['location?']
 
206
    takes_options = ['remember', Option('directory',
 
207
            help='Branch to push from, '
 
208
                 'rather than the one containing the working directory.',
 
209
            short_name='d',
 
210
            type=unicode,
 
211
            ),
 
212
            Option('no-rebase', help="Don't rebase after push")]
 
213
 
 
214
    def run(self, location=None, remember=False, directory=None, 
 
215
            no_rebase=False):
 
216
        from bzrlib import urlutils
 
217
        from bzrlib.bzrdir import BzrDir
 
218
        from bzrlib.errors import BzrCommandError, NoWorkingTree
 
219
        from bzrlib.workingtree import WorkingTree
 
220
 
 
221
        if directory is None:
 
222
            directory = "."
 
223
        try:
 
224
            source_wt = WorkingTree.open_containing(directory)[0]
 
225
            source_branch = source_wt.branch
 
226
        except NoWorkingTree:
 
227
            source_branch = Branch.open_containing(directory)[0]
 
228
            source_wt = None
 
229
        stored_loc = source_branch.get_push_location()
 
230
        if location is None:
 
231
            if stored_loc is None:
 
232
                raise BzrCommandError("No push location known or specified.")
 
233
            else:
 
234
                display_url = urlutils.unescape_for_display(stored_loc,
 
235
                        self.outf.encoding)
 
236
                self.outf.write("Using saved location: %s\n" % display_url)
 
237
                location = stored_loc
 
238
 
 
239
        bzrdir = BzrDir.open(location)
 
240
        target_branch = bzrdir.open_branch()
 
241
        target_branch.lock_write()
 
242
        if not isinstance(target_branch, ForeignBranch):
 
243
            info("target branch is not a foreign branch, using regular push.")
 
244
            target_branch.pull(source_branch)
 
245
            no_rebase = True
 
246
        else:
 
247
            revid_map = target_branch.dpull(source_branch)
 
248
        # We successfully created the target, remember it
 
249
        if source_branch.get_push_location() is None or remember:
 
250
            source_branch.set_push_location(target_branch.base)
 
251
        if not no_rebase:
 
252
            _, old_last_revid = source_branch.last_revision_info()
 
253
            new_last_revid = revid_map[old_last_revid]
 
254
            if source_wt is not None:
 
255
                source_wt.pull(target_branch, overwrite=True, 
 
256
                               stop_revision=new_last_revid)
 
257
            else:
 
258
                source_branch.pull(target_branch, overwrite=True, 
 
259
                                   stop_revision=new_last_revid)
 
260
 
 
261
def test_suite():
 
262
    from unittest import TestSuite
 
263
    from bzrlib.tests import TestUtil
 
264
    loader = TestUtil.TestLoader()
 
265
    suite = TestSuite()
 
266
    testmod_names = ['test_versionedfiles', ]
 
267
    suite.addTest(loader.loadTestsFromModuleNames(testmod_names))
 
268
    return suite
 
269
 
 
270
 
 
271
def escape_commit_message(message):
 
272
    """Replace xml-incompatible control characters."""
 
273
    if message is None:
 
274
        return None
 
275
    import re
 
276
    # FIXME: RBC 20060419 this should be done by the revision
 
277
    # serialiser not by commit. Then we can also add an unescaper
 
278
    # in the deserializer and start roundtripping revision messages
 
279
    # precisely. See repository_implementations/test_repository.py
 
280
    
 
281
    # Python strings can include characters that can't be
 
282
    # represented in well-formed XML; escape characters that
 
283
    # aren't listed in the XML specification
 
284
    # (http://www.w3.org/TR/REC-xml/#NT-Char).
 
285
    message, _ = re.subn(
 
286
        u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
 
287
        lambda match: match.group(0).encode('unicode_escape'),
 
288
        message)
 
289
    return message
 
290
 
 
291
 
 
292
class ForeignRevision(Revision):
 
293
    """A Revision from a Foreign repository. Remembers 
 
294
    information about foreign revision id and mapping.
 
295
 
 
296
    """
 
297
 
 
298
    def __init__(self, foreign_revid, mapping, *args, **kwargs):
 
299
        if not "inventory_sha1" in kwargs:
 
300
            kwargs["inventory_sha1"] = ""
 
301
        super(ForeignRevision, self).__init__(*args, **kwargs)
 
302
        self.foreign_revid = foreign_revid
 
303
        self.mapping = mapping
 
304
 
 
305
 
 
306
def show_foreign_properties(rev):
 
307
    """Custom log displayer for foreign revision identifiers.
 
308
 
 
309
    :param rev: Revision object.
 
310
    """
 
311
    # Revision comes directly from a foreign repository
 
312
    if isinstance(rev, ForeignRevision):
 
313
        return rev.mapping.show_foreign_revid(rev.foreign_revid)
 
314
 
 
315
    # Revision was once imported from a foreign repository
 
316
    try:
 
317
        foreign_revid, mapping = foreign_vcs_registry.parse_revision_id(rev.revision_id)
 
318
    except InvalidRevisionId:
 
319
        return {}
 
320
 
 
321
    return mapping.show_foreign_revid(foreign_revid)
 
322
 
 
323
if not "foreign" in log.properties_handler_registry:
 
324
    log.properties_handler_registry.register("foreign",
 
325
                                             show_foreign_properties,
 
326
                                             "Show foreign VCS properties")
 
327
 
 
328
foreign_vcs_registry = ForeignVcsRegistry()