/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/merge_directive.py

  • Committer: Breezy landing bot
  • Author(s): Colin Watson
  • Date: 2020-11-16 21:47:08 UTC
  • mfrom: (7521.1.1 remove-lp-workaround)
  • Revision ID: breezy.the.bot@gmail.com-20201116214708-jos209mgxi41oy15
Remove breezy.git workaround for bazaar.launchpad.net.

Merged from https://code.launchpad.net/~cjwatson/brz/remove-lp-workaround/+merge/393710

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
 
from StringIO import StringIO
 
17
import base64
 
18
import contextlib
 
19
from io import BytesIO
20
20
import re
21
21
 
22
 
from bzrlib import lazy_import
 
22
from . import lazy_import
23
23
lazy_import.lazy_import(globals(), """
24
 
from bzrlib import (
 
24
from breezy import (
25
25
    branch as _mod_branch,
26
26
    diff,
27
27
    email_message,
28
 
    errors,
29
28
    gpg,
30
29
    hooks,
31
30
    registry,
32
31
    revision as _mod_revision,
33
32
    rio,
34
 
    testament,
35
33
    timestamp,
36
34
    trace,
37
35
    )
38
 
from bzrlib.bundle import (
 
36
from breezy.bzr import (
 
37
    testament,
 
38
    )
 
39
from breezy.bzr.bundle import (
39
40
    serializer as bundle_serializer,
40
41
    )
41
42
""")
 
43
from . import (
 
44
    errors,
 
45
    )
 
46
 
 
47
 
 
48
class IllegalMergeDirectivePayload(errors.BzrError):
 
49
    """A merge directive contained something other than a patch or bundle"""
 
50
 
 
51
    _fmt = "Bad merge directive payload %(start)r"
 
52
 
 
53
    def __init__(self, start):
 
54
        errors.BzrError(self)
 
55
        self.start = start
42
56
 
43
57
 
44
58
class MergeRequestBodyParams(object):
60
74
    """Hooks for MergeDirective classes."""
61
75
 
62
76
    def __init__(self):
63
 
        hooks.Hooks.__init__(self, "bzrlib.merge_directive", "BaseMergeDirective.hooks")
64
 
        self.add_hook('merge_request_body',
 
77
        hooks.Hooks.__init__(self, "breezy.merge_directive",
 
78
                             "BaseMergeDirective.hooks")
 
79
        self.add_hook(
 
80
            'merge_request_body',
65
81
            "Called with a MergeRequestBodyParams when a body is needed for"
66
82
            " a merge request.  Callbacks must return a body.  If more"
67
83
            " than one callback is registered, the output of one callback is"
71
87
class BaseMergeDirective(object):
72
88
    """A request to perform a merge into a branch.
73
89
 
74
 
    This is the base class that all merge directive implementations 
 
90
    This is the base class that all merge directive implementations
75
91
    should derive from.
76
92
 
77
 
    :cvar multiple_output_files: Whether or not this merge directive 
 
93
    :cvar multiple_output_files: Whether or not this merge directive
78
94
        stores a set of revisions in more than one file
79
95
    """
80
96
 
141
157
                stanza.add(key, self.__dict__[key])
142
158
        if base_revision:
143
159
            stanza.add('base_revision_id', self.base_revision_id)
144
 
        lines = ['# ' + self._format_string + '\n']
 
160
        lines = [b'# ' + self._format_string + b'\n']
145
161
        lines.extend(rio.to_patch_lines(stanza))
146
 
        lines.append('# \n')
 
162
        lines.append(b'# \n')
147
163
        return lines
148
164
 
149
165
    def write_to_directory(self, path):
155
171
 
156
172
    @classmethod
157
173
    def from_objects(klass, repository, revision_id, time, timezone,
158
 
                 target_branch, patch_type='bundle',
159
 
                 local_target_branch=None, public_branch=None, message=None):
 
174
                     target_branch, patch_type='bundle',
 
175
                     local_target_branch=None, public_branch=None, message=None):
160
176
        """Generate a merge directive from various objects
161
177
 
162
178
        :param repository: The repository containing the revision
199
215
                                                submit_revision_id)
200
216
            type_handler = {'bundle': klass._generate_bundle,
201
217
                            'diff': klass._generate_diff,
202
 
                            None: lambda x, y, z: None }
 
218
                            None: lambda x, y, z: None}
203
219
            patch = type_handler[patch_type](repository, revision_id,
204
220
                                             ancestor_id)
205
221
 
210
226
                                                   revision_id)
211
227
 
212
228
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
213
 
            patch, patch_type, public_branch, message)
 
229
                     patch, patch_type, public_branch, message)
214
230
 
215
231
    def get_disk_name(self, branch):
216
232
        """Generate a suitable basename for storing this directive on disk
222
238
        if self.revision_id == revision_id:
223
239
            revno = [revno]
224
240
        else:
225
 
            revno = branch.get_revision_id_to_revno_map().get(self.revision_id,
226
 
                ['merge'])
227
 
        nick = re.sub('(\W+)', '-', branch.nick).strip('-')
 
241
            try:
 
242
                revno = branch.revision_id_to_dotted_revno(self.revision_id)
 
243
            except errors.NoSuchRevision:
 
244
                revno = ['merge']
 
245
        nick = re.sub('(\\W+)', '-', branch.nick).strip('-')
228
246
        return '%s-%s' % (nick, '.'.join(str(n) for n in revno))
229
247
 
230
248
    @staticmethod
231
249
    def _generate_diff(repository, revision_id, ancestor_id):
232
250
        tree_1 = repository.revision_tree(ancestor_id)
233
251
        tree_2 = repository.revision_tree(revision_id)
234
 
        s = StringIO()
 
252
        s = BytesIO()
235
253
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
236
254
        return s.getvalue()
237
255
 
238
256
    @staticmethod
239
257
    def _generate_bundle(repository, revision_id, ancestor_id):
240
 
        s = StringIO()
 
258
        s = BytesIO()
241
259
        bundle_serializer.write_bundle(repository, revision_id,
242
260
                                       ancestor_id, s)
243
261
        return s.getvalue()
249
267
        :return: a string
250
268
        """
251
269
        my_gpg = gpg.GPGStrategy(branch.get_config_stack())
252
 
        return my_gpg.sign(''.join(self.to_lines()))
 
270
        return my_gpg.sign(b''.join(self.to_lines()), gpg.MODE_CLEAR)
253
271
 
254
272
    def to_email(self, mail_to, branch, sign=False):
255
273
        """Serialize as an email message.
269
287
        if sign:
270
288
            body = self.to_signed(branch)
271
289
        else:
272
 
            body = ''.join(self.to_lines())
 
290
            body = b''.join(self.to_lines())
273
291
        message = email_message.EmailMessage(mail_from, mail_to, subject,
274
 
            body)
 
292
                                             body)
