/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 GitInventory.__repr__ to include more information.

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
import stat
 
22
 
 
23
from bzrlib import (
 
24
    errors,
 
25
    foreign,
 
26
    osutils,
 
27
    urlutils,
 
28
    )
 
29
from bzrlib.inventory import (
 
30
    ROOT_ID,
 
31
    )
21
32
from bzrlib.foreign import (
22
 
        ForeignRevision,
23
 
        )
 
33
    ForeignVcs, 
 
34
    VcsMappingRegistry, 
 
35
    ForeignRevision,
 
36
    )
 
37
from bzrlib.xml_serializer import (
 
38
    escape_invalid_chars,
 
39
    )
 
40
 
 
41
DEFAULT_FILE_MODE = stat.S_IFREG | 0644
24
42
 
25
43
 
26
44
def escape_file_id(file_id):
28
46
 
29
47
 
30
48
def unescape_file_id(file_id):
31
 
    return file_id.replace("_s", " ").replace("__", "_")
 
49
    ret = []
 
50
    i = 0
 
51
    while i < len(file_id):
 
52
        if file_id[i] != '_':
 
53
            ret.append(file_id[i])
 
54
        else:
 
55
            if file_id[i+1] == '_':
 
56
                ret.append("_")
 
57
            elif file_id[i+1] == 's':
 
58
                ret.append(" ")
 
59
            else:
 
60
                raise AssertionError("unknown escape character %s" % file_id[i+1])
 
61
            i += 1
 
62
        i += 1
 
63
    return "".join(ret)
 
64
 
 
65
 
 
66
def fix_person_identifier(text):
 
67
    if "<" in text and ">" in text:
 
68
        return text
 
69
    return "%s <%s>" % (text, text)
32
70
 
33
71
 
34
72
class BzrGitMapping(foreign.VcsMapping):
35
73
    """Class that maps between Git and Bazaar semantics."""
36
74
    experimental = False
37
75
 
38
 
    def revision_id_foreign_to_bzr(self, git_rev_id):
 
76
    def __init__(self):
 
77
        super(BzrGitMapping, self).__init__(foreign_git)
 
78
 
 
79
    def __eq__(self, other):
 
80
        return type(self) == type(other) and self.revid_prefix == other.revid_prefix
 
81
 
 
82
    @classmethod
 
83
    def revision_id_foreign_to_bzr(cls, git_rev_id):
39
84
        """Convert a git revision id handle to a Bazaar revision id."""
40
 
        return "%s:%s" % (self.revid_prefix, git_rev_id)
 
85
        return "%s:%s" % (cls.revid_prefix, git_rev_id)
41
86
 
42
 
    def revision_id_bzr_to_foreign(self, bzr_rev_id):
 
87
    @classmethod
 
88
    def revision_id_bzr_to_foreign(cls, bzr_rev_id):
43
89
        """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 }
 
90
        if not bzr_rev_id.startswith("%s:" % cls.revid_prefix):
 
91
            raise errors.InvalidRevisionId(bzr_rev_id, cls)
 
92
        return bzr_rev_id[len(cls.revid_prefix)+1:], cls()
50
93
 
51
94
    def generate_file_id(self, path):
 
95
        # Git paths are just bytestrings
 
96
        # We must just hope they are valid UTF-8..
 
97
        assert isinstance(path, str)
52
98
        if path == "":
53
99
            return ROOT_ID
54
 
        return escape_file_id(path.encode('utf-8'))
 
100
        return escape_file_id(path)
 
101
 
 
102
    def parse_file_id(self, file_id):
 
103
        if file_id == ROOT_ID:
 
104
            return ""
 
105
        return unescape_file_id(file_id)
55
106
 
56
107
    def import_commit(self, commit):
57
108
        """Convert a git commit to a bzr revision.
62
113
            raise AssertionError("Commit object can't be None")
63
114
        rev = ForeignRevision(commit.id, self, self.revision_id_foreign_to_bzr(commit.id))
64
115
        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")
 
116
        rev.message = escape_invalid_chars(commit.message.decode("utf-8", "replace"))[0]
 
117
        rev.committer = escape_invalid_chars(str(commit.committer).decode("utf-8", "replace"))[0]
67
118
        if commit.committer != commit.author:
68
 
            rev.properties['author'] = str(commit.author).decode("utf-8", "replace")
 
119
            rev.properties['author'] = escape_invalid_chars(str(commit.author).decode("utf-8", "replace"))[0]
 
120
 
 
121
        if commit.commit_time != commit.author_time:
 
122
            rev.properties['author-timestamp'] = str(commit.author_time)
 
123
        if commit.commit_timezone != commit.author_timezone:
 
124
            rev.properties['author-timezone'] = "%f" % (commit.author_timezone * .6)
69
125
        rev.timestamp = commit.commit_time
70
 
        rev.timezone = 0
 
126
        rev.timezone = int(commit.commit_timezone * .6)
 
127
        if rev.timezone / .6 != commit.commit_timezone:
 
128
            rev.properties['commit-timezone'] = "%f" % (commit.commit_timezone * .6)
71
129
        return rev
72
130
 
73
131
 
74
 
class BzrGitMappingExperimental(BzrGitMapping):
 
132
class BzrGitMappingv1(BzrGitMapping):
 
133
    revid_prefix = 'git-v1'
 
134
    experimental = False
 
135
 
 
136
    def __str__(self):
 
137
        return self.revid_prefix
 
138
 
 
139
 
 
140
class BzrGitMappingExperimental(BzrGitMappingv1):
75
141
    revid_prefix = 'git-experimental'
76
142
    experimental = True
77
143
 
78
144
 
79
 
default_mapping = BzrGitMappingExperimental()
 
145
class GitMappingRegistry(VcsMappingRegistry):
 
146
 
 
147
    def revision_id_bzr_to_foreign(self, bzr_revid):
 
148
        if not bzr_revid.startswith("git-"):
 
149
            raise errors.InvalidRevisionId(bzr_revid, None)
 
150
        (mapping_version, git_sha) = bzr_revid.split(":", 1)
 
151
        mapping = self.get(mapping_version)
 
152
        return mapping.revision_id_bzr_to_foreign(bzr_revid)
 
153
 
 
154
    parse_revision_id = revision_id_bzr_to_foreign
 
155
 
 
156
 
 
157
mapping_registry = GitMappingRegistry()
 
158
mapping_registry.register_lazy('git-v1', "bzrlib.plugins.git.mapping",
 
159
                                   "BzrGitMappingv1")
 
160
mapping_registry.register_lazy('git-experimental', "bzrlib.plugins.git.mapping",
 
161
                                   "BzrGitMappingExperimental")
 
162
 
 
163
 
 
164
class ForeignGit(ForeignVcs):
 
165
    """The Git Stupid Content Tracker"""
 
166
 
 
167
    def __init__(self):
 
168
        super(ForeignGit, self).__init__(mapping_registry)
 
169
 
 
170
    @classmethod
 
171
    def show_foreign_revid(cls, foreign_revid):
 
172
        return { "git commit": foreign_revid }
 
173
 
 
174
 
 
175
foreign_git = ForeignGit()
 
176
default_mapping = BzrGitMappingv1()
 
177
 
 
178
 
 
179
def text_to_blob(texts, entry):
 
180
    from dulwich.objects import Blob
 
181
    text = texts.get_record_stream([(entry.file_id, entry.revision)], 'unordered', True).next().get_bytes_as('fulltext')
 
182
    blob = Blob()
 
183
    blob._text = text
 
184
    return blob
 
185
 
 
186
 
 
187
def symlink_to_blob(entry):
 
188
    from dulwich.objects import Blob
 
189
    blob = Blob()
 
190
    blob._text = entry.symlink_target
 
191
    return blob
 
192
 
 
193
 
 
194
def entry_mode(entry):
 
195
    if entry.kind == 'directory':
 
196
        return stat.S_IFDIR
 
197
    elif entry.kind == 'symlink':
 
198
        return stat.S_IFLNK
 
199
    elif entry.kind == 'file':
 
200
        mode = stat.S_IFREG | 0644
 
201
        if entry.executable:
 
202
            mode |= 0111
 
203
        return mode
 
204
    else:
 
205
        raise AssertionError
 
206
 
 
207
 
 
208
def directory_to_tree(entry, lookup_ie_sha1):
 
209
    from dulwich.objects import Tree
 
