/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

mention the requirement to install Dulwich.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
2
 
# Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
3
 
# Copyright (C) 2008 John Carr
 
1
# Copyright (C) 2007-2008 Canonical Ltd
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 base64
22
 
import stat
23
 
 
24
 
from bzrlib import (
25
 
    errors,
26
 
    foreign,
27
 
    osutils,
28
 
    trace,
29
 
    urlutils,
30
 
    )
31
 
try:
32
 
    from bzrlib import bencode
33
 
except ImportError:
34
 
    from bzrlib.util import bencode
35
 
from bzrlib.inventory import (
36
 
    ROOT_ID,
37
 
    )
 
19
from bzrlib import errors, foreign
 
20
from bzrlib.inventory import ROOT_ID
38
21
from bzrlib.foreign import (
39
 
    ForeignVcs,
40
 
    VcsMappingRegistry,
41
 
    ForeignRevision,
42
 
    )
43
 
from bzrlib.revision import (
44
 
    NULL_REVISION,
45
 
    )
46
 
from bzrlib.plugins.git.hg import (
47
 
    format_hg_metadata,
48
 
    extract_hg_metadata,
49
 
    )
50
 
 
51
 
DEFAULT_FILE_MODE = stat.S_IFREG | 0644
52
 
 
 
22
        ForeignVcs, 
 
23
        VcsMappingRegistry, 
 
24
        ForeignRevision,
 
25
        )
53
26
 
54
27
def escape_file_id(file_id):
55
28
    return file_id.replace('_', '__').replace(' ', '_s')
56
29
 
57
30
 
58
31
def unescape_file_id(file_id):
59
 
    ret = []
60
 
    i = 0
61
 
    while i < len(file_id):
62
 
        if file_id[i] != '_':
63
 
            ret.append(file_id[i])
64
 
        else:
65
 
            if file_id[i+1] == '_':
66
 
                ret.append("_")
67
 
            elif file_id[i+1] == 's':
68
 
                ret.append(" ")
69
 
            else:
70
 
                raise AssertionError("unknown escape character %s" %
71
 
                    file_id[i+1])
72
 
            i += 1
73
 
        i += 1
74
 
    return "".join(ret)
75
 
 
76
 
 
77
 
def fix_person_identifier(text):
78
 
    if "<" in text and ">" in text:
79
 
        return text
80
 
    return "%s <%s>" % (text, text)
81
 
 
82
 
 
83
 
def warn_escaped(commit, num_escaped):
84
 
    trace.warning("Escaped %d XML-invalid characters in %s. Will be unable "
85
 
                  "to regenerate the SHA map.", num_escaped, commit)
86
 
 
87
 
 
88
 
def warn_unusual_mode(commit, path, mode):
89
 
    trace.mutter("Unusual file mode %o for %s in %s. Storing as revision "
90
 
                 "property. ", mode, path, commit)
91
 
 
92
 
 
93
 
def squash_revision(target_repo, rev):
94
 
    """Remove characters that can't be stored from a revision, if necessary.
95
 
 
96
 
    :param target_repo: Repository in which the revision will be stored
97
 
    :param rev: Revision object, will be modified in-place
98
 
    """
99
 
    if not getattr(target_repo._serializer, "squashes_xml_invalid_characters", True):
100
 
        return
101
 
    from bzrlib.xml_serializer import escape_invalid_chars
102
 
    rev.message, num_escaped = escape_invalid_chars(rev.message)
103
 
    if num_escaped:
104
 
        warn_escaped(rev.foreign_revid, num_escaped)
105
 
    if 'author' in rev.properties:
106
 
        rev.properties['author'], num_escaped = escape_invalid_chars(
107
 
            rev.properties['author'])
108
 
        if num_escaped:
109
 
            warn_escaped(rev.foreign_revid, num_escaped)
110
 
    rev.committer, num_escaped = escape_invalid_chars(rev.committer)
111
 
    if num_escaped:
112
 
        warn_escaped(rev.foreign_revid, num_escaped)
 
32
    return file_id.replace("_s", " ").replace("__", "_")
113
33
 
114
34
 
115
35
class BzrGitMapping(foreign.VcsMapping):
120
40
        super(BzrGitMapping, self).__init__(foreign_git)
121
41
 
