/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 mapping.py

Allow paranoia checking with -Dverify.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2007 Canonical Ltd
 
2
# Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
 
3
# Copyright (C) 2008 John Carr
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
16
18
 
17
19
"""Converters, etc for going between Bazaar and Git ids."""
18
20
 
19
 
from bzrlib import errors, foreign
20
 
from bzrlib.inventory import ROOT_ID
 
21
from bzrlib import (
 
22
    errors,
 
23
    foreign,
 
24
    urlutils,
 
25
    )
 
26
from bzrlib.inventory import (
 
27
    ROOT_ID,
 
28
    )
21
29
from bzrlib.foreign import (
22
 
        ForeignRevision,
23
 
        )
 
30
    ForeignVcs, 
 
31
    VcsMappingRegistry, 
 
32
    ForeignRevision,
 
33
    )
 
34
from bzrlib.xml_serializer import (
 
35
    escape_invalid_chars,
 
36
    )
 
37
 
 
38
DEFAULT_TREE_MODE = 0040000
 
39
DEFAULT_FILE_MODE = 0100644
 
40
DEFAULT_SYMLINK_MODE = 0120000
24
41
 
25
42
 
26
43
def escape_file_id(file_id):
35
52
    """Class that maps between Git and Bazaar semantics."""
36
53
    experimental = False
37
54
 
38
 
    def revision_id_foreign_to_bzr(self, git_rev_id):
 
55
    def __init__(self):
 
56
        super(BzrGitMapping, self).__init__(foreign_git)
 
57
 
 
58
    def __eq__(self, other):
 
59
        return type(self) == type(other) and self.revid_prefix == other.revid_prefix
 
60
 
 
61
    @classmethod
 
62
    def revision_id_foreign_to_bzr(cls, git_rev_id):
39
63
        """Convert a git revision id handle to a Bazaar revision id."""
40
 
        return "%s:%s" % (self.revid_prefix, git_rev_id)
 
64
        return "%s:%s" % (cls.revid_prefix, git_rev_id)
41
65
 
42
 
    def revision_id_bzr_to_foreign(self, bzr_rev_id):
 
66
    @classmethod
 
67
    def revision_id_bzr_to_foreign(cls, bzr_rev_id):
43
68
        """Convert a Bazaar revision id to a git revision id handle."""
44
 
        if not bzr_rev_id.startswith("%s:" % self.revid_prefix):
45
 
            raise errors.InvalidRevisionId(bzr_rev_id, self)
46
 
        return bzr_rev_id[len(self.revid_prefix)+1:]
47
 
 
48
 
    def show_foreign_revid(self, foreign_revid):
49
 
        return { "git commit": foreign_revid }
 
69
        if not bzr_rev_id.startswith("%s:" % cls.revid_prefix):
 
70
            raise errors.InvalidRevisionId(bzr_rev_id, cls)
 
71
        return bzr_rev_id[len(cls.revid_prefix)+1:], cls()
50
72
 
51
73
    def generate_file_id(self, path):
 
74
        # Git paths are just bytestrings
 
75
        # We must just hope they are valid UTF-8..
 
76
        assert isinstance(path, str)
52
77
        if path == "":
53
78
            return ROOT_ID
54
 
        return escape_file_id(path.encode('utf-8'))
 
79
        return escape_file_id(path)
 
80
 
 
81
    def parse_file_id(self, file_id):
 
82
        if file_id == ROOT_ID:
 
83
            return ""
 
84
        return unescape_file_id(file_id)
55
85
 
56
86
    def import_commit(self, commit):
57
87
        """Convert a git commit to a bzr revision.
62
92
            raise AssertionError("Commit object can't be None")
63
93
        rev = ForeignRevision(commit.id, self, self.revision_id_foreign_to_bzr(commit.id))
64
94
        rev.parent_ids = tuple([self.revision_id_foreign_to_bzr(p) for p in commit.parents])
65
 
        rev.message = commit.message.decode("utf-8", "replace")
66
 
        rev.committer = str(commit.committer).decode("utf-8", "replace")
 
95
        rev.message = escape_invalid_chars(commit.message.decode("utf-8", "replace"))[0]
 
96
        rev.committer = escape_invalid_chars(str(commit.committer).decode("utf-8", "replace"))[0]
67
97
        if commit.committer != commit.author:
68
 
            rev.properties['author'] = str(commit.author).decode("utf-8", "replace")
 
98
            rev.properties['author'] = escape_invalid_chars(str(commit.author).decode("utf-8", "replace"))[0]
 
99
 
 
100
        if commit.commit_time != commit.author_time:
 
101
            rev.properties['author-timestamp'] = str(commit.author_time)
69
102
        rev.timestamp = commit.commit_time
70
103
        rev.timezone = 0
71
104
        return rev
72
105
 
73
106
 
74
 
class BzrGitMappingExperimental(BzrGitMapping):
 
107
class BzrGitMappingv1(BzrGitMapping):
 
108
    revid_prefix = 'git-v1'
 
109
    experimental = False
 
110
 
 
111
 
 
112
class BzrGitMappingExperimental(BzrGitMappingv1):
75
113
    revid_prefix = 'git-experimental'
76
114
    experimental = True
77
115
 
78
116
 
79
 
default_mapping = BzrGitMappingExperimental()
 
117
class GitMappingRegistry(VcsMappingRegistry):
 
118
 
 
119
    def revision_id_bzr_to_foreign(self, bzr_revid):
 
120
        if not bzr_revid.startswith("git-"):
 
121
            raise errors.InvalidRevisionId(bzr_revid, None)
 
122
        (mapping_version, git_sha) = bzr_revid.split(":", 1)
 
123
        mapping = self.get(mapping_version)
 
124
        return mapping.revision_id_bzr_to_foreign(bzr_revid)
 
125
 
 
126
    parse_revision_id = revision_id_bzr_to_foreign
 
127
 
 
128
 
 
129
mapping_registry = GitMappingRegistry()
 
130
mapping_registry.register_lazy('git-v1', "bzrlib.plugins.git.mapping",
 
131
                                   "BzrGitMappingv1")
 
132
mapping_registry.register_lazy('git-experimental', "bzrlib.plugins.git.mapping",
 
133
                                   "BzrGitMappingExperimental")
 
134
 
 
135
 
 
136
class ForeignGit(ForeignVcs):
 
137
    """Foreign Git."""
 
138
 
 
139
    def __init__(self):
 
140
        super(ForeignGit, self).__init__(mapping_registry)
 
141
 
 
142
    @classmethod
 
143
    def show_foreign_revid(cls, foreign_revid):
 
144
        return { "git commit": foreign_revid }
 
145
 
 
146
 
 
147
foreign_git = ForeignGit()
 
148
default_mapping = BzrGitMappingv1()
 
149
 
 
150
 
 
151
def text_to_blob(text):
 
152
    from dulwich.objects import Blob
 
153
    blob = Blob()
 
154
    blob._text = text
 
155
    return blob
 
156
 
 
157
 
 
158
def symlink_to_blob(entry):
 
159
    from dulwich.objects import Blob
 
160
    blob = Blob()
 
161
    blob._text = entry.symlink_target
 
162
    return blob
 
163
 
 
164
 
 
165
def inventory_to_tree_and_blobs(inventory, texts, mapping, cur=None):
 
166
    """Convert a Bazaar tree to a Git tree.
 
