/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

ImportĀ RemoteGitBranch._get_config().

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