/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

Fix nasty bug in inventory_to_trees_and_blobs

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