/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

  • Committer: Jelmer Vernooij
  • Date: 2010-05-13 15:44:32 UTC
  • mto: (0.200.912 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20100513154432-wtw5355qat6ywsfu
Avoid trying to set HEAD for remote branches.

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-2010 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 base64
 
22
import stat
 
23
 
 
24
from bzrlib import (
 
25
    errors,
 
26
    foreign,
 
27
    trace,
 
28
    )
 
29
try:
 
30
    from bzrlib import bencode
 
31
except ImportError:
 
32
    from bzrlib.util import bencode
 
33
from bzrlib.inventory import (
 
34
    ROOT_ID,
 
35
    )
21
36
from bzrlib.foreign import (
22
 
        ForeignVcs, 
23
 
        VcsMappingRegistry, 
24
 
        ForeignRevision,
25
 
        )
 
37
    ForeignVcs,
 
38
    VcsMappingRegistry,
 
39
    ForeignRevision,
 
40
    )
 
41
from bzrlib.revision import (
 
42
    NULL_REVISION,
 
43
    )
 
44
from bzrlib.plugins.git.hg import (
 
45
    format_hg_metadata,
 
46
    extract_hg_metadata,
 
47
    )
 
48
from bzrlib.plugins.git.roundtrip import (
 
49
    extract_bzr_metadata,
 
50
    inject_bzr_metadata,
 
51
    BzrGitRevisionMetadata,
 
52
    deserialize_fileid_map,
 
53
    serialize_fileid_map,
 
54
    )
 
55
 
 
56
DEFAULT_FILE_MODE = stat.S_IFREG | 0644
 
57
 
26
58
 
27
59
def escape_file_id(file_id):
28
60
    return file_id.replace('_', '__').replace(' ', '_s')
29
61
 
30
62
 
31
63
def unescape_file_id(file_id):
32
 
    return file_id.replace("_s", " ").replace("__", "_")
 
64
    ret = []
 
65
    i = 0
 
66
    while i < len(file_id):
 
67
        if file_id[i] != '_':
 
68
            ret.append(file_id[i])
 
69
        else:
 
70
            if file_id[i+1] == '_':
 
71
                ret.append("_")
 
72
            elif file_id[i+1] == 's':
 
73
                ret.append(" ")
 
74
            else:
 
75
                raise AssertionError("unknown escape character %s" %
 
76
                    file_id[i+1])
 
77
            i += 1
 
78
        i += 1
 
79
    return "".join(ret)
 
80
 
 
81
 
 
82
def fix_person_identifier(text):
 
83
    if "<" in text and ">" in text:
 
84
        return text
 
85
    return "%s <%s>" % (text, text)
 
86
 
 
87
 
 
88
def warn_escaped(commit, num_escaped):
 
89
    trace.warning("Escaped %d XML-invalid characters in %s. Will be unable "
 
90
                  "to regenerate the SHA map.", num_escaped, commit)
 
91
 
 
92
 
 
93
def warn_unusual_mode(commit, path, mode):
 
94
    trace.mutter("Unusual file mode %o for %s in %s. Storing as revision "
 
95
                 "property. ", mode, path, commit)
33
96
 
34
97
 
35
98
class BzrGitMapping(foreign.VcsMapping):
36
99
    """Class that maps between Git and Bazaar semantics."""
37
100
    experimental = False
38
101
 
 
102
    BZR_FILE_IDS_FILE = '.bzrfileids'
 
103
 
 
104
    BZR_DUMMY_FILE = '.bzrdummy'
 
105
 
39
106
    def __init__(self):
40
107
        super(BzrGitMapping, self).__init__(foreign_git)
41
108
 
42
109
    def __eq__(self, other):
43
 
        return type(self) == type(other) and self.revid_prefix == other.revid_prefix
 
110
        return (type(self) == type(other) and 
 
111
                self.revid_prefix == other.revid_prefix)
44
112
 
45
113
    @classmethod