122
42
    def __eq__(self, other):
123
 
        return (type(self) == type(other) and 
124
 
                self.revid_prefix == other.revid_prefix)
 
43
        return type(self) == type(other) and self.revid_prefix == other.revid_prefix
125
44
 
126
45
    @classmethod
127
46
    def revision_id_foreign_to_bzr(cls, git_rev_id):
128
47
        """Convert a git revision id handle to a Bazaar revision id."""
129
 
        if git_rev_id == "0" * 40:
130
 
            return NULL_REVISION
131
48
        return "%s:%s" % (cls.revid_prefix, git_rev_id)
132
49
 
133
50
    @classmethod
138
55
        return bzr_rev_id[len(cls.revid_prefix)+1:], cls()
139
56
 
140
57
    def generate_file_id(self, path):
141
 
        # Git paths are just bytestrings
142
 
        # We must just hope they are valid UTF-8..
143
58
        if path == "":
144
59
            return ROOT_ID
145
 
        return escape_file_id(path)
146
 
 
147
 
    def parse_file_id(self, file_id):
148
 
        if file_id == ROOT_ID:
149
 
            return ""
150
 
        return unescape_file_id(file_id)
151
 
 
152
 
    def import_unusual_file_modes(self, rev, unusual_file_modes):
153
 
        if unusual_file_modes:
154
 
            ret = [(name, unusual_file_modes[name])
155
 
                   for name in sorted(unusual_file_modes.keys())]
156
 
            rev.properties['file-modes'] = bencode.bencode(ret)
157
 
 
158
 
    def export_unusual_file_modes(self, rev):
159
 
        try:
160
 
            return dict([(self.generate_file_id(path), mode) for (path, mode) in bencode.bdecode(rev.properties['file-modes'].encode("utf-8"))])
161
 
        except KeyError:
162
 
            return {}
163
 
 
164
 
    def _generate_git_svn_metadata(self, rev, encoding):
165
 
        try:
166
 
            return "\ngit-svn-id: %s\n" % rev.properties["git-svn-id"].encode(
167
 
                encoding)
168
 
        except KeyError:
169
 
            return ""
170
 
 
171
 
    def _generate_hg_message_tail(self, rev):
172
 
        extra = {}
173
 
        renames = []
174
 
        branch = 'default'
175
 
        for name in rev.properties:
176
 
            if name == 'hg:extra:branch':
177
 
                branch = rev.properties['hg:extra:branch']
178
 
            elif name.startswith('hg:extra'):
179
 
                extra[name[len('hg:extra:'):]] = base64.b64decode(
180
 
                    rev.properties[name])
181
 
            elif name == 'hg:renames':
182
 
                renames = bencode.bdecode(base64.b64decode(
183
 
                    rev.properties['hg:renames']))
184
 
            # TODO: Export other properties as 'bzr:' extras?
185
 
        ret = format_hg_metadata(renames, branch, extra)
186
 
        assert isinstance(ret, str)
187
 
        return ret
188
 
 
189
 
    def _extract_git_svn_metadata(self, rev, message):
190
 
        lines = message.split("\n")
191
 
        if not (lines[-1] == "" and lines[-2].startswith("git-svn-id:")):
192
 
            return message
193
 
        git_svn_id = lines[-2].split(": ", 1)[1]
194
 
        rev.properties['git-svn-id'] = git_svn_id
195
 
        (url, rev, uuid) = parse_git_svn_id(git_svn_id)
196
 
        # FIXME: Convert this to converted-from property somehow..
197
 
        ret = "\n".join(lines[:-2])
198
 
        assert isinstance(ret, str)
199
 
        return ret
200
 
 
201
 
    def _extract_hg_metadata(self, rev, message):
202
 
        (message, renames, branch, extra) = extract_hg_metadata(message)
203
 
        if branch is not None:
204
 
            rev.properties['hg:extra:branch'] = branch
205
 
        for name, value in extra.iteritems():
206
 
            rev.properties['hg:extra:' + name] = base64.b64encode(value)
207
 
        if renames:
208
 
            rev.properties['hg:renames'] = base64.b64encode(bencode.bencode(
209
 
                [(new, old) for (old, new) in renames.iteritems()]))
210
 
        return message
