/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: Robert Collins
  • Date: 2007-07-15 15:40:37 UTC
  • mto: (2592.3.33 repository)
  • mto: This revision was merged to the branch mainline in revision 2624.
  • Revision ID: robertc@robertcollins.net-20070715154037-3ar8g89decddc9su
Make GraphIndex accept nodes as key, value, references, so that the method
signature is closer to what a simple key->value index delivers. Also
change the behaviour when the reference list count is zero to accept
key, value as nodes, and emit key, value to make it identical in that case
to a simple key->value index. This may not be a good idea, but for now it
seems ok.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
2
 
# Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
3
 
# Copyright (C) 2008 John Carr
4
 
#
5
 
# This program is free software; you can redistribute it and/or modify
6
 
# it under the terms of the GNU General Public License as published by
7
 
# the Free Software Foundation; either version 2 of the License, or
8
 
# (at your option) any later version.
9
 
#
10
 
# This program is distributed in the hope that it will be useful,
11
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 
# GNU General Public License for more details.
14
 
#
15
 
# You should have received a copy of the GNU General Public License
16
 
# along with this program; if not, write to the Free Software
17
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
 
 
19
 
"""Converters, etc for going between Bazaar and Git ids."""
20
 
 
21
 
import stat
22
 
 
23
 
from bzrlib import (
24
 
    errors,
25
 
    foreign,
26
 
    osutils,
27
 
    urlutils,
28
 
    )
29
 
from bzrlib.inventory import (
30
 
    ROOT_ID,
31
 
    )
32
 
from bzrlib.foreign import (
33
 
    ForeignVcs, 
34
 
    VcsMappingRegistry, 
35
 
    ForeignRevision,
36
 
    )
37
 
from bzrlib.xml_serializer import (
38
 
    escape_invalid_chars,
39
 
    )
40
 
 
41
 
DEFAULT_FILE_MODE = stat.S_IFREG | 0644
42
 
 
43
 
 
44
 
def escape_file_id(file_id):
45
 
    return file_id.replace('_', '__').replace(' ', '_s')
46
 
 
47
 
 
48
 
def unescape_file_id(file_id):
49
 
    ret = []
50
 
    i = 0
51
 
    while i < len(file_id):
52
 
        if file_id[i] != '_':
53
 
            ret.append(file_id[i])
54
 
        else:
55
 
            if file_id[i+1] == '_':
56
 
                ret.append("_")
57
 
            elif file_id[i+1] == 's':
58
 
                ret.append(" ")
59
 
            else:
60
 
                raise AssertionError("unknown escape character %s" % file_id[i+1])
61
 
            i += 1
62
 
        i += 1
63
 
    return "".join(ret)
64
 
 
65
 
 
66
 
def fix_person_identifier(text):
67
 
    if "<" in text and ">" in text:
68
 
        return text
69
 
    return "%s <%s>" % (text, text)
70
 
 
71
 
 
72
 
class BzrGitMapping(foreign.VcsMapping):
73
 
    """Class that maps between Git and Bazaar semantics."""
74
 
    experimental = False
75
 
 
76
 
    def __init__(self):
77
 
        super(BzrGitMapping, self).__init__(foreign_git)
78
 
 
79
 
    def __eq__(self, other):
80
 
        return type(self) == type(other) and self.revid_prefix == other.revid_prefix
81
 
 
82
 
    @classmethod
83
 
    def revision_id_foreign_to_bzr(cls, git_rev_id):
84
 
        """Convert a git revision id handle to a Bazaar revision id."""
85
 
        return "%s:%s" % (cls.revid_prefix, git_rev_id)
86
 
 
87
 
    @classmethod
88
 
    def revision_id_bzr_to_foreign(cls, bzr_rev_id):
89
 
        """Convert a Bazaar revision id to a git revision id handle."""
90
 
        if not bzr_rev_id.startswith("%s:" % cls.revid_prefix):
91
 
            raise errors.InvalidRevisionId(bzr_rev_id, cls)
92
 
        return bzr_rev_id[len(cls.revid_prefix)+1:], cls()
93
 
 
94
 
    def generate_file_id(self, path):
95
 
        # Git paths are just bytestrings
96
 
        # We must just hope they are valid UTF-8..
97
 
        if path == "":
98
 
            return ROOT_ID
99
 
        return escape_file_id(path)
100
 
 
101
 
    def parse_file_id(self, file_id):
102
 
        if file_id == ROOT_ID:
103
 
            return ""
104
 
        return unescape_file_id(file_id)
