/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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
 
 
23
21
import base64
24
22
import stat
25
23
 
37
35
    )
38
36
from ..revision import (
39
37
    NULL_REVISION,
40
 
    )
41
 
from ..sixish import (
42
 
    PY3,
43
 
    text_type,
44
 
    viewitems,
 
38
    Revision,
45
39
    )
46
40
from .errors import (
47
41
    NoPushSupport,
48
 
    UnknownCommitExtra,
49
 
    UnknownMercurialCommitExtra,
50
42
    )
51
43
from .hg import (
52
44
    format_hg_metadata,
56
48
    extract_bzr_metadata,
57
49
    inject_bzr_metadata,
58
50
    CommitSupplement,
59
 
    deserialize_fileid_map,
60
 
    serialize_fileid_map,
61
51
    )
62
52
 
63
53
 
74
64
ROOT_ID = b"TREE_ROOT"
75
65
 
76
66
 
 
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
 
77
85
def escape_file_id(file_id):
78
86
    file_id = file_id.replace(b'_', b'__')
79
87
    file_id = file_id.replace(b' ', b'_s')
106
114
    if b"<" not in text and b">" not in text:
107
115
        username = text
108
116
        email = text
 
117
    elif b">" not in text:
 
118
        return text + b">"
109
119
    else:
110
120
        if text.rindex(b">") < text.rindex(b"<"):
111
121
            raise ValueError(text)
116
126
    return b"%s <%s>" % (username, email)
117
127
 
118
128
 
 
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
 
119
149
def warn_escaped(commit, num_escaped):
120
150
    trace.warning("Escaped %d XML-invalid characters in %s. Will be unable "
121
151
                  "to regenerate the SHA map.", num_escaped, commit)
130
160
    """Class that maps between Git and Bazaar semantics."""
131
161
    experimental = False
132
162
 
133
 
    BZR_FILE_IDS_FILE = None
134
 
 
135
 
    BZR_DUMMY_FILE = None
 
163
    BZR_DUMMY_FILE = None  # type: Optional[str]
136
164
 
137
165
    def is_special_file(self, filename):
138
 
        return (filename in (self.BZR_FILE_IDS_FILE, self.BZR_DUMMY_FILE))
 
166
        return (filename in (self.BZR_DUMMY_FILE, ))
139
167
 
140
168
    def __init__(self):
141
169
        super(BzrGitMapping, self).__init__(foreign_vcs_git)
162
190
    def generate_file_id(self, path):
163
191
        # Git paths are just bytestrings
164
192
        # We must just hope they are valid UTF-8..
165
 
        if isinstance(path, text_type):
 
193
        if isinstance(path, str):
166
194
            path = path.encode("utf-8")
167
195
        if path == b"":
168
196
            return ROOT_ID
173
201
            return u""
174
202
        if not file_id.startswith(FILE_ID_PREFIX):
175
203
            raise ValueError
176
 
        return unescape_file_id(file_id[len(FILE_ID_PREFIX):]).decode('utf-8')
177
 
 
178
 
    def revid_as_refname(self, revid):
179
 
        if not isinstance(revid, bytes):
180
 
            raise TypeError(revid)
181
 
        if PY3:
182
 
            revid = revid.decode('utf-8')
183
 
        quoted_revid = urlutils.quote(revid)
184
 
        return b"refs/bzr/" + quoted_revid.encode('utf-8')
 
204
        return decode_git_path(unescape_file_id(file_id[len(FILE_ID_PREFIX):]))
185
205
 
186
206
    def import_unusual_file_modes(self, rev, unusual_file_modes):
187
207
        if unusual_file_modes:
239
259
        (message, renames, branch, extra) = extract_hg_metadata(message)
240
260
        if branch is not None:
241
261
            rev.properties[u'hg:extra:branch'] = branch
242
 
        for name, value in viewitems(extra):
 
262
        for name, value in extra.items():
243
263
            rev.properties[u'hg:extra:' + name] = base64.b64encode(value)
244
264
        if renames:
245
265
            rev.properties[u'hg:renames'] = base64.b64encode(bencode.bencode(
246
 
                [(new, old) for (old, new) in viewitems(renames)]))
 
266
                [(new, old) for (old, new) in renames.items()]))
247
267
        return message
248
268
 
249
269
    def _extract_bzr_metadata(self, rev, message):