46
114
    def revision_id_foreign_to_bzr(cls, git_rev_id):
47
115
        """Convert a git revision id handle to a Bazaar revision id."""
 
116
        from dulwich.protocol import ZERO_SHA
 
117
        if git_rev_id == ZERO_SHA:
 
118
            return NULL_REVISION
48
119
        return "%s:%s" % (cls.revid_prefix, git_rev_id)
49
120
 
50
121
    @classmethod
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'))
61
 
 
62
 
    def import_commit(self, commit):
 
133
        return escape_file_id(path)
 
134
 
 
135
    def is_control_file(self, path):
 
136
        return path in (self.BZR_FILE_IDS_FILE, self.BZR_DUMMY_FILE)
 
137
 
 
138
    def parse_file_id(self, file_id):
 
139
        if file_id == ROOT_ID:
 
140
            return ""
 
141
        return unescape_file_id(file_id)
 
142
 
 
143
    def revid_as_refname(self, revid):
 
144
        import urllib
 
145
        return "refs/bzr/%s" % urllib.quote(revid)
 
146
 
 
147
    def import_unusual_file_modes(self, rev, unusual_file_modes):
 
148
        if unusual_file_modes:
 
149
            ret = [(path, unusual_file_modes[path])
 
150
                   for path in sorted(unusual_file_modes.keys())]
 
151
            rev.properties['file-modes'] = bencode.bencode(ret)
 
152
 
 
153
    def export_unusual_file_modes(self, rev):
 
154
        try:
 
155
            file_modes = rev.properties['file-modes']
 
156
        except KeyError:
 
157
            return {}
 
158
        else:
 
159
            return dict([(self.generate_file_id(path), mode) for (path, mode) in bencode.bdecode(file_modes.encode("utf-8"))])
 
160
 
 
161
    def _generate_git_svn_metadata(self, rev, encoding):
 
162
        try:
 
163
            git_svn_id = rev.properties["git-svn-id"]
 
164
        except KeyError:
 
165
            return ""
 
166
        else:
 
167
            return "\ngit-svn-id: %s\n" % git_svn_id.encode(encoding)
 
168
 
 
169
    def _generate_hg_message_tail(self, rev):
 
170
        extra = {}
 
171
        renames = []
 
172
        branch = 'default'
 
173
        for name in rev.properties:
 
174
            if name == 'hg:extra:branch':
 
175
                branch = rev.properties['hg:extra:branch']
 
176
            elif name.startswith('hg:extra'):
 
177
                extra[name[len('hg:extra:'):]] = base64.b64decode(
 
178
                    rev.properties[name])
 
179
            elif name == 'hg:renames':
 
180
                renames = bencode.bdecode(base64.b64decode(
 
181
                    rev.properties['hg:renames']))
 
182
            # TODO: Export other properties as 'bzr:' extras?
 
183
        ret = format_hg_metadata(renames, branch, extra)
 
184
        assert isinstance(ret, str)
 
185
        return ret
 
186
 
 
187
    def _extract_git_svn_metadata(self, rev, message):
 
188
        lines = message.split("\n")
 
189
        if not (lines[-1] == "" and lines[-2].startswith("git-svn-id:")):
 
190
            return message
 
191
        git_svn_id = lines[-2].split(": ", 1)[1]
 
192
        rev.properties['git-svn-id'] = git_svn_id
 
193
        (url, rev, uuid) = parse_git_svn_id(git_svn_id)
 
194
        # FIXME: Convert this to converted-from property somehow..
 
195
        ret = "\n".join(lines[:-2])
 
196
        assert isinstance(ret, str)
 
197
        return ret
 
198
 
 
199
    def _extract_hg_metadata(self, rev, message):
 
200
        (message, renames, branch, extra) = extract_hg_metadata(message)
 
201
        if branch is not None:
 
202
            rev.properties['hg:extra:branch'] = branch
 
203
        for name, value in extra.iteritems():
 