167
 
 
168
    :return: Yields tuples with object sha1, object and path
 
169
    """
 
170
    from dulwich.objects import Tree
 
171
    from bzrlib.inventory import InventoryDirectory, InventoryFile
 
172
    import stat
 
173
    stack = []
 
174
    if cur is None:
 
175
        cur = ""
 
176
    tree = Tree()
 
177
 
 
178
    # stack contains the set of trees that we haven't 
 
179
    # finished constructing
 
180
    for path, entry in inventory.iter_entries():
 
181
        while stack and not path.startswith(cur):
 
182
            tree.serialize()
 
183
            sha = tree.id
 
184
            yield sha, tree, cur
 
185
            t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
 
186
            cur, tree = stack.pop()
 
187
            tree.add(*t)
 
188
 
 
189
        if entry.kind == "directory":
 
190
            stack.append((cur, tree))
 
191
            cur = path
 
192
            tree = Tree()
 
193
        elif entry.kind == "file":
 
194
            #FIXME: We can make potentially make this Lazy to avoid shaing lots of stuff
 
195
            # and having all these objects in memory at once
 
196
            text = texts.get_record_stream([(entry.file_id, entry.revision)], 'unordered', True).next().get_bytes_as('fulltext')
 
197
            blob = text_to_blob(text)
 
198
            sha = blob.id
 
199
            yield sha, blob, path
 
200
 
 
201
            name = urlutils.basename(path).encode("utf-8")
 
202
            mode = stat.S_IFREG | 0644
 
203
            if entry.executable:
 
204
                mode |= 0111
 
205
            tree.add(mode, name, sha)
 
206
        elif entry.kind == "symlink":
 
207
            blob = symlink_to_blob(entry)
 
208
            sha = blob.id
 
209
            yield sha, blob, path
 
210
            name = urlutils.basename(path).encode("utf-8")
 
211
            tree.add(stat.S_IFLNK, name, sha)
 
212
        else:
 
213
            raise AssertionError("Unknown kind %s" % entry.kind)
 
214
 
 
215
    while len(stack) > 1:
 
216
        tree.serialize()
 
217
        sha = tree.id
 
218
        yield sha, tree, cur
 
219
        t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
 
220
        cur, tree = stack.pop()
 
221
        tree.add(*t)
 
222
 
 
223
    tree.serialize()
 
224
    yield tree.id, tree, cur
 
225
 
 
226
 
 
227
def revision_to_commit(rev, tree_sha, parent_lookup):
 
228
    """Turn a Bazaar revision in to a Git commit
 
229
 
 
230
    :param tree_sha: Tree sha for the commit
 
231
    :param parent_lookup: Function for looking up the GIT sha equiv of a bzr revision
 
232
    :return dulwich.objects.Commit represent the revision:
 
233
    """
 
234
    from dulwich.objects import Commit
 
235
    commit = Commit()
 
236
    commit._tree = tree_sha
 
237
    for p in rev.parent_ids:
 
238
        git_p = parent_lookup(p)
 
239
        if git_p is not None:
 
240
            assert len(git_p) == 40, "unexpected length for %r" % git_p
 
241
            commit._parents.append(git_p)
 
242
    commit._message = rev.message.encode("utf-8")
 
243
    commit._committer = rev.committer.encode("utf-8")
 
244
    commit._author = rev.get_apparent_authors()[0].encode("utf-8")
 
245
    commit._commit_time = long(rev.timestamp)
 
246
    if 'author-timestamp' in rev.properties:
 
247
        commit._author_time = long(rev.properties['author-timestamp'])
 
248
    else:
 
249
        commit._author_time = commit._commit_time
 
250
    commit.serialize()
 
251
    return commit