275
293
        return message
276
294
 
277
295
    def install_revisions(self, target_repo):
279
297
        if not target_repo.has_revision(self.revision_id):
280
298
            if self.patch_type == 'bundle':
281
299
                info = bundle_serializer.read_bundle(
282
 
                    StringIO(self.get_raw_bundle()))
 
300
                    BytesIO(self.get_raw_bundle()))
283
301
                # We don't use the bundle's target revision, because
284
302
                # MergeDirective.revision_id is authoritative.
285
303
                try:
297
315
                                           info.real_revisions)
298
316
                    for revision in info.real_revisions:
299
317
                        for parent_id in revision.parent_ids:
300
 
                            if (parent_id not in bundle_revisions and
301
 
                                not target_repo.has_revision(parent_id)):
 
318
                            if (parent_id not in bundle_revisions
 
319
                                    and not target_repo.has_revision(parent_id)):
302
320
                                missing_revisions.append(parent_id)
303
321
                    # reverse missing revisions to try to get heads first
304
322
                    unique_missing = []
343
361
        elif len(self.hooks['merge_request_body']) > 0:
344
362
            trace.warning('Cannot run merge_request_body hooks because mail'
345
363
                          ' client %s does not support message bodies.',
346
 
                        mail_client.__class__.__name__)
 
364
                          mail_client.__class__.__name__)
347
365
        mail_client.compose_merge_request(to, subject,
348
 
                                          ''.join(self.to_lines()),
 
366
                                          b''.join(self.to_lines()),
349
367
                                          basename, body)
350
368
 
351
369
 
365
383
    directly using the standard patch program.
366
384
    """
367
385
 
368
 
    _format_string = 'Bazaar merge directive format 1'
 
386
    _format_string = b'Bazaar merge directive format 1'
369
387
 
370
388
    def __init__(self, revision_id, testament_sha1, time, timezone,
371
389
                 target_branch, patch=None, patch_type=None,
385
403
        :param message: The message to use when committing this merge
386
404
        """
387
405
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
388
 
            timezone, target_branch, patch, source_branch, message)
 
406
                                    timezone, target_branch, patch, source_branch, message)
389
407
        if patch_type not in (None, 'diff', 'bundle'):
390
408
            raise ValueError(patch_type)
391
409
        if patch_type != 'bundle' and source_branch is None:
417
435
        :return: a MergeRequest
