/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 storing of unusual file modes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2008 Canonical Ltd
 
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, urlutils
20
 
from bzrlib.inventory import ROOT_ID
 
21
import stat
 
22
 
 
23
from bzrlib import (
 
24
    bencode,
 
25
    errors,
 
26
    foreign,
 
27
    osutils,
 
28
    trace,
 
29
    urlutils,
 
30
    )
 
31
from bzrlib.inventory import (
 
32
    ROOT_ID,
 
33
    )
21
34
from bzrlib.foreign import (
22
 
        ForeignVcs, 
23
 
        VcsMappingRegistry, 
24
 
        ForeignRevision,
25
 
        )
 
35
    ForeignVcs, 
 
36
    VcsMappingRegistry, 
 
37
    ForeignRevision,
 
38
    )
 
39
 
 
40
DEFAULT_FILE_MODE = stat.S_IFREG | 0644
 
41
 
26
42
 
27
43
def escape_file_id(file_id):
28
44
    return file_id.replace('_', '__').replace(' ', '_s')
29
45
 
30
46
 
31
47
def unescape_file_id(file_id):
32
 
    return file_id.replace("_s", " ").replace("__", "_")
 
48
    ret = []
 
49
    i = 0
 
50
    while i < len(file_id):
 
51
        if file_id[i] != '_':
 
52
            ret.append(file_id[i])
 
53
        else:
 
54
            if file_id[i+1] == '_':
 
55
                ret.append("_")
 
56
            elif file_id[i+1] == 's':
 
57
                ret.append(" ")
 
58
            else:
 
59
                raise AssertionError("unknown escape character %s" % file_id[i+1])
 
60
            i += 1
 
61
        i += 1
 
62
    return "".join(ret)
 
63
 
 
64
 
 
65
def fix_person_identifier(text):
 
66
    if "<" in text and ">" in text:
 
67
        return text
 
68
    return "%s <%s>" % (text, text)
 
69
 
 
70
 
 
71
def warn_escaped(commit, num_escaped):
 
72
    trace.warning("Escaped %d XML-invalid characters in %s. Will be unable "
 
73
                  "to regenerate the SHA map.", num_escaped, commit)
 
74
 
 
75
 
 
76
def warn_unusual_mode(commit, path, mode):
 
77
    trace.mutter("Unusual file mode %o for %s in %s. Storing as revision property. ",
 
78
                 mode, path, commit)
 
79
 
 
80
 
 
81
def squash_revision(target_repo, rev):
 
82
    """Remove characters that can't be stored from a revision, if necessary.
 
83
    
 
84
    :param target_repo: Repository in which the revision will be stored
 
85
    :param rev: Revision object, will be modified in-place
 
86
    """
 
87
    if not getattr(target_repo._serializer, "squashes_xml_invalid_characters", True):
 
88
        return
 
89
    from bzrlib.xml_serializer import escape_invalid_chars
 
90
    rev.message, num_escaped = escape_invalid_chars(rev.message)
 
91
    if num_escaped:
 
92
        warn_escaped(rev.foreign_revid, num_escaped)
 
93
    if 'author' in rev.properties:
 
94
        rev.properties['author'], num_escaped = escape_invalid_chars(
 
95
            rev.properties['author'])
 
96
        if num_escaped:
 
97
            warn_escaped(rev.foreign_revid, num_escaped)
 
98
    rev.committer, num_escaped = escape_invalid_chars(rev.committer)
 
99
    if num_escaped:
 
100
        warn_escaped(rev.foreign_revid, num_escaped)
33
101
 
34
102
 
35
103
class BzrGitMapping(foreign.VcsMapping):
55
123
        return bzr_rev_id[len(cls.revid_prefix)+1:], cls()
56
124
 
57
125
    def generate_file_id(self, path):
 
126
        # Git paths are just bytestrings
 
127
        # We must just hope they are valid UTF-8..
58
128
        if path == "":
59
129
            return ROOT_ID
60
 
        return escape_file_id(path.encode('utf-8'))
 
130
        return escape_file_id(path)
 
131
 
 
132
    def parse_file_id(self, file_id):
 
133
        if file_id == ROOT_ID:
 
134
            return ""
 
135
        return unescape_file_id(file_id)
 
136
 
 
137
    def import_unusual_file_modes(self, rev, unusual_file_modes):
 
138
        if unusual_file_modes:
 
139
            ret = [(name, unusual_file_modes[name]) for name in sorted(unusual_file_modes.keys())]
 
140
            rev.properties['file-modes'] = bencode.bencode(ret)
 
141
 
 
142
    def export_unusual_file_modes(self, rev):
 
143
        try:
 
144
            return dict([(self.generate_file_id(path), mode) for (path, mode) in bencode.bdecode(rev.properties['file-modes'])])
 
145
        except KeyError:
 
146
            return {}
61
147
 
62
148
    def import_commit(self, commit):
