/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

Share more infrastructure between LocalGitDir and RemoteGitDir.

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