/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

Add simple tests and docstrings for GraphWalker.

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