204
            rev.properties['hg:extra:' + name] = base64.b64encode(value)
 
205
        if renames:
 
206
            rev.properties['hg:renames'] = base64.b64encode(bencode.bencode(
 
207
                [(new, old) for (old, new) in renames.iteritems()]))
 
208
        return message
 
209
 
 
210
    def _extract_bzr_metadata(self, rev, message):
 
211
        (message, metadata) = extract_bzr_metadata(message)
 
212
        return message, metadata
 
213
 
 
214
    def _decode_commit_message(self, rev, message, encoding):
 
215
        message, metadata = self._extract_bzr_metadata(rev, message)
 
216
        return message.decode(encoding), metadata
 
217
 
 
218
    def _encode_commit_message(self, rev, message, encoding):
 
219
        return message.encode(encoding)
 
220
 
 
221
    def export_fileid_map(self, fileid_map):
 
222
        """Export a file id map to a fileid map.
 
223
 
 
224
        :param fileid_map: File id map, mapping paths to file ids
 
225
        :return: A Git blob object
 
226
        """
 
227
        from dulwich.objects import Blob
 
228
        b = Blob()
 
229
        b.set_raw_chunks(serialize_fileid_map(fileid_map))
 
230
        return b
 
231
 
 
232
    def export_commit(self, rev, tree_sha, parent_lookup, roundtrip):
 
233
        """Turn a Bazaar revision in to a Git commit
 
234
 
 
235
        :param tree_sha: Tree sha for the commit
 
236
        :param parent_lookup: Function for looking up the GIT sha equiv of a
 
237
            bzr revision
 
238
        :return dulwich.objects.Commit represent the revision:
 
239
        """
 
240
        from dulwich.objects import Commit
 
241
        commit = Commit()
 
242
        commit.tree = tree_sha
 
243
        if roundtrip:
 
244
            metadata = BzrGitRevisionMetadata()
 
245
        else:
 
246
            metadata = None
 
247
        parents = []
 
248
        for p in rev.parent_ids:
 
249
            try:
 
250
                git_p = parent_lookup(p)
 
251
            except KeyError:
 
252
                git_p = None
 
253
                if metadata is not None:
 
254
                    metadata.explicit_parent_ids = rev.parent_ids
 
255
            if git_p is not None:
 
256
                assert len(git_p) == 40, "unexpected length for %r" % git_p
 
257
                parents.append(git_p)
 
258
        commit.parents = parents
 
259
        try:
 
260
            encoding = rev.properties['git-explicit-encoding']
 
261
        except KeyError:
 
262
            encoding = rev.properties.get('git-implicit-encoding', 'utf-8')
 
263
        commit.encoding = rev.properties.get('git-explicit-encoding')
 
264
        commit.committer = fix_person_identifier(rev.committer.encode(
 
265
            encoding))
 
266
        commit.author = fix_person_identifier(
 
267
            rev.get_apparent_authors()[0].encode(encoding))
 
268
        commit.commit_time = long(rev.timestamp)
 
269
        if 'author-timestamp' in rev.properties:
 
270
            commit.author_time = long(rev.properties['author-timestamp'])
 
271
        else:
 
272
            commit.author_time = commit.commit_time
 
273
        commit._commit_timezone_neg_utc = "commit-timezone-neg-utc" in rev.properties
 
274
        commit.commit_timezone = rev.timezone
 
275
        commit._author_timezone_neg_utc = "author-timezone-neg-utc" in rev.properties
 
276
        if 'author-timezone' in rev.properties:
 
277
            commit.author_timezone = int(rev.properties['author-timezone'])
 
278
        else:
 
279
            commit.author_timezone = commit.commit_timezone
 
280
        commit.message = self._encode_commit_message(rev, rev.message, 
 
281
            encoding)
 
282
        assert type(commit.message) == str
 
283
        if metadata is not None:
 
284
            try:
 
285
                mapping_registry.parse_revision_id(rev.revision_id)
 
