/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 formatting, remove catch-all for exceptions when opening local repositories.

Show diffs side-by-side

added added

removed removed

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