211
 
 
212
 
    def _decode_commit_message(self, rev, message, encoding):
213
 
        return message.decode(encoding)
214
 
 
215
 
    def _encode_commit_message(self, rev, message, encoding):
216
 
        return message.encode(encoding)
217
 
 
218
 
    def export_commit(self, rev, tree_sha, parent_lookup):
219
 
        """Turn a Bazaar revision in to a Git commit
220
 
 
221
 
        :param tree_sha: Tree sha for the commit
222
 
        :param parent_lookup: Function for looking up the GIT sha equiv of a
223
 
            bzr revision
224
 
        :return dulwich.objects.Commit represent the revision:
225
 
        """
226
 
        from dulwich.objects import Commit
227
 
        commit = Commit()
228
 
        commit.tree = tree_sha
229
 
        for p in rev.parent_ids:
230
 
            try:
231
 
                git_p = parent_lookup(p)
232
 
            except KeyError:
233
 
                git_p = None
234
 
            if git_p is not None:
235
 
                assert len(git_p) == 40, "unexpected length for %r" % git_p
236
 
                commit.parents.append(git_p)
237
 
        try:
238
 
            encoding = rev.properties['git-explicit-encoding']
239
 
        except KeyError:
240
 
            encoding = rev.properties.get('git-implicit-encoding', 'utf-8')
241
 
        commit.encoding = rev.properties.get('git-explicit-encoding')
242
 
        commit.committer = fix_person_identifier(rev.committer.encode(
243
 
            encoding))
244
 
        commit.author = fix_person_identifier(
245
 
            rev.get_apparent_authors()[0].encode(encoding))
246
 
        commit.commit_time = long(rev.timestamp)
247
 
        if 'author-timestamp' in rev.properties:
248
 
            commit.author_time = long(rev.properties['author-timestamp'])
249
 
        else:
250
 
            commit.author_time = commit.commit_time
251
 
        commit.commit_timezone = rev.timezone
252
 
        if 'author-timezone' in rev.properties:
253
 
            commit.author_timezone = int(rev.properties['author-timezone'])
254
 
        else:
255
 
            commit.author_timezone = commit.commit_timezone
256
 
        commit.message = self._encode_commit_message(rev, rev.message, 
257
 
            encoding)
258
 
        return commit
 
60
        return escape_file_id(path.encode('utf-8'))
259
61
 
260
62
    def import_commit(self, commit):
261
63
        """Convert a git commit to a bzr revision.
264
66
        """
265
67
        if commit is None:
266
68
            raise AssertionError("Commit object can't be None")
267
 
        rev = ForeignRevision(commit.id, self,
268
 
                self.revision_id_foreign_to_bzr(commit.id))
 
69
        rev = ForeignRevision(commit.id, self, self.revision_id_foreign_to_bzr(commit.id))
269
70
        rev.parent_ids = tuple([self.revision_id_foreign_to_bzr(p) for p in commit.parents])
270
 
        def decode_using_encoding(rev, commit, encoding):
271
 
            rev.committer = str(commit.committer).decode(encoding)
272
 
            if commit.committer != commit.author:
273
 
                rev.properties['author'] = str(commit.author).decode(encoding)
274
 
            rev.message = self._decode_commit_message(rev, commit.message, 
275
 
                encoding)
276
 
        if commit.encoding is not None:
277
 
            rev.properties['git-explicit-encoding'] = commit.encoding
278
 
            decode_using_encoding(rev, commit, commit.encoding)
279
 
        else:
280
 
            for encoding in ('utf-8', 'latin1'):
281
 
                try:
282
 
                    decode_using_encoding(rev, commit, encoding)
283
 
                except UnicodeDecodeError:
284
 
                    pass
285
 
                else:
286
 
                    if encoding != 'utf-8':
287
 
                        rev.properties['git-implicit-encoding'] = encoding
288
 
                    break
289
 
        if commit.commit_time != commit.author_time:
290
 
            rev.properties['author-timestamp'] = str(commit.author_time)
291
 
        if commit.commit_timezone != commit.author_timezone:
292
 
            rev.properties['author-timezone'] = "%d" % commit.author_timezone
 
71
        rev.message = commit.message.decode("utf-8", "replace")
 
72
        rev.committer = str(commit.committer).decode("utf-8", "replace")
 