63
149
        """Convert a git commit to a bzr revision.
72
158
        rev.committer = str(commit.committer).decode("utf-8", "replace")
73
159
        if commit.committer != commit.author:
74
160
            rev.properties['author'] = str(commit.author).decode("utf-8", "replace")
 
161
 
 
162
        if commit.commit_time != commit.author_time:
 
163
            rev.properties['author-timestamp'] = str(commit.author_time)
 
164
        if commit.commit_timezone != commit.author_timezone:
 
165
            rev.properties['author-timezone'] = "%d" % (commit.author_timezone, )
75
166
        rev.timestamp = commit.commit_time
76
 
        rev.timezone = 0
 
167
        rev.timezone = commit.commit_timezone
77
168
        return rev
78
169
 
79
170
 
81
172
    revid_prefix = 'git-v1'
82
173
    experimental = False
83
174
 
 
175
    def __str__(self):
 
176
        return self.revid_prefix
 
177
 
84
178
 
85
179
class BzrGitMappingExperimental(BzrGitMappingv1):
86
180
    revid_prefix = 'git-experimental'
88
182
 
89
183
 
90
184
class GitMappingRegistry(VcsMappingRegistry):
 
185
    """Registry with available git mappings."""
91
186
 
92
187
    def revision_id_bzr_to_foreign(self, bzr_revid):
93
188
        if not bzr_revid.startswith("git-"):
107
202
 
108
203
 
109
204
class ForeignGit(ForeignVcs):
110
 
    """Foreign Git."""
 
205
    """The Git Stupid Content Tracker"""
111
206
 
112
207
    def __init__(self):
113
208
        super(ForeignGit, self).__init__(mapping_registry)
121
216
default_mapping = BzrGitMappingv1()
122
217
 
123
218
 
124
 
def inventory_to_tree_and_blobs(repo, mapping, revision_id):
125
 
    from dulwich.objects import Tree, Blob
126
 
    from bzrlib.inventory import InventoryDirectory, InventoryFile
 
219
def text_to_blob(texts, entry):
 
220
    from dulwich.objects import Blob
 
221
    text = texts.get_record_stream([(entry.file_id, entry.revision)], 'unordered', True).next().get_bytes_as('fulltext')
 
222
    blob = Blob()
 
223
    blob._text = text
 
224
    return blob
 
225
 
 
226
 
 
227
def symlink_to_blob(entry):
 
228
    from dulwich.objects import Blob
 
229
    blob = Blob()
 
230
    blob._text = entry.symlink_target
 
231
    return blob
 
232
 
 
233
 
 
234
def mode_is_executable(mode):
 
235
    """Check if mode should be considered executable."""
 
236
    return bool(mode & 0111)
 
237
 
 
238
 
 
239
def mode_kind(mode):
 
240
    """Determine the Bazaar inventory kind based on Unix file mode."""
 
241
    entry_kind = (mode & 0700000) / 0100000
 
242
    if entry_kind == 0:
 
243
        return 'directory'
 
244
    elif entry_kind == 1:
 
245
        file_kind = (mode & 070000) / 010000
 
246
        if file_kind == 0:
 
247
            return 'file'
 
248
        elif file_kind == 2:
 
249
            return 'symlink'
 
250
        elif file_kind == 6:
 
251
            return 'tree-reference'
 
252
        else:
 
253
            raise AssertionError(
 
254
                "Unknown file kind %d, perms=%o." % (file_kind, mode,))
 
255
    else:
 
256
        raise AssertionError(
 
257
            "Unknown kind, perms=%r." % (mode,))
 
258
 
 
259
 
 
260
def entry_mode(entry):
 
261
    """Determine the git file mode for an inventory entry."""
 
262
    if entry.kind == 'directory':
 
263
        return stat.S_IFDIR
 
264
    elif entry.kind == 'symlink':
 
265
        return stat.S_IFLNK
 
266
    elif entry.kind == 'file':
 
267
        mode = stat.S_IFREG | 0644
 
268
        if entry.executable:
 
269
            mode |= 0111
 
270
        return mode
 
271
    else:
 
272
        raise AssertionError
 
273
 
 
274
 
 
275
def directory_to_tree(entry, lookup_ie_sha1, unusual_modes):
 
276
    from dulwich.objects import Tree
 
277
    tree = Tree()
 
278
    for name in sorted(entry.children.keys()):
 
279
        ie = entry.children[name]
 
280
        try:
 
281
            mode = unusual_modes[ie.file_id]
 
282
        except KeyError:
 
283
            mode = entry_mode(ie)
 
284
        tree.add(mode, name.encode("utf-8"), lookup_ie_sha1(ie))
 
285
    tree.serialize()
 
286
    return tree
 
287
 
 
288
 
 
289
def extract_unusual_modes(rev):
 
290
    try:
 
291
        foreign_revid, mapping = mapping_registry.parse_revision_id(rev.revision_id)
 
292
    except errors.InvalidRevisionId:
 
293
        return {}
 
294
    else:
 
295
        return mapping.export_unusual_file_modes(rev)
 
296
 
 
297
 
 
298
def inventory_to_tree_and_blobs(inventory, texts, mapping, unusual_modes, cur=None):
 
299
    """Convert a Bazaar tree to a Git tree.
 
300
 
 
301
    :return: Yields tuples with object sha1, object and path
 