286
            except errors.InvalidRevisionId:
 
287
                metadata.revision_id = rev.revision_id
 
288
            mapping_properties = set(
 
289
                ['author', 'author-timezone', 'author-timezone-neg-utc',
 
290
                 'commit-timezone-neg-utc', 'git-implicit-encoding',
 
291
                 'git-explicit-encoding', 'author-timestamp', 'file-modes'])
 
292
            for k, v in rev.properties.iteritems():
 
293
                if not k in mapping_properties:
 
294
                    metadata.properties[k] = v
 
295
        commit.message = inject_bzr_metadata(commit.message, metadata, 
 
296
                                             encoding)
 
297
        assert type(commit.message) == str
 
298
        return commit
 
299
 
 
300
    def import_fileid_map(self, blob):
 
301
        """Convert a git file id map blob.
 
302
 
 
303
        :param blob: Git blob object with fileid map
 
304
        :return: Dictionary mapping paths to file ids
 
305
        """
 
306
        return deserialize_fileid_map(blob.data)
 
307
 
 
308
    def import_commit(self, commit, lookup_parent_revid):
63
309
        """Convert a git commit to a bzr revision.
64
310
 
65
 
        :return: a `bzrlib.revision.Revision` object.
 
311
        :return: a `bzrlib.revision.Revision` object and a 
 
312
            dictionary of path -> file ids
66
313
        """
67
314
        if commit is None:
68
315
            raise AssertionError("Commit object can't be None")
69
 
        rev = ForeignRevision(commit.id, self, self.revision_id_foreign_to_bzr(commit.id))
70
 
        rev.parent_ids = tuple([self.revision_id_foreign_to_bzr(p) for p in commit.parents])
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")
 
316
        rev = ForeignRevision(commit.id, self,
 
317
                self.revision_id_foreign_to_bzr(commit.id))
 
318
        rev.parent_ids = tuple([lookup_parent_revid(p) for p in commit.parents])
 
319
        rev.git_metadata = None
 
320
        def decode_using_encoding(rev, commit, encoding):
 
321
            rev.committer = str(commit.committer).decode(encoding)
 
322
            if commit.committer != commit.author:
 
323
                rev.properties['author'] = str(commit.author).decode(encoding)
 
324
            rev.message, rev.git_metadata = self._decode_commit_message(
 
325
                rev, commit.message, encoding)
 
326
        if commit.encoding is not None:
 
327
            rev.properties['git-explicit-encoding'] = commit.encoding
 
328
            decode_using_encoding(rev, commit, commit.encoding)
 
329
        else:
 
330
            for encoding in ('utf-8', 'latin1'):
 
331
                try:
 
332
                    decode_using_encoding(rev, commit, encoding)
 
333
                except UnicodeDecodeError:
 
334
                    pass
 
335
                else:
 
336
                    if encoding != 'utf-8':
 
337
                        rev.properties['git-implicit-encoding'] = encoding
 
338
                    break
 
339
        if commit.commit_time != commit.author_time:
 
340
            rev.properties['author-timestamp'] = str(commit.author_time)
 
341
        if commit.commit_timezone != commit.author_timezone:
 
342
            rev.properties['author-timezone'] = "%d" % commit.author_timezone
 
343
        if commit._author_timezone_neg_utc:
 
344
            rev.properties['author-timezone-neg-utc'] = ""
 
345
        if commit._commit_timezone_neg_utc:
 
346
            rev.properties['commit-timezone-neg-utc'] = ""
75
347
        rev.timestamp = commit.commit_time
76
 
        rev.timezone = 0
 
348
        rev.timezone = commit.commit_timezone
 
349
        if rev.git_metadata is not None:
 
350
            md = rev.git_metadata
 
351
            if md.revision_id:
 
352
                rev.revision_id = md.revision_id
 
353
            if md.explicit_parent_ids:
 
354
                rev.parent_ids = md.explicit_parent_ids
 