73
        if commit.committer != commit.author:
 
74
            rev.properties['author'] = str(commit.author).decode("utf-8", "replace")
293
75
        rev.timestamp = commit.commit_time
294
 
        rev.timezone = commit.commit_timezone
 
76
        rev.timezone = 0
295
77
        return rev
296
78
 
297
79
 
299
81
    revid_prefix = 'git-v1'
300
82
    experimental = False
301
83
 
302
 
    def __str__(self):
303
 
        return self.revid_prefix
304
 
 
305
84
 
306
85
class BzrGitMappingExperimental(BzrGitMappingv1):
307
86
    revid_prefix = 'git-experimental'
308
87
    experimental = True
309
88
 
310
 
    def _decode_commit_message(self, rev, message, encoding):
311
 
        message = self._extract_hg_metadata(rev, message)
312
 
        message = self._extract_git_svn_metadata(rev, message)
313
 
        return message.decode(encoding)
314
 
 
315
 
    def _encode_commit_message(self, rev, message, encoding):
316
 
        ret = message.encode(encoding)
317
 
        ret += self._generate_hg_message_tail(rev)
318
 
        ret += self._generate_git_svn_metadata(rev, encoding)
319
 
        return ret
320
 
 
321
 
    def import_commit(self, commit):
322
 
        rev = super(BzrGitMappingExperimental, self).import_commit(commit)
323
 
        rev.properties['converted_revision'] = "git %s\n" % commit.id
324
 
        return rev
325
 
 
326
89
 
327
90
class GitMappingRegistry(VcsMappingRegistry):
328
 
    """Registry with available git mappings."""
329
91
 
330
92
    def revision_id_bzr_to_foreign(self, bzr_revid):
331
 
        if bzr_revid == NULL_REVISION:
332
 
            return "0" * 20, None
333
93
        if not bzr_revid.startswith("git-"):
334
94
            raise errors.InvalidRevisionId(bzr_revid, None)
335
95
        (mapping_version, git_sha) = bzr_revid.split(":", 1)
341
101
 
342
102
mapping_registry = GitMappingRegistry()
343
103
mapping_registry.register_lazy('git-v1', "bzrlib.plugins.git.mapping",
344
 
    "BzrGitMappingv1")
345
 
mapping_registry.register_lazy('git-experimental',
346
 
    "bzrlib.plugins.git.mapping", "BzrGitMappingExperimental")
347
 
mapping_registry.set_default('git-v1')
 
104
                                   "BzrGitMappingv1")
 
105
mapping_registry.register_lazy('git-experimental', "bzrlib.plugins.git.mapping",
 
106
                                   "BzrGitMappingExperimental")
348
107
 
349
108
 
350
109
class ForeignGit(ForeignVcs):
351
 
    """The Git Stupid Content Tracker"""
352
 
 
353
 
    @property
354
 
    def branch_format(self):
355
 
        from bzrlib.plugins.git.branch import GitBranchFormat
356
 
        return GitBranchFormat()
357
 
 
358
 
    @property
359
 
    def repository_format(self):
360
 
        from bzrlib.plugins.git.repository import GitRepositoryFormat
361
 
        return GitRepositoryFormat()
 
110
    """Foreign Git."""
362
111
 
363
112
    def __init__(self):
364
113
        super(ForeignGit, self).__init__(mapping_registry)
365
 
        self.abbreviation = "git"
366
 
 
367
 
    @classmethod
368
 
    def serialize_foreign_revid(self, foreign_revid):
369
 
        return foreign_revid
370
114
 
371
115
    @classmethod
372
116
    def show_foreign_revid(cls, foreign_revid):
374
118
 
375
119
 
376
120
foreign_git = ForeignGit()
377
 
default_mapping = mapping_registry.get_default()()
378
 
 
379
 
 
380
 
def symlink_to_blob(entry):
381
 
    from dulwich.objects import Blob
382
 
    blob = Blob()
383
 
    symlink_target = entry.symlink_target
384
 
    if type(symlink_target) == unicode:
385
 
        symlink_target = symlink_target.encode('utf-8')
386
 
    blob.data = symlink_target
387
 
    return blob
388
 
 
389
 
 
390
 
def mode_is_executable(mode):
391
 
    """Check if mode should be considered executable."""