256
276
    def _encode_commit_message(self, rev, message, encoding):
257
277
        return message.encode(encoding)
258
278
 
259
 
    def export_fileid_map(self, fileid_map):
260
 
        """Export a file id map to a fileid map.
261
 
 
262
 
        :param fileid_map: File id map, mapping paths to file ids
263
 
        :return: A Git blob object (or None if there are no entries)
264
 
        """
265
 
        from dulwich.objects import Blob
266
 
        b = Blob()
267
 
        b.set_raw_chunks(serialize_fileid_map(fileid_map))
268
 
        return b
269
 
 
270
279
    def export_commit(self, rev, tree_sha, parent_lookup, lossy,
271
280
                      verifiers):
272
281
        """Turn a Bazaar revision in to a Git commit
330
339
            commit.author_timezone = commit.commit_timezone
331
340
        if u'git-gpg-signature' in rev.properties:
332
341
            commit.gpgsig = rev.properties[u'git-gpg-signature'].encode(
333
 
                'ascii')
 
342
                'utf-8', 'surrogateescape')
334
343
        commit.message = self._encode_commit_message(rev, rev.message,
335
344
                                                     encoding)
336
345
        if not isinstance(commit.message, bytes):
345
354
                 u'commit-timezone-neg-utc', u'git-implicit-encoding',
346
355
                 u'git-gpg-signature', u'git-explicit-encoding',
347
356
                 u'author-timestamp', u'file-modes'])
348
 
            for k, v in viewitems(rev.properties):
 
357
            for k, v in rev.properties.items():
349
358
                if k not in mapping_properties:
350
359
                    metadata.properties[k] = v
351
360
        if not lossy and metadata:
369
378
                 for l in rev.properties[u'git-extra'].splitlines()])
370
379
        return commit
371
380
 
372
 
    def import_fileid_map(self, blob):
373
 
        """Convert a git file id map blob.
374
 
 
375
 
        :param blob: Git blob object with fileid map
376
 
        :return: Dictionary mapping paths to file ids
377
 
        """
378
 
        return deserialize_fileid_map(blob.data)
379
 
 
380
 
    def import_commit(self, commit, lookup_parent_revid):
 
381
    def get_revision_id(self, commit):
 
382
        if commit.encoding:
 
383
            encoding = commit.encoding.decode('ascii')
 
384
        else:
 
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
 
394
        return self.revision_id_foreign_to_bzr(commit.id)
 
395
 
 
396
    def import_commit(self, commit, lookup_parent_revid, strict=True):
381
397
        """Convert a git commit to a bzr revision.
382
398
 
383
399
        :return: a `breezy.revision.Revision` object, foreign revid and a
419
435
            rev.properties[u'commit-timezone-neg-utc'] = ""
420
436
        if commit.gpgsig:
421
437
            rev.properties[u'git-gpg-signature'] = commit.gpgsig.decode(
422
 
                'ascii')
 
438
                'utf-8', 'surrogateescape')
423
439
        if commit.mergetag:
424
440
            for i, tag in enumerate(commit.mergetag):
425
441
                rev.properties[u'git-mergetag-%d' % i] = tag.as_raw_string()
451
467
                extra_lines.append(k + b' ' + v + b'\n')
452
468
            elif k == HG_EXTRA:
453
469
                hgk, hgv = v.split(b':', 1)
454
 
                if hgk not in (HG_EXTRA_AMEND_SOURCE, ):
455
 
                    raise UnknownMercurialCommitExtra(commit, hgk)
 
470
                if hgk not in (HG_EXTRA_AMEND_SOURCE, ) and strict:
 
471
                    raise UnknownMercurialCommitExtra(commit, [hgk])
456
472
                extra_lines.append(k + b' ' + v + b'\n')
457
473
            else:
458
474
                unknown_extra_fields.append(k)
459
 
        if unknown_extra_fields:
 
475
        if unknown_extra_fields and strict:
460
476
            raise UnknownCommitExtra(
461
477
                commit,
462
478
                [f.decode('ascii', 'replace') for f in unknown_extra_fields])
464
480
            rev.properties[u'git-extra'] = b''.join(extra_lines)
465
481
        return rev, roundtrip_revid, verifiers
466
482
 
467
 
    def get_fileid_map(self, lookup_object, tree_sha):