210
    tree = Tree()
 
211
    for name in sorted(entry.children.keys()):
 
212
        ie = entry.children[name]
 
213
        tree.add(entry_mode(ie), name.encode("utf-8"), lookup_ie_sha1(ie))
 
214
    tree.serialize()
 
215
    return tree
 
216
 
 
217
 
 
218
def inventory_to_tree_and_blobs(inventory, texts, mapping, cur=None):
 
219
    """Convert a Bazaar tree to a Git tree.
 
220
 
 
221
    :return: Yields tuples with object sha1, object and path
 
222
    """
 
223
    from dulwich.objects import Tree
 
224
    import stat
 
225
    stack = []
 
226
    if cur is None:
 
227
        cur = ""
 
228
    tree = Tree()
 
229
 
 
230
    # stack contains the set of trees that we haven't 
 
231
    # finished constructing
 
232
    for path, entry in inventory.iter_entries():
 
233
        while stack and not path.startswith(osutils.pathjoin(cur, "")):
 
234
            # We've hit a file that's not a child of the previous path
 
235
            tree.serialize()
 
236
            sha = tree.id
 
237
            yield sha, tree, cur.encode("utf-8")
 
238
            t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
 
239
            cur, tree = stack.pop()
 
240
            tree.add(*t)
 
241
 
 
242
        if entry.kind == "directory":
 
243
            stack.append((cur, tree))
 
244
            cur = path
 
245
            tree = Tree()
 
246
        else:
 
247
            if entry.kind == "file":
 
248
                blob = text_to_blob(texts, entry)
 
249
            elif entry.kind == "symlink":
 
250
                blob = symlink_to_blob(entry)
 
251
            else:
 
252
                raise AssertionError("Unknown kind %s" % entry.kind)
 
253
            sha = blob.id
 
254
            yield sha, blob, path.encode("utf-8")
 
255
            name = urlutils.basename(path).encode("utf-8")
 
256
            tree.add(entry_mode(entry), name, sha)
 
257
 
 
258
    while len(stack) > 1:
 
259
        tree.serialize()
 
260
        sha = tree.id
 
261
        yield sha, tree, cur.encode("utf-8")
 
262
        t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
 
263
        cur, tree = stack.pop()
 
264
        tree.add(*t)
 
265
 
 
266
    tree.serialize()
 
267
    yield tree.id, tree, cur.encode("utf-8")
 
268
 
 
269
 
 
270
def revision_to_commit(rev, tree_sha, parent_lookup):
 
271
    """Turn a Bazaar revision in to a Git commit
 
272
 
 
273
    :param tree_sha: Tree sha for the commit
 
274
    :param parent_lookup: Function for looking up the GIT sha equiv of a bzr revision
 
275
    :return dulwich.objects.Commit represent the revision:
 
276
    """
 
277
    from dulwich.objects import Commit
 
278
    commit = Commit()
 
279
    commit._tree = tree_sha
 
280
    for p in rev.parent_ids:
 
281
        git_p = parent_lookup(p)
 
282
        if git_p is not None:
 
283
            assert len(git_p) == 40, "unexpected length for %r" % git_p
 
284
            commit._parents.append(git_p)
 
285
    commit._message = rev.message.encode("utf-8")
 
286
    commit._committer = fix_person_identifier(rev.committer.encode("utf-8"))
 
287
    commit._author = fix_person_identifier(rev.get_apparent_authors()[0].encode("utf-8"))
 
288
    commit._commit_time = long(rev.timestamp)
 
289
    if 'author-timestamp' in rev.properties:
 
290
        commit._author_time = long(rev.properties['author-timestamp'])
 
291
    else:
 
292
        commit._author_time = commit._commit_time
 
293
    if 'committer-timezone' in rev.properties:
 
294
        commit._commit_timezone = int(float(rev.properties['commit-timezone']) / .6)
 
295
    else:
 
296
        commit._commit_timezone = int(rev.timezone / .6) 
 
297
    if 'author-timezone' in rev.properties:
 
298
        commit._author_timezone = int(float(rev.properties['author-timezone']) / .6)
 
299
    else:
 
300
        commit._author_timezone = commit._commit_timezone 
 
301
    commit.serialize()
 
302
    return commit