/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 breezy/git/mapping.py

  • Committer: Jelmer Vernooij
  • Date: 2019-03-05 07:32:38 UTC
  • mto: (7290.1.21 work)
  • mto: This revision was merged to the branch mainline in revision 7311.
  • Revision ID: jelmer@jelmer.uk-20190305073238-zlqn981opwnqsmzi
Add appveyor configuration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 
19
19
"""Converters, etc for going between Bazaar and Git ids."""
20
20
 
 
21
from __future__ import absolute_import
 
22
 
21
23
import base64
22
24
import stat
23
25
 
37
39
    NULL_REVISION,
38
40
    Revision,
39
41
    )
 
42
from ..sixish import (
 
43
    PY3,
 
44
    text_type,
 
45
    viewitems,
 
46
    )
40
47
from .errors import (
41
48
    NoPushSupport,
 
49
    UnknownCommitExtra,
 
50
    UnknownMercurialCommitExtra,
42
51
    )
43
52
from .hg import (
44
53
    format_hg_metadata,
48
57
    extract_bzr_metadata,
49
58
    inject_bzr_metadata,
50
59
    CommitSupplement,
 
60
    deserialize_fileid_map,
 
61
    serialize_fileid_map,
51
62
    )
52
63
 
53
64
 
64
75
ROOT_ID = b"TREE_ROOT"
65
76
 
66
77
 
67
 
class UnknownCommitExtra(errors.BzrError):
68
 
    _fmt = "Unknown extra fields in %(object)r: %(fields)r."
69
 
 
70
 
    def __init__(self, object, fields):
71
 
        errors.BzrError.__init__(self)
72
 
        self.object = object
73
 
        self.fields = ",".join(fields)
74
 
 
75
 
 
76
 
class UnknownMercurialCommitExtra(errors.BzrError):
77
 
    _fmt = "Unknown mercurial extra fields in %(object)r: %(fields)r."
78
 
 
79
 
    def __init__(self, object, fields):
80
 
        errors.BzrError.__init__(self)
81
 
        self.object = object
82
 
        self.fields = b",".join(fields)
83
 
 
84
 
 
85
78
def escape_file_id(file_id):
86
79
    file_id = file_id.replace(b'_', b'__')
87
80
    file_id = file_id.replace(b' ', b'_s')
114
107
    if b"<" not in text and b">" not in text:
115
108
        username = text
116
109
        email = text
117
 
    elif b">" not in text:
118
 
        return text + b">"
119
110
    else:
120
111
        if text.rindex(b">") < text.rindex(b"<"):
121
112
            raise ValueError(text)
126
117
    return b"%s <%s>" % (username, email)
127
118
 
128
119
 
129
 
def decode_git_path(path):
130
 
    """Take a git path and decode it."""
131
 
    try:
132
 
        return path.decode('utf-8')
133
 
    except UnicodeDecodeError:
134
 
        if PY3:
135
 
            return path.decode('utf-8', 'surrogateescape')
136
 
        raise
137
 
 
138
 
 
139
 
def encode_git_path(path):
140
 
    """Take a regular path and encode it for git."""
141
 
    try:
142
 
        return path.encode('utf-8')
143
 
    except UnicodeEncodeError:
144
 
        if PY3:
145
 
            return path.encode('utf-8', 'surrogateescape')
146
 
        raise
147
 
 
148
 
 
149
120
def warn_escaped(commit, num_escaped):
150
121
    trace.warning("Escaped %d XML-invalid characters in %s. Will be unable "
151
122
                  "to regenerate the SHA map.", num_escaped, commit)
160
131
    """Class that maps between Git and Bazaar semantics."""
161
132
    experimental = False
162
133
 
163
 
    BZR_DUMMY_FILE = None  # type: Optional[str]
 
134
    BZR_FILE_IDS_FILE = None
 
135
 
 
136
    BZR_DUMMY_FILE = None
164
137
 
165
138
    def is_special_file(self, filename):
166
 
        return (filename in (self.BZR_DUMMY_FILE, ))
 
139
        return (filename in (self.BZR_FILE_IDS_FILE, self.BZR_DUMMY_FILE))
167
140
 
168
141
    def __init__(self):
169
142
        super(BzrGitMapping, self).__init__(foreign_vcs_git)
190
163
    def generate_file_id(self, path):
191
164
        # Git paths are just bytestrings
192
165
        # We must just hope they are valid UTF-8..
193
 
        if isinstance(path, str):
 
166
        if isinstance(path, text_type):
194
167
            path = path.encode("utf-8")
195
168
        if path == b"":
196
169
            return ROOT_ID
201
174
            return u""
202
175
        if not file_id.startswith(FILE_ID_PREFIX):
203
176
            raise ValueError
204
 
        return decode_git_path(unescape_file_id(file_id[len(FILE_ID_PREFIX):]))
 
177
        return unescape_file_id(file_id[len(FILE_ID_PREFIX):]).decode('utf-8')
 
178
 
 
179
    def revid_as_refname(self, revid):
 