468
 
        """Obtain a fileid map for a particular tree.
469
 
 
470
 
        :param lookup_object: Function for looking up an object
471
 
        :param tree_sha: SHA of the root tree
472
 
        :return: GitFileIdMap instance
473
 
        """
474
 
        try:
475
 
            file_id_map_sha = lookup_object(
476
 
                tree_sha)[self.BZR_FILE_IDS_FILE][1]
477
 
        except KeyError:
478
 
            file_ids = {}
479
 
        else:
480
 
            file_ids = self.import_fileid_map(lookup_object(file_id_map_sha))
481
 
        return GitFileIdMap(file_ids, self)
482
 
 
483
483
 
484
484
class BzrGitMappingv1(BzrGitMapping):
485
485
    revid_prefix = b'git-v1'
492
492
class BzrGitMappingExperimental(BzrGitMappingv1):
493
493
    revid_prefix = b'git-experimental'
494
494
    experimental = True
495
 
    roundtripping = True
496
 
 
497
 
    BZR_FILE_IDS_FILE = '.bzrfileids'
 
495
    roundtripping = False
498
496
 
499
497
    BZR_DUMMY_FILE = '.bzrdummy'
500
498
 
501
499
    def _decode_commit_message(self, rev, message, encoding):
 
500
        if rev is None:
 
501
            rev = Revision()
502
502
        message = self._extract_hg_metadata(rev, message)
503
503
        message = self._extract_git_svn_metadata(rev, message)
504
504
        message, metadata = self._extract_bzr_metadata(rev, message)
510
510
        ret += self._generate_git_svn_metadata(rev, encoding)
511
511
        return ret
512
512
 
513
 
    def import_commit(self, commit, lookup_parent_revid):
 
513
    def import_commit(self, commit, lookup_parent_revid, strict=True):
514
514
        rev, roundtrip_revid, verifiers = super(
515
515
            BzrGitMappingExperimental, self).import_commit(
516
 
                commit, lookup_parent_revid)
 
516
                commit, lookup_parent_revid, strict)
517
517
        rev.properties[u'converted_revision'] = "git %s\n" % commit.id
518
518
        return rev, roundtrip_revid, verifiers
519
519
 
581
581
def symlink_to_blob(symlink_target):
582
582
    from dulwich.objects import Blob
583
583
    blob = Blob()
584
 
    if isinstance(symlink_target, text_type):
585
 
        symlink_target = symlink_target.encode('utf-8')
 
584
    if isinstance(symlink_target, str):
 
585
        symlink_target = encode_git_path(symlink_target)
586
586
    blob.data = symlink_target
587
587
    return blob
588
588
 
656
656
    return (full_url, int(rev), uuid)
657
657
 
658
658
 
659
 
class GitFileIdMap(object):
660
 
 
661
 
    def __init__(self, file_ids, mapping):
662
 
        self.file_ids = file_ids
663
 
        self.paths = None
664
 
        self.mapping = mapping
665
 
 
666
 
    def set_file_id(self, path, file_id):
667
 
        if type(path) is not str:
668
 
            raise TypeError(path)
669
 
        if not isinstance(file_id, bytes):
670
 
            raise TypeError(file_id)
671
 
        self.file_ids[path] = file_id
672
 
 
673
 
    def lookup_file_id(self, path):
674
 
        if not isinstance(path, text_type):
675
 
            raise TypeError(path)
676
 
        try:
677
 
            file_id = self.file_ids[path]
678
 
        except KeyError:
679
 
            file_id = self.mapping.generate_file_id(path)
680
 
        if not isinstance(file_id, bytes):
681
 
            raise TypeError(file_id)
682
 
        return file_id
683
 
 
684
 
    def lookup_path(self, file_id):
685
 
        if self.paths is None:
686
 
            self.paths = {}
687
 
            for k, v in viewitems(self.file_ids):
688
 
                self.paths[v] = k
689
 
        try:
690
 
            path = self.paths[file_id]
691
 
        except KeyError:
692
 
            return self.mapping.parse_file_id(file_id)
693
 
        else:
694
 
            if not isinstance(path, text_type):
695
 
                raise TypeError(path)
696
 
            return path
697
 
 
698
 
    def copy(self):
699
 
        return self.__class__(dict(self.file_ids), self.mapping)
700
 
 
701
 
 
702
659
def needs_roundtripping(repo, revid):
703
660
    try:
704
661
        mapping_registry.parse_revision_id(revid)