/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: 2009-10-12 09:41:06 UTC
  • mto: (0.200.644 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20091012094106-4ubc41ms0mr22v52
Use foreign branch testing infrastructure.

Show diffs side-by-side

added added

removed removed

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