105
 
 
106
 
    def import_commit(self, commit):
107
 
        """Convert a git commit to a bzr revision.
108
 
 
109
 
        :return: a `bzrlib.revision.Revision` object.
110
 
        """
111
 
        if commit is None:
112
 
            raise AssertionError("Commit object can't be None")
113
 
        rev = ForeignRevision(commit.id, self, self.revision_id_foreign_to_bzr(commit.id))
114
 
        rev.parent_ids = tuple([self.revision_id_foreign_to_bzr(p) for p in commit.parents])
115
 
        rev.message = escape_invalid_chars(commit.message.decode("utf-8", "replace"))[0]
116
 
        rev.committer = escape_invalid_chars(str(commit.committer).decode("utf-8", "replace"))[0]
117
 
        if commit.committer != commit.author:
118
 
            rev.properties['author'] = escape_invalid_chars(str(commit.author).decode("utf-8", "replace"))[0]
119
 
 
120
 
        if commit.commit_time != commit.author_time:
121
 
            rev.properties['author-timestamp'] = str(commit.author_time)
122
 
        if commit.commit_timezone != commit.author_timezone:
123
 
            rev.properties['author-timezone'] = "%f" % (commit.author_timezone * .6)
124
 
        rev.timestamp = commit.commit_time
125
 
        rev.timezone = int(commit.commit_timezone * .6)
126
 
        if rev.timezone / .6 != commit.commit_timezone:
127
 
            rev.properties['commit-timezone'] = "%f" % (commit.commit_timezone * .6)
128
 
        return rev
129
 
 
130
 
 
131
 
class BzrGitMappingv1(BzrGitMapping):
132
 
    revid_prefix = 'git-v1'
133
 
    experimental = False
134
 
 
135
 
    def __str__(self):
136
 
        return self.revid_prefix
137
 
 
138
 
 
139
 
class BzrGitMappingExperimental(BzrGitMappingv1):
140
 
    revid_prefix = 'git-experimental'
141
 
    experimental = True
142
 
 
143
 
 
144
 
class GitMappingRegistry(VcsMappingRegistry):
145
 
 
146
 
    def revision_id_bzr_to_foreign(self, bzr_revid):
147
 
        if not bzr_revid.startswith("git-"):
148
 
            raise errors.InvalidRevisionId(bzr_revid, None)
149
 
        (mapping_version, git_sha) = bzr_revid.split(":", 1)
150
 
        mapping = self.get(mapping_version)
151
 
        return mapping.revision_id_bzr_to_foreign(bzr_revid)
152
 
 
153
 
    parse_revision_id = revision_id_bzr_to_foreign
154
 
 
155
 
 
156
 
mapping_registry = GitMappingRegistry()
157
 
mapping_registry.register_lazy('git-v1', "bzrlib.plugins.git.mapping",
158
 
                                   "BzrGitMappingv1")
159
 
mapping_registry.register_lazy('git-experimental', "bzrlib.plugins.git.mapping",
160
 
                                   "BzrGitMappingExperimental")
161
 
 
162
 
 
163
 
class ForeignGit(ForeignVcs):
164
 
    """The Git Stupid Content Tracker"""
165
 
 
166
 
    def __init__(self):
167
 
        super(ForeignGit, self).__init__(mapping_registry)
168
 
 
169
 
    @classmethod
170
 
    def show_foreign_revid(cls, foreign_revid):
171
 
        return { "git commit": foreign_revid }
172
 
 
173
 
 
174
 
foreign_git = ForeignGit()
175
 
default_mapping = BzrGitMappingv1()
176
 
 
177
 
 
178
 
def text_to_blob(texts, entry):
179
 
    from dulwich.objects import Blob
180
 
    text = texts.get_record_stream([(entry.file_id, entry.revision)], 'unordered', True).next().get_bytes_as('fulltext')
181
 
    blob = Blob()
182
 
    blob._text = text
183
 
    return blob
184
 
 
185
 
 
186
 
def symlink_to_blob(entry):
187
 
    from dulwich.objects import Blob
188
 
    blob = Blob()
189
 
    blob._text = entry.symlink_target
190
 
    return blob
191
 
 
192
 
 
193
 
def entry_mode(entry):
194
 
    if entry.kind == 'directory':
195
 
        return stat.S_IFDIR
196
 
    elif entry.kind == 'symlink':
197
 
        return stat.S_IFLNK
198
 
    elif entry.kind == 'file':
199
 
        mode = stat.S_IFREG | 0644
200
 
        if entry.executable:
201
 
            mode |= 0111
202
 
        return mode
203
 
    else:
204
 
        raise AssertionError
205
 
 
206
 
 
207
 
def directory_to_tree(entry, lookup_ie_sha1):
208
 
    from dulwich.objects import Tree
209
 
    tree = Tree()
210
 
    for name in sorted(entry.children.keys()):
211
 
        ie = entry.children[name]
212
 
        tree.add(entry_mode(ie), name.encode("utf-8"), lookup_ie_sha1(ie))
213
 
    tree.serialize()
214
 
    return tree
215
 
 
216
 
 
217
 
def inventory_to_tree_and_blobs(inventory, texts, mapping, cur=None):
218
 
    """Convert a Bazaar tree to a Git tree.
219
 
 
220
 
    :return: Yields tuples with object sha1, object and path
221
 
    """
222
 
    from dulwich.objects import Tree
223
 
    import stat
224
 
    stack = []
225
 
    if cur is None:
226
 
        cur = ""
227
 
    tree = Tree()
228
 
 
229
 
    # stack contains the set of trees that we haven't 
230
 
    # finished constructing
231
 
    for path, entry in inventory.iter_entries():
232
 
        while stack and not path.startswith(osutils.pathjoin(cur, "")):
233
 
            # We've hit a file that's not a child of the previous path
234
 
            tree.serialize()
235
 
            sha = tree.id
236
 
            yield sha, tree, cur.encode("utf-8")
237
 
            t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
238
 
            cur, tree = stack.pop()
239
 
            tree.add(*t)
240
 
 
241
 
        if entry.kind == "directory":
242
 
            stack.append((cur, tree))
243
 
            cur = path
244
 
            tree = Tree()
245
 
        else:
246
 
            if entry.kind == "file":
247
 
                blob = text_to_blob(texts, entry)
248
 
            elif entry.kind == "symlink":
249
 
                blob = symlink_to_blob(entry)
250
 
            else:
251
 
                raise AssertionError("Unknown kind %s" % entry.kind)
252
 
            sha = blob.id
253
 
            yield sha, blob, path.encode("utf-8")
254
 
            name = urlutils.basename(path).encode("utf-8")
255
 
            tree.add(entry_mode(entry), name, sha)
256
 
 
257
 
    while len(stack) > 1:
258
 
        tree.serialize()
259
 
        sha = tree.id
260
 
        yield sha, tree, cur.encode("utf-8")
261
 
        t = (stat.S_IFDIR, urlutils.basename(cur).encode('UTF-8'), sha)
262
 
        cur, tree = stack.pop()
263
 
        tree.add(*t)
264
 
 
265
 
    tree.serialize()
266
 
    yield tree.id, tree, cur.encode("utf-8")
267
 
 
268
 
 
269
 
def revision_to_commit(rev, tree_sha, parent_lookup):
270
 
    """Turn a Bazaar revision in to a Git commit
271
 
 
272
 
    :param tree_sha: Tree sha for the commit
273
 
    :param parent_lookup: Function for looking up the GIT sha equiv of a bzr revision
274
 
    :return dulwich.objects.Commit represent the revision:
275
 
    """
276
 
    from dulwich.objects import Commit
277
 
    commit = Commit()
278
 
    commit.tree = tree_sha
279
 
    for p in rev.parent_ids:
280
 
        git_p = parent_lookup(p)
281
 
        if git_p is not None:
282
 
            assert len(git_p) == 40, "unexpected length for %r" % git_p
283
 
            commit.parents.append(git_p)
284
 
    commit.message = rev.message.encode("utf-8")
285
 
    commit.committer = fix_person_identifier(rev.committer.encode("utf-8"))
286
 
    commit.author = fix_person_identifier(rev.get_apparent_authors()[0].encode("utf-8"))
287
 
    commit.commit_time = long(rev.timestamp)
288
 
    if 'author-timestamp' in rev.properties:
289
 
        commit.author_time = long(rev.properties['author-timestamp'])
290
 
    else:
291
 
        commit.author_time = commit.commit_time
292
 
    if 'committer-timezone' in rev.properties:
293
 
        commit.commit_timezone = int(float(rev.properties['commit-timezone']) / .6)
294
 
    else:
295
 
        commit.commit_timezone = int(rev.timezone / .6) 
296
 
    if 'author-timezone' in rev.properties:
297
 
        commit.author_timezone = int(float(rev.properties['author-timezone']) / .6)
298
 
    else:
299
 
        commit.author_timezone = commit.commit_timezone 
300
 
    return commit