355
            rev.properties.update(md.properties)
77
356
        return rev
78
357
 
 
358
    def get_fileid_map(self, lookup_object, tree_sha):
 
359
        """Obtain a fileid map for a particular tree.
 
360
 
 
361
        :param lookup_object: Function for looking up an object
 
362
        :param tree_sha: SHA of the root tree
 
363
        :return: GitFileIdMap instance
 
364
        """
 
365
        try:
 
366
            file_id_map_sha = lookup_object(tree_sha)[self.BZR_FILE_IDS_FILE][1]
 
367
        except KeyError:
 
368
            file_ids = {}
 
369
        else:
 
370
            file_ids = self.import_fileid_map(lookup_object(file_id_map_sha))
 
371
        return GitFileIdMap(file_ids, self)
 
372
 
79
373
 
80
374
class BzrGitMappingv1(BzrGitMapping):
81
375
    revid_prefix = 'git-v1'
82
376
    experimental = False
83
377
 
 
378
    def __str__(self):
 
379
        return self.revid_prefix
 
380
 
84
381
 
85
382
class BzrGitMappingExperimental(BzrGitMappingv1):
86
383
    revid_prefix = 'git-experimental'
87
384
    experimental = True
88
385
 
 
386
    def _decode_commit_message(self, rev, message, encoding):
 
387
        message = self._extract_hg_metadata(rev, message)
 
388
        message = self._extract_git_svn_metadata(rev, message)
 
389
        message, metadata = self._extract_bzr_metadata(rev, message)
 
390
        return message.decode(encoding), metadata
 
391
 
 
392
    def _encode_commit_message(self, rev, message, encoding):
 
393
        ret = message.encode(encoding)
 
394
        ret += self._generate_hg_message_tail(rev)
 
395
        ret += self._generate_git_svn_metadata(rev, encoding)
 
396
        return ret
 
397
 
 
398
    def import_commit(self, commit, lookup_parent_revid):
 
399
        rev, file_ids = super(BzrGitMappingExperimental, self).import_commit(commit, lookup_parent_revid)
 
400
        rev.properties['converted_revision'] = "git %s\n" % commit.id
 
401
        return rev, file_ids
 
402
 
89
403
 
90
404
class GitMappingRegistry(VcsMappingRegistry):
 
405
    """Registry with available git mappings."""
91
406
 
92
407
    def revision_id_bzr_to_foreign(self, bzr_revid):
 
408
        if bzr_revid == NULL_REVISION:
 
409
            from dulwich.protocol import ZERO_SHA
 
410
            return ZERO_SHA, None
93
411
        if not bzr_revid.startswith("git-"):
94
412
            raise errors.InvalidRevisionId(bzr_revid, None)
95
413
        (mapping_version, git_sha) = bzr_revid.split(":", 1)
101
419
 
102
420
mapping_registry = GitMappingRegistry()
103
421
mapping_registry.register_lazy('git-v1', "bzrlib.plugins.git.mapping",
104
 
                                   "BzrGitMappingv1")
105
 
mapping_registry.register_lazy('git-experimental', "bzrlib.plugins.git.mapping",
106
 
                                   "BzrGitMappingExperimental")
 
422
    "BzrGitMappingv1")
 
423
mapping_registry.register_lazy('git-experimental',
 
424
    "bzrlib.plugins.git.mapping", "BzrGitMappingExperimental")
 
425
mapping_registry.set_default('git-v1')
107
426
 
108
427
 
109
428
class ForeignGit(ForeignVcs):
110
 
    """Foreign Git."""
 
429
    """The Git Stupid Content Tracker"""
 
430
 
 
431
    @property
 
432
    def branch_format(self):
 
433
        from bzrlib.plugins.git.branch import GitBranchFormat
 
434
        return GitBranchFormat()
 
435
 
 
436
    @property
 
437
    def repository_format(self):
 
438
        from bzrlib.plugins.git.repository import GitRepositoryFormat
 