180
        if not isinstance(revid, bytes):
 
181
            raise TypeError(revid)
 
182
        if PY3:
 
183
            revid = revid.decode('utf-8')
 
184
        quoted_revid = urlutils.quote(revid)
 
185
        return b"refs/bzr/" + quoted_revid.encode('utf-8')
205
186
 
206
187
    def import_unusual_file_modes(self, rev, unusual_file_modes):
207
188
        if unusual_file_modes:
259
240
        (message, renames, branch, extra) = extract_hg_metadata(message)
260
241
        if branch is not None:
261
242
            rev.properties[u'hg:extra:branch'] = branch
262
 
        for name, value in extra.items():
 
243
        for name, value in viewitems(extra):
263
244
            rev.properties[u'hg:extra:' + name] = base64.b64encode(value)
264
245
        if renames:
265
246
            rev.properties[u'hg:renames'] = base64.b64encode(bencode.bencode(
266
 
                [(new, old) for (old, new) in renames.items()]))
 
247
                [(new, old) for (old, new) in viewitems(renames)]))
267
248
        return message
268
249
 
269
250
    def _extract_bzr_metadata(self, rev, message):
276
257
    def _encode_commit_message(self, rev, message, encoding):
277
258
        return message.encode(encoding)
278
259
 
 
260
    def export_fileid_map(self, fileid_map):
 
261
        """Export a file id map to a fileid map.
 
262
 
 
263
        :param fileid_map: File id map, mapping paths to file ids
 
264
        :return: A Git blob object (or None if there are no entries)
 
265
        """
 
266
        from dulwich.objects import Blob
 
267
        b = Blob()
 
268
        b.set_raw_chunks(serialize_fileid_map(fileid_map))
 
269
        return b
 
270
 
279
271
    def export_commit(self, rev, tree_sha, parent_lookup, lossy,
280
272
                      verifiers):
281
273
        """Turn a Bazaar revision in to a Git commit
339
331
            commit.author_timezone = commit.commit_timezone
340
332
        if u'git-gpg-signature' in rev.properties:
341
333
            commit.gpgsig = rev.properties[u'git-gpg-signature'].encode(
342
 
                'utf-8', 'surrogateescape')
 
334
                'ascii')
343
335
        commit.message = self._encode_commit_message(rev, rev.message,
344
336
                                                     encoding)
345
337
        if not isinstance(commit.message, bytes):
354
346
                 u'commit-timezone-neg-utc', u'git-implicit-encoding',
355
347
                 u'git-gpg-signature', u'git-explicit-encoding',
356
348
                 u'author-timestamp', u'file-modes'])
357
 
            for k, v in rev.properties.items():
 
349
            for k, v in viewitems(rev.properties):
358
350
                if k not in mapping_properties:
359
351
                    metadata.properties[k] = v
360
352
        if not lossy and metadata:
378
370
                 for l in rev.properties[u'git-extra'].splitlines()])
379
371
        return commit
380
372
 
 
373
    def import_fileid_map(self, blob):
 
374
        """Convert a git file id map blob.
 
375
 
 
376
        :param blob: Git blob object with fileid map
 
377
        :return: Dictionary mapping paths to file ids
 
378
        """
 
379
        return deserialize_fileid_map(blob.data)
 
380
 
381
381
    def get_revision_id(self, commit):
382
382
        if commit.encoding:
383
383
            encoding = commit.encoding.decode('ascii')
384
384
        else:
385
385
            encoding = 'utf-8'
386
 
        try:
387
 
            message, metadata = self._decode_commit_message(
388
 
                None, commit.message, encoding)
389
 
        except UnicodeDecodeError:
390
 
            pass
391
 
        else:
392
 
            if metadata.revision_id:
393
 
                return metadata.revision_id
 
386
        message, metadata = self._decode_commit_message(
 
387
            None, commit.message, encoding)
 
388
        if metadata.revision_id:
 
389
            return metadata.revision_id
394
390
        return self.revision_id_foreign_to_bzr(commit.id)
395
391
 
396
 
    def import_commit(self, commit, lookup_parent_revid, strict=True):
 
392
    def import_commit(self, commit, lookup_parent_revid):
397
393
        """Convert a git commit to a bzr revision.
398
394
 
399
395
        :return: a `breezy.revision.Revision` object, foreign revid and a
435
431
            rev.properties[u'commit-timezone-neg-utc'] = ""
436
432
        if commit.gpgsig:
437
433
            rev.properties[u'git-gpg-signature'] = commit.gpgsig.decode(
438
 
                'utf-8', 'surrogateescape')
 
434
                'ascii')
439
435
        if commit.mergetag:
440
436
            for i, tag in enumerate(commit.mergetag):
441
437
                rev.properties[u'git-mergetag-%d' % i] = tag.as_raw_string()
467
463
                extra_lines.append(k + b' ' + v + b'\n')
468
464
            elif k == HG_EXTRA:
469
465
                hgk, hgv = v.split(b':', 1)