418
436
        """
419
437
        line_iter = iter(lines)
420
 
        firstline = ""
 
438
        firstline = b""
421
439
        for line in line_iter:
422
 
            if line.startswith('# Bazaar merge directive format '):
 
440
            if line.startswith(b'# Bazaar merge directive format '):
423
441
                return _format_registry.get(line[2:].rstrip())._from_lines(
424
442
                    line_iter)
425
443
            firstline = firstline or line.strip()
433
451
            patch = None
434
452
            patch_type = None
435
453
        else:
436
 
            patch = ''.join(patch_lines)
 
454
            patch = b''.join(patch_lines)
437
455
            try:
438
 
                bundle_serializer.read_bundle(StringIO(patch))
 
456
                bundle_serializer.read_bundle(BytesIO(patch))
439
457
            except (errors.NotABundle, errors.BundleNotSupported,
440
458
                    errors.BadBundle):
441
459
                patch_type = 'diff'
450
468
            except KeyError:
451
469
                pass
452
470
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
 
471
        if 'testament_sha1' in kwargs:
 
472
            kwargs['testament_sha1'] = kwargs['testament_sha1'].encode('ascii')
453
473
        return MergeDirective(time=time, timezone=timezone,
454
474
                              patch_type=patch_type, patch=patch, **kwargs)
455
475
 
461
481
 
462
482
    @staticmethod
463
483
    def _generate_bundle(repository, revision_id, ancestor_id):
464
 
        s = StringIO()
 
484
        s = BytesIO()
465
485
        bundle_serializer.write_bundle(repository, revision_id,
466
486
                                       ancestor_id, s, '0.9')
467
487
        return s.getvalue()
476
496
 
477
497
class MergeDirective2(BaseMergeDirective):
478
498
 
479
 
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
 
499
    _format_string = b'Bazaar merge directive format 2 (Bazaar 0.90)'
480
500
 
481
501
    def __init__(self, revision_id, testament_sha1, time, timezone,
482
502
                 target_branch, patch=None, source_branch=None, message=None,
484
504
        if source_branch is None and bundle is None:
485
505
            raise errors.NoMergeSource()
486
506
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
487
 
            timezone, target_branch, patch, source_branch, message)
 
507
                                    timezone, target_branch, patch, source_branch, message)
488
508
        self.bundle = bundle
489
509
        self.base_revision_id = base_revision_id
490
510
 
506
526
        if self.bundle is None:
507
527
            return None
508
528
        else:
509
 
            return self.bundle.decode('base-64')
 
529
            return base64.b64decode(self.bundle)
510
530
 
511
531
    @classmethod
512
532
    def _from_lines(klass, line_iter):
514
534
        patch = None
515
535
        bundle = None
516
536
        try:
517
 
            start = line_iter.next()
 
537
            start = next(line_iter)
518
538
        except StopIteration:
519
539
            pass
520
540
        else:
521
 
            if start.startswith('# Begin patch'):
 
541
            if start.startswith(b'# Begin patch'):
522
542
                patch_lines = []
523
543
                for line in line_iter:
524
 
                    if line.startswith('# Begin bundle'):
 
544
                    if line.startswith(b'# Begin bundle'):
525
545
                        start = line
526
546
                        break
527
547
                    patch_lines.append(line)
528
548
                else:
529
549
                    start = None
530
 
                patch = ''.join(patch_lines)
 
550
                patch = b''.join(patch_lines)
531
551
            if start is not None:
532
 
                if start.startswith('# Begin bundle'):
533
 
                    bundle = ''.join(line_iter)
 
552
                if start.startswith(b'# Begin bundle'):
 
553
                    bundle = b''.join(line_iter)
534
554
                else:
535
 
                    raise errors.IllegalMergeDirectivePayload(start)
 
555
                    raise IllegalMergeDirectivePayload(start)
536
556
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
537
557
        kwargs = {}
538
558
        for key in ('revision_id', 'testament_sha1', 'target_branch',
544
564
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
545
565
        kwargs['base_revision_id'] =\
546
566
            kwargs['base_revision_id'].encode('utf-8')
 
567
        if 'testament_sha1' in kwargs:
 
568
            kwargs['testament_sha1'] = kwargs['testament_sha1'].encode('ascii')
547
569
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
548
570
                     **kwargs)
549
571
 
550
572
    def to_lines(self):
551
573
        lines = self._to_lines(base_revision=True)
552
574
        if self.patch is not None:
553
 
            lines.append('# Begin patch\n')
 
575
            lines.append(b'# Begin patch\n')
554
576
            lines.extend(self.patch.splitlines(True))
555
577
        if self.bundle is not None:
556
 
            lines.append('# Begin bundle\n')
 
578
            lines.append(b'# Begin bundle\n')
557
579
            lines.extend(self.bundle.splitlines(True))
558
580
        return lines
559
581
 
560
582
    @classmethod
561
583
    def from_objects(klass, repository, revision_id, time, timezone,
562
 
                 target_branch, include_patch=True, include_bundle=True,
563
 
                 local_target_branch=None, public_branch=None, message=None,
564
 
                 base_revision_id=None):
 
584
                     target_branch, include_patch=True, include_bundle=True,
 
585
                     local_target_branch=None, public_branch=None, message=None,
 
586
                     base_revision_id=None):
565
587
        """Generate a merge directive from various objects