439
        return GitRepositoryFormat()
111
440
 
112
441
    def __init__(self):
113
442
        super(ForeignGit, self).__init__(mapping_registry)
 
443
        self.abbreviation = "git"
 
444
 
 
445
    @classmethod
 
446
    def serialize_foreign_revid(self, foreign_revid):
 
447
        return foreign_revid
114
448
 
115
449
    @classmethod
116
450
    def show_foreign_revid(cls, foreign_revid):
118
452
 
119
453
 
120
454
foreign_git = ForeignGit()
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
127
 
    import stat
128
 
    stack = []
129
 
    cur = ""
 
455
default_mapping = mapping_registry.get_default()()
 
456
 
 
457
 
 
458
def symlink_to_blob(entry):
 
459
    from dulwich.objects import Blob
 
460
    blob = Blob()
 
461
    symlink_target = entry.symlink_target
 
462
    if type(symlink_target) == unicode:
 
463
        symlink_target = symlink_target.encode('utf-8')
 
464
    blob.data = symlink_target
 
465
    return blob
 
466
 
 
467
 
 
468
def mode_is_executable(mode):
 
469
    """Check if mode should be considered executable."""
 
470
    return bool(mode & 0111)
 
471
 
 
472
 
 
473
def mode_kind(mode):
 
474
    """Determine the Bazaar inventory kind based on Unix file mode."""
 
475
    entry_kind = (mode & 0700000) / 0100000
 
476
    if entry_kind == 0:
 
477
        return 'directory'
 
478
    elif entry_kind == 1:
 
479
        file_kind = (mode & 070000) / 010000
 
480
        if file_kind == 0:
 
481
            return 'file'
 
482
        elif file_kind == 2:
 
483
            return 'symlink'
 
484
        elif file_kind == 6:
 
485
            return 'tree-reference'
 
486
        else:
 
487
            raise AssertionError(
 
488
                "Unknown file kind %d, perms=%o." % (file_kind, mode,))
 
489
    else:
 
490
        raise AssertionError(
 
491
            "Unknown kind, perms=%r." % (mode,))
 
492
 
 
493
 
 
494
def object_mode(kind, executable):
 
495
    if kind == 'directory':
 
496
        return stat.S_IFDIR
 
497
    elif kind == 'symlink':
 
498
        mode = stat.S_IFLNK
 
499
        if executable:
 
500
            mode |= 0111
 
501
        return mode
 
502
    elif kind == 'file':
 
503
        mode = stat.S_IFREG | 0644
 
504
        if executable:
 
505
            mode |= 0111
 
506
        return mode
 
507
    elif kind == 'tree-reference':
 
508
        from dulwich.objects import S_IFGITLINK
 
509
        return S_IFGITLINK
 
510
    else:
 
511
        raise AssertionError
 
512
 
 
513
 
 
514
def entry_mode(entry):
 
515
    """Determine the git file mode for an inventory entry."""
 
516
    return object_mode(entry.kind, entry.executable)
 
517
 
 
518
 
 
519
def directory_to_tree(entry, lookup_ie_sha1, unusual_modes, empty_file_name):
 
520
    """Create a Git Tree object from a Bazaar directory.
 
521
 
 
522
    :param entry: Inventory entry
 
523
    :param lookup_ie_sha1: Lookup the Git SHA1 for a inventory entry
 
524
    :param unusual_modes: Dictionary with unusual file modes by file ids
 
525
    :param empty_file_name: Name to use for dummy files in empty directories,
 
526
        None to ignore empty directories.
 
527
    """
 
528
    from dulwich.objects import Blob, Tree
130
529
    tree = Tree()
131
 
 
132
 
    inv = repo.get_inventory(revision_id)
133
 
 
134
 
    # stack contains the set of trees that we haven't 
135
 
    # finished constructing
136
 
 
137
 
    for path, entry in inv.iter_entries():
138
 
        while stack and not path.startswith(cur):