392
 
    return bool(mode & 0111)
393
 
 
394
 
 
395
 
def mode_kind(mode):
396
 
    """Determine the Bazaar inventory kind based on Unix file mode."""
397
 
    entry_kind = (mode & 0700000) / 0100000
398
 
    if entry_kind == 0:
399
 
        return 'directory'
400
 
    elif entry_kind == 1:
401
 
        file_kind = (mode & 070000) / 010000
402
 
        if file_kind == 0:
403
 
            return 'file'
404
 
        elif file_kind == 2:
405
 
            return 'symlink'
406
 
        elif file_kind == 6:
407
 
            return 'tree-reference'
408
 
        else:
409
 
            raise AssertionError(
410
 
                "Unknown file kind %d, perms=%o." % (file_kind, mode,))
411
 
    else:
412
 
        raise AssertionError(
413
 
            "Unknown kind, perms=%r." % (mode,))
414
 
 
415
 
 
416
 
def object_mode(kind, executable):
417
 
    if kind == 'directory':
418
 
        return stat.S_IFDIR
419
 
    elif kind == 'symlink':
420
 
        mode = stat.S_IFLNK
421
 
        if executable:
422
 
            mode |= 0111
423
 
        return mode
424
 
    elif kind == 'file':
425
 
        mode = stat.S_IFREG | 0644
426
 
        if executable:
427
 
            mode |= 0111
428
 
        return mode
429
 
    elif kind == 'tree-reference':
430
 
        from dulwich.objects import S_IFGITLINK
431
 
        return S_IFGITLINK
432
 
    else:
433
 
        raise AssertionError
434
 
 
435
 
 
436
 
def entry_mode(entry):
437
 
    """Determine the git file mode for an inventory entry."""
438
 
    return object_mode(entry.kind, entry.executable)
439
 
 
440
 
 
441
 
def directory_to_tree(entry, lookup_ie_sha1, unusual_modes):
442
 
    from dulwich.objects import Tree
443
 
    tree = Tree()
444
 
    for name, value in entry.children.iteritems():
445
 
        ie = entry.children[name]
446
 
        try:
447
 
            mode = unusual_modes[ie.file_id]
448
 
        except KeyError:
449
 
            mode = entry_mode(ie)
450
 
        hexsha = lookup_ie_sha1(ie)
451
 
        if hexsha is not None:
452
 
            tree.add(mode, name.encode("utf-8"), hexsha)
453
 
    if entry.parent_id is not None and len(tree) == 0:
454
 
        # Only the root can be an empty tree
455
 
        return None
456
 
    return tree
457
 
 
458
 
 
459
 
def extract_unusual_modes(rev):
460
 
    try:
461
 
        foreign_revid, mapping = mapping_registry.parse_revision_id(
462
 
            rev.revision_id)
463
 
    except errors.InvalidRevisionId:
464
 
        return {}
465
 
    else:
466
 
        return mapping.export_unusual_file_modes(rev)
467
 
 
468
 
 
469
 
def inventory_to_tree_and_blobs(inventory, texts, mapping, unusual_modes,
470
 
                                cur=None):
471
 
    """Convert a Bazaar tree to a Git tree.
472
 
 
473
 
    :return: Yields tuples with object sha1, object and path
474
 
    """
475
 
    from dulwich.objects import Tree
 
121
default_mapping = BzrGitMappingv1()
 
122
 
 
123
 
 
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
476
127
    import stat
477
128
    stack = []
478
 
    if cur is None:
479
 
        cur = ""
 
129
    cur = ""
480
130
    tree = Tree()
481
131
 
482
 
    # stack contains the set of trees that we haven't
483
 
    # finished constructing
484
 
    for path, entry in inventory.iter_entries():
485
 
        while stack and not path.startswith(osutils.pathjoin(cur, "")):
486
 
            # We've hit a file that's not a child of the previous path
487
 
            sha = tree.id
488
 
            yield sha, tree, cur.encode("utf-8")
489
 
            mode = unusual_modes.get(cur.encode("utf-8"), stat.S_IFDIR)
490
 
            t = (mode, urlutils.basename(cur).encode('UTF-8'), sha)
 
132
    inv = repo.get_inventory(revision_id)
 