566
588
 
567
589
        :param repository: The repository containing the revision
583
605
        If the message is not supplied, the message from revision_id will be
584
606
        used for the commit.
585
607
        """
586
 
        locked = []
587
 
        try:
588
 
            repository.lock_write()
589
 
            locked.append(repository)
 
608
        with contextlib.ExitStack() as exit_stack:
 
609
            exit_stack.enter_context(repository.lock_write())
590
610
            t_revision_id = revision_id
591
 
            if revision_id == 'null:':
 
611
            if revision_id == b'null:':
592
612
                t_revision_id = None
593
613
            t = testament.StrictTestament3.from_revision(repository,
594
 
                t_revision_id)
 
614
                                                         t_revision_id)
595
615
            if local_target_branch is None:
596
616
                submit_branch = _mod_branch.Branch.open(target_branch)
597
617
            else:
598
618
                submit_branch = local_target_branch
599
 
            submit_branch.lock_read()
600
 
            locked.append(submit_branch)
 
619
            exit_stack.enter_context(submit_branch.lock_read())
601
620
            if submit_branch.get_public_branch() is not None:
602
621
                target_branch = submit_branch.get_public_branch()
603
622
            submit_revision_id = submit_branch.last_revision()
616
635
                patch = None
617
636
 
618
637
            if include_bundle:
619
 
                bundle = klass._generate_bundle(repository, revision_id,
620
 
                    ancestor_id).encode('base-64')
 
638
                bundle = base64.b64encode(klass._generate_bundle(repository, revision_id,
 
639
                                                                 ancestor_id))
621
640
            else:
622
641
                bundle = None
623
642
 
624
643
            if public_branch is not None and not include_bundle:
625
644
                public_branch_obj = _mod_branch.Branch.open(public_branch)
626
 
                public_branch_obj.lock_read()
627
 
                locked.append(public_branch_obj)
 
645
                exit_stack.enter_context(public_branch_obj.lock_read())
628
646
                if not public_branch_obj.repository.has_revision(
629
 
                    revision_id):
 
647
                        revision_id):
630
648
                    raise errors.PublicBranchOutOfDate(public_branch,
631
649
                                                       revision_id)
632
650
            testament_sha1 = t.as_sha1()
633
 
        finally:
634
 
            for entry in reversed(locked):
635
 
                entry.unlock()
636
651
        return klass(revision_id, testament_sha1, time, timezone,
637
 
            target_branch, patch, public_branch, message, bundle,
638
 
            base_revision_id)
 
652
                     target_branch, patch, public_branch, message, bundle,
 
653
                     base_revision_id)
639
654
 
640
655
    def _verify_patch(self, repository):
641
656
        calculated_patch = self._generate_diff(repository, self.revision_id,
642
657
                                               self.base_revision_id)
643
658
        # Convert line-endings to UNIX
644
 
        stored_patch = re.sub('\r\n?', '\n', self.patch)
645
 
        calculated_patch = re.sub('\r\n?', '\n', calculated_patch)
 
659
        stored_patch = re.sub(b'\r\n?', b'\n', self.patch)
 
660
        calculated_patch = re.sub(b'\r\n?', b'\n', calculated_patch)
646
661
        # Strip trailing whitespace
647
 
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
648
 
        stored_patch = re.sub(' *\n', '\n', stored_patch)
 
662
        calculated_patch = re.sub(b' *\n', b'\n', calculated_patch)
 
663
        stored_patch = re.sub(b' *\n', b'\n', stored_patch)
649
664
        return (calculated_patch == stored_patch)
650
665
 
651
666
    def get_merge_request(self, repository):
681
696
# already merge directives in the wild that used 0.19. Registering with the old
682
697
# format string to retain compatibility with those merge directives.
683
698
_format_registry.register(MergeDirective2,
684
 
                          'Bazaar merge directive format 2 (Bazaar 0.19)')
 
699
                          b'Bazaar merge directive format 2 (Bazaar 0.19)')