139
 
            tree.serialize()
140
 
            sha = tree.sha().hexdigest()
141
 
            yield sha, tree, cur
142
 
            t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
143
 
            cur, tree = stack.pop()
144
 
            tree.add(*t)
145
 
 
146
 
        if type(entry) == InventoryDirectory:
147
 
            stack.append((cur, tree))
148
 
            cur = path
149
 
            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
 
 
159
 
            name = urlutils.basename(path).encode("utf-8")
160
 
            mode = stat.S_IFREG | 0644
161
 
            if entry.executable:
162
 
                mode |= 0111
163
 
            tree.add(mode, name, sha)
164
 
 
165
 
    while len(stack) > 1:
166
 
        tree.serialize()
167
 
        sha = tree.sha().hexdigest()
168
 
        yield sha, tree, cur
169
 
        t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
170
 
        cur, tree = stack.pop()
171
 
        tree.add(*t)
172
 
 
173
 
    tree.serialize()
174
 
    yield tree.sha().hexdigest(), tree, cur
175
 
 
176
 
 
177
 
def revision_to_commit(rev, tree_sha, parent_lookup):
178
 
    """Turn a Bazaar revision in to a Git commit
179
 
 
180
 
    :param tree_sha: Tree sha for the commit
181
 
    :param parent_lookup: Function for looking up the GIT sha equiv of a bzr revision
182
 
    :return dulwich.objects.Commit represent the revision:
183
 
    """
184
 
    from dulwich.objects import Commit
185
 
    commit = Commit()
186
 
    commit._tree = tree_sha
187
 
    for p in rev.parent_ids:
188
 
        git_p = parent_lookup(p)
189
 
        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()
196
 
    return commit
 
530
    for name, value in entry.children.iteritems():
 
531
        ie = entry.children[name]
 
532
        try:
 
533
            mode = unusual_modes[ie.file_id]
 
534
        except KeyError:
 
535
            mode = entry_mode(ie)
 
536
        hexsha = lookup_ie_sha1(ie)
 
537
        if hexsha is not None:
 
538
            tree.add(mode, name.encode("utf-8"), hexsha)
 
539
    if entry.parent_id is not None and len(tree) == 0:
 
540
        # Only the root can be an empty tree
 
541
        if empty_file_name is not None:
 
542
            tree.add(stat.S_IFREG | 0644, empty_file_name, 
 
543
                Blob().id)
 
544
        else:
 
545
            return None
 
546
    return tree
 
547
 
 
548
 
 
549
def extract_unusual_modes(rev):
 
550
    try:
 
551
        foreign_revid, mapping = mapping_registry.parse_revision_id(
 
552
            rev.revision_id)
 
553
    except errors.InvalidRevisionId:
 
554
        return {}
 
555
    else:
 
556
        return mapping.export_unusual_file_modes(rev)
 
557
 
 
558
 
 
559
def parse_git_svn_id(text):
 
560
    (head, uuid) = text.rsplit(" ", 1)
 
561
    (full_url, rev) = head.rsplit("@", 1)
 
562
    return (full_url, int(rev), uuid)
 
563
 
 
564
 
 
565
class GitFileIdMap(object):
 
566
 
 
567
    def __init__(self, file_ids, mapping):
 
568
        self.file_ids = file_ids
 
569
        self.paths = None
 
570
        self.mapping = mapping
 
571
 
 
572
    def lookup_file_id(self, path):
 
573
        try:
 
574
            return self.file_ids[path]
 
575
        except KeyError:
 
576
            return self.mapping.generate_file_id(path)
 
577
 
 
578
    def lookup_path(self, file_id):
 
579
        if self.paths is None:
 
580
            self.paths = {}
 
581
            for k, v in self.file_ids.iteritems():
 
582
                self.paths[v] = k
 
583
        try:
 
584
            return self.paths[file_id]
 
585
        except KeyError:
 
586
            return self.mapping.parse_file_id(file_id)