133
 
 
134
    for path, entry in inv.iter_entries():
 
135
        while stack and not path.startswith(cur):
 
136
            tree.serialize()
 
137
            sha = tree.sha().hexdigest()
 
138
            yield sha, tree, path
 
139
            t = (stat.S_IFDIR, splitpath(cur)[-1:][0].encode('UTF-8'), sha)
491
140
            cur, tree = stack.pop()
492
141
            tree.add(*t)
493
142
 
494
 
        if entry.kind == "directory":
 
143
        if type(entry) == InventoryDirectory:
495
144
            stack.append((cur, tree))
496
145
            cur = path
497
146
            tree = Tree()
498
 
        else:
499
 
            if entry.kind == "file":
500
 
                from dulwich.objects import Blob
501
 
                stream = texts.get_record_stream(
502
 
                    [(entry.file_id, entry.revision)], 'unordered', True)
503
 
                blob = Blob()
504
 
                blob.chunked = stream.next().get_bytes_as('chunks')
505
 
            elif entry.kind == "symlink":
506
 
                blob = symlink_to_blob(entry)
507
 
            else:
508
 
                raise AssertionError("Unknown kind %s" % entry.kind)
509
 
            sha = blob.id
510
 
            yield sha, blob, path.encode("utf-8")
511
 
            name = urlutils.basename(path).encode("utf-8")
512
 
            mode = unusual_modes.get(path.encode("utf-8"), entry_mode(entry))
 
147
 
 
148
        if type(entry) == InventoryFile:
 
149
            #FIXME: We can make potentially make this Lazy to avoid shaing lots of stuff
 
150
            # and having all these objects in memory at once
 
151
            blob = Blob()
 
152
            _, blob._text = repo.iter_files_bytes([(entry.file_id, revision_id, path)]).next()
 
153
            sha = blob.sha().hexdigest()
 
154
            yield sha, blob, path
 
155
 
 
156
            name = splitpath(path)[-1:][0].encode('UTF-8')
 
157
            mode = stat.S_IFREG | 0644
 
158
            if entry.executable:
 
159
                mode |= 0111
513
160
            tree.add(mode, name, sha)
514
161
 
515
162
    while len(stack) > 1:
516
 
        sha = tree.id
517
 
        yield sha, tree, cur.encode("utf-8")
518
 
        mode = unusual_modes.get(cur.encode('utf-8'), stat.S_IFDIR)
519
 
        t = (mode, urlutils.basename(cur).encode('UTF-8'), sha)
 
163
        tree.serialize()
 
164
        sha = tree.sha().hexdigest()
 
165
        yield sha, tree, path
 
166
        t = (stat.S_IFDIR, splitpath(cur)[-1:][0].encode('UTF-8'), sha)
520
167
        cur, tree = stack.pop()
521
168
        tree.add(*t)
522
169
 
523
 
    yield tree.id, tree, cur.encode("utf-8")
524
 
 
525
 
 
526
 
def parse_git_svn_id(text):
527
 
    (head, uuid) = text.rsplit(" ", 1)
528
 
    (full_url, rev) = head.rsplit("@", 1)
529
 
    return (full_url, int(rev), uuid)
 
170
    tree.serialize()
 
171
    yield tree.sha().hexdigest(), tree, path
 
172
 
 
173
 
 
174
def revision_to_commit(rev, tree_sha, parent_lookup):
 
175
    """Turn a Bazaar revision in to a Git commit
 
176
 
 
177
    :param tree_sha: Tree sha for the commit
 
178
    :param parent_lookup: Function for looking up the GIT sha equiv of a bzr revision
 
179
    :return dulwich.objects.Commit represent the revision:
 
180
    """
 
181
    from dulwich.objects import Commit
 
182
    commit = Commit()
 
183
    commit._tree = tree_sha
 
184
    for p in rev.parent_ids:
 
185
        commit._parents.append(parent_lookup(p))
 
186
    commit._message = rev.message
 
187
    commit._committer = rev.committer
 
188
    if 'author' in rev.properties:
 
189
        commit._author = rev.properties['author']
 
190
    else:
 
191
        commit._author = rev.committer
 
192
    commit._commit_time = long(rev.timestamp)
 
193
    commit.serialize()
 
194
    return commit