470
 
                if hgk not in (HG_EXTRA_AMEND_SOURCE, ) and strict:
 
466
                if hgk not in (HG_EXTRA_AMEND_SOURCE, ):
471
467
                    raise UnknownMercurialCommitExtra(commit, [hgk])
472
468
                extra_lines.append(k + b' ' + v + b'\n')
473
469
            else:
474
470
                unknown_extra_fields.append(k)
475
 
        if unknown_extra_fields and strict:
 
471
        if unknown_extra_fields:
476
472
            raise UnknownCommitExtra(
477
473
                commit,
478
474
                [f.decode('ascii', 'replace') for f in unknown_extra_fields])
480
476
            rev.properties[u'git-extra'] = b''.join(extra_lines)
481
477
        return rev, roundtrip_revid, verifiers
482
478
 
 
479
    def get_fileid_map(self, lookup_object, tree_sha):
 
480
        """Obtain a fileid map for a particular tree.
 
481
 
 
482
        :param lookup_object: Function for looking up an object
 
483
        :param tree_sha: SHA of the root tree
 
484
        :return: GitFileIdMap instance
 
485
        """
 
486
        try:
 
487
            file_id_map_sha = lookup_object(
 
488
                tree_sha)[self.BZR_FILE_IDS_FILE][1]
 
489
        except KeyError:
 
490
            file_ids = {}
 
491
        else:
 
492
            file_ids = self.import_fileid_map(lookup_object(file_id_map_sha))
 
493
        return GitFileIdMap(file_ids, self)
 
494
 
483
495
 
484
496
class BzrGitMappingv1(BzrGitMapping):
485
497
    revid_prefix = b'git-v1'
492
504
class BzrGitMappingExperimental(BzrGitMappingv1):
493
505
    revid_prefix = b'git-experimental'
494
506
    experimental = True
495
 
    roundtripping = False
 
507
    roundtripping = True
 
508
 
 
509
    BZR_FILE_IDS_FILE = '.bzrfileids'
496
510
 
497
511
    BZR_DUMMY_FILE = '.bzrdummy'
498
512
 
510
524
        ret += self._generate_git_svn_metadata(rev, encoding)
511
525
        return ret
512
526
 
513
 
    def import_commit(self, commit, lookup_parent_revid, strict=True):
 
527
    def import_commit(self, commit, lookup_parent_revid):
514
528
        rev, roundtrip_revid, verifiers = super(
515
529
            BzrGitMappingExperimental, self).import_commit(
516
 
                commit, lookup_parent_revid, strict)
 
530
                commit, lookup_parent_revid)
517
531
        rev.properties[u'converted_revision'] = "git %s\n" % commit.id
518
532
        return rev, roundtrip_revid, verifiers
519
533
 
581
595
def symlink_to_blob(symlink_target):
582
596
    from dulwich.objects import Blob
583
597
    blob = Blob()
584
 
    if isinstance(symlink_target, str):
585
 
        symlink_target = encode_git_path(symlink_target)
 
598
    if isinstance(symlink_target, text_type):
 
599
        symlink_target = symlink_target.encode('utf-8')
586
600
    blob.data = symlink_target
587
601
    return blob
588
602
 
656
670
    return (full_url, int(rev), uuid)
657
671
 
658
672
 
 
673
class GitFileIdMap(object):
 
674
 
 
675
    def __init__(self, file_ids, mapping):
 
676
        self.file_ids = file_ids
 
677
        self.paths = None
 
678
        self.mapping = mapping
 
679
 
 
680
    def set_file_id(self, path, file_id):
 
681
        if type(path) is not str:
 
682
            raise TypeError(path)
 
683
        if not isinstance(file_id, bytes):
 
684
            raise TypeError(file_id)
 
685
        self.file_ids[path] = file_id
 
686
 
 
687
    def lookup_file_id(self, path):
 
688
        if not isinstance(path, text_type):
 
689
            raise TypeError(path)
 
690
        try:
 
691
            file_id = self.file_ids[path]
 
692
        except KeyError:
 
693
            file_id = self.mapping.generate_file_id(path)
 
694
        if not isinstance(file_id, bytes):
 
695
            raise TypeError(file_id)
 
696
        return file_id
 
697
 
 
698
    def lookup_path(self, file_id):
 
699
        if self.paths is None:
 
700
            self.paths = {}
 
701
            for k, v in viewitems(self.file_ids):
 
702
                self.paths[v] = k
 
703
        try:
 
704
            path = self.paths[file_id]
 
705
        except KeyError:
 
706
            return self.mapping.parse_file_id(file_id)
 
707
        else:
 
708
            if not isinstance(path, text_type):
 
709
                raise TypeError(path)
 
710
            return path
 
711
 
 
712
    def copy(self):
 
713
        return self.__class__(dict(self.file_ids), self.mapping)
 
714
 
 
715
 
659
716
def needs_roundtripping(repo, revid):
660
717
    try:
661
718
        mapping_registry.parse_revision_id(revid)