302
    """
 
303
    from dulwich.objects import Tree
127
304
    import stat
128
305
    stack = []
129
 
    cur = ""
 
306
    if cur is None:
 
307
        cur = ""
130
308
    tree = Tree()
131
309
 
132
 
    inv = repo.get_inventory(revision_id)
133
 
 
134
310
    # stack contains the set of trees that we haven't 
135
311
    # finished constructing
136
 
 
137
 
    for path, entry in inv.iter_entries():
138
 
        while stack and not path.startswith(cur):
 
312
    for path, entry in inventory.iter_entries():
 
313
        while stack and not path.startswith(osutils.pathjoin(cur, "")):
 
314
            # We've hit a file that's not a child of the previous path
139
315
            tree.serialize()
140
 
            sha = tree.sha().hexdigest()
141
 
            yield sha, tree, cur
142
 
            t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
 
316
            sha = tree.id
 
317
            yield sha, tree, cur.encode("utf-8")
 
318
            mode = unusual_modes.get(cur.encode("utf-8"), stat.S_IFDIR)
 
319
            t = (mode, urlutils.basename(cur).encode('UTF-8'), sha)
143
320
            cur, tree = stack.pop()
144
321
            tree.add(*t)
145
322
 
146
 
        if type(entry) == InventoryDirectory:
 
323
        if entry.kind == "directory":
147
324
            stack.append((cur, tree))
148
325
            cur = path
149
326
            tree = Tree()
150
 
 
151
 
        if type(entry) == InventoryFile:
152
 
            #FIXME: We can make potentially make this Lazy to avoid shaing lots of stuff
153
 
            # and having all these objects in memory at once
154
 
            blob = Blob()
155
 
            _, blob._text = repo.iter_files_bytes([(entry.file_id, entry.revision, path)]).next()
156
 
            sha = blob.sha().hexdigest()
157
 
            yield sha, blob, path
158
 
 
 
327
        else:
 
328
            if entry.kind == "file":
 
329
                blob = text_to_blob(texts, entry)
 
330
            elif entry.kind == "symlink":
 
331
                blob = symlink_to_blob(entry)
 
332
            else:
 
333
                raise AssertionError("Unknown kind %s" % entry.kind)
 
334
            sha = blob.id
 
335
            yield sha, blob, path.encode("utf-8")
159
336
            name = urlutils.basename(path).encode("utf-8")
160
 
            mode = stat.S_IFREG | 0644
161
 
            if entry.executable:
162
 
                mode |= 0111
 
337
            mode = unusual_modes.get(path.encode("utf-8"), entry_mode(entry))
163
338
            tree.add(mode, name, sha)
164
339
 
165
340
    while len(stack) > 1:
166
341
        tree.serialize()
167
 
        sha = tree.sha().hexdigest()
168
 
        yield sha, tree, cur
169
 
        t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
 
342
        sha = tree.id
 
343
        yield sha, tree, cur.encode("utf-8")
 
344
        mode = unusual_modes.get(cur.encode('utf-8'), stat.S_IFDIR)
 
345
        t = (mode, urlutils.basename(cur).encode('UTF-8'), sha)
170
346
        cur, tree = stack.pop()
171
347
        tree.add(*t)
172
348
 
173
349
    tree.serialize()
174
 
    yield tree.sha().hexdigest(), tree, cur
 
350
    yield tree.id, tree, cur.encode("utf-8")
175
351
 
176
352
 
177
353
def revision_to_commit(rev, tree_sha, parent_lookup):
183
359
    """
184
360
    from dulwich.objects import Commit
185
361
    commit = Commit()
186
 
    commit._tree = tree_sha
 
362
    commit.tree = tree_sha
187
363
    for p in rev.parent_ids:
188
364
        git_p = parent_lookup(p)
189
365
        if git_p is not None:
190
 
            commit._parents.append(git_p)
191
 
    commit._message = rev.message.encode("utf-8")
192
 
    commit._committer = rev.committer.encode("utf-8")
193
 
    commit._author = rev.get_apparent_author().encode("utf-8")
194
 
    commit._commit_time = long(rev.timestamp)
195
 
    commit.serialize()
 
366
            assert len(git_p) == 40, "unexpected length for %r" % git_p
 
367
            commit.parents.append(git_p)
 
368
    commit.message = rev.message.encode("utf-8")
 
369
    commit.committer = fix_person_identifier(rev.committer.encode("utf-8"))
 
370
    commit.author = fix_person_identifier(rev.get_apparent_authors()[0].encode("utf-8"))
 
371
    commit.commit_time = long(rev.timestamp)
 
372
    if 'author-timestamp' in rev.properties:
 
373
        commit.author_time = long(rev.properties['author-timestamp'])
 
374
    else:
 
375
        commit.author_time = commit.commit_time
 
376
    commit.commit_timezone = rev.timezone
 
377
    if 'author-timezone' in rev.properties:
 
378
        commit.author_timezone = int(rev.properties['author-timezone'])
 
379
    else:
 
380
        commit.author_timezone = commit.commit_timezone 
196
381
    return commit