/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: Jelmer Vernooij
  • Date: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

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
 
 
 
17
import base64
 
18
import contextlib
 
19
from io import BytesIO
19
20
import re
20
21
 
21
22
from . import lazy_import
24
25
    branch as _mod_branch,
25
26
    diff,
26
27
    email_message,
27
 
    errors,
28
28
    gpg,
29
29
    hooks,
30
30
    registry,
31
31
    revision as _mod_revision,
32
32
    rio,
33
 
    testament,
34
33
    timestamp,
35
34
    trace,
36
35
    )
37
 
from breezy.bundle import (
 
36
from breezy.bzr import (
 
37
    testament,
 
38
    )
 
39
from breezy.bzr.bundle import (
38
40
    serializer as bundle_serializer,
39
41
    )
40
42
""")
41
 
from .sixish import (
42
 
    BytesIO,
 
43
from . import (
 
44
    errors,
43
45
    )
44
46
 
45
47
 
62
64
    """Hooks for MergeDirective classes."""
63
65
 
64
66
    def __init__(self):
65
 
        hooks.Hooks.__init__(self, "breezy.merge_directive", "BaseMergeDirective.hooks")
66
 
        self.add_hook('merge_request_body',
 
67
        hooks.Hooks.__init__(self, "breezy.merge_directive",
 
68
                             "BaseMergeDirective.hooks")
 
69
        self.add_hook(
 
70
            'merge_request_body',
67
71
            "Called with a MergeRequestBodyParams when a body is needed for"
68
72
            " a merge request.  Callbacks must return a body.  If more"
69
73
            " than one callback is registered, the output of one callback is"
73
77
class BaseMergeDirective(object):
74
78
    """A request to perform a merge into a branch.
75
79
 
76
 
    This is the base class that all merge directive implementations 
 
80
    This is the base class that all merge directive implementations
77
81
    should derive from.
78
82
 
79
 
    :cvar multiple_output_files: Whether or not this merge directive 
 
83
    :cvar multiple_output_files: Whether or not this merge directive
80
84
        stores a set of revisions in more than one file
81
85
    """
82
86
 
143
147
                stanza.add(key, self.__dict__[key])
144
148
        if base_revision:
145
149
            stanza.add('base_revision_id', self.base_revision_id)
146
 
        lines = ['# ' + self._format_string + '\n']
 
150
        lines = [b'# ' + self._format_string + b'\n']
147
151
        lines.extend(rio.to_patch_lines(stanza))
148
 
        lines.append('# \n')
 
152
        lines.append(b'# \n')
149
153
        return lines
150
154
 
151
155
    def write_to_directory(self, path):
157
161
 
158
162
    @classmethod
159
163
    def from_objects(klass, repository, revision_id, time, timezone,
160
 
                 target_branch, patch_type='bundle',
161
 
                 local_target_branch=None, public_branch=None, message=None):
 
164
                     target_branch, patch_type='bundle',
 
165
                     local_target_branch=None, public_branch=None, message=None):
162
166
        """Generate a merge directive from various objects
163
167
 
164
168
        :param repository: The repository containing the revision
201
205
                                                submit_revision_id)
202
206
            type_handler = {'bundle': klass._generate_bundle,
203
207
                            'diff': klass._generate_diff,
204
 
                            None: lambda x, y, z: None }
 
208
                            None: lambda x, y, z: None}
205
209
            patch = type_handler[patch_type](repository, revision_id,
206
210
                                             ancestor_id)
207
211
 
212
216
                                                   revision_id)
213
217
 
214
218
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
215
 
            patch, patch_type, public_branch, message)
 
219
                     patch, patch_type, public_branch, message)
216
220
 
217
221
    def get_disk_name(self, branch):
218
222
        """Generate a suitable basename for storing this directive on disk
224
228
        if self.revision_id == revision_id:
225
229
            revno = [revno]
226
230
        else:
227
 
            revno = branch.get_revision_id_to_revno_map().get(self.revision_id,
228
 
                ['merge'])
 
231
            try:
 
232
                revno = branch.revision_id_to_dotted_revno(self.revision_id)
 
233
            except errors.NoSuchRevision:
 
234
                revno = ['merge']
229
235
        nick = re.sub('(\\W+)', '-', branch.nick).strip('-')
230
236
        return '%s-%s' % (nick, '.'.join(str(n) for n in revno))
231
237
 
251
257
        :return: a string
252
258
        """
253
259
        my_gpg = gpg.GPGStrategy(branch.get_config_stack())
254
 
        return my_gpg.sign(''.join(self.to_lines()))
 
260
        return my_gpg.sign(b''.join(self.to_lines()), gpg.MODE_CLEAR)
255
261
 
256
262
    def to_email(self, mail_to, branch, sign=False):
257
263
        """Serialize as an email message.
271
277
        if sign:
272
278
            body = self.to_signed(branch)
273
279
        else:
274
 
            body = ''.join(self.to_lines())
 
280
            body = b''.join(self.to_lines())
275
281
        message = email_message.EmailMessage(mail_from, mail_to, subject,
276
 
            body)
 
282
                                             body)
277
283
        return message
278
284
 
279
285
    def install_revisions(self, target_repo):
299
305
                                           info.real_revisions)
300
306
                    for revision in info.real_revisions:
301
307
                        for parent_id in revision.parent_ids:
302
 
                            if (parent_id not in bundle_revisions and
303
 
                                not target_repo.has_revision(parent_id)):
 
308
                            if (parent_id not in bundle_revisions
 
309
                                    and not target_repo.has_revision(parent_id)):
304
310
                                missing_revisions.append(parent_id)
305
311
                    # reverse missing revisions to try to get heads first
306
312
                    unique_missing = []
345
351
        elif len(self.hooks['merge_request_body']) > 0:
346
352
            trace.warning('Cannot run merge_request_body hooks because mail'
347
353
                          ' client %s does not support message bodies.',
348
 
                        mail_client.__class__.__name__)
 
354
                          mail_client.__class__.__name__)
349
355
        mail_client.compose_merge_request(to, subject,
350
 
                                          ''.join(self.to_lines()),
 
356
                                          b''.join(self.to_lines()),
351
357
                                          basename, body)
352
358
 
353
359
 
367
373
    directly using the standard patch program.
368
374
    """
369
375
 
370
 
    _format_string = 'Bazaar merge directive format 1'
 
376
    _format_string = b'Bazaar merge directive format 1'
371
377
 
372
378
    def __init__(self, revision_id, testament_sha1, time, timezone,
373
379
                 target_branch, patch=None, patch_type=None,
387
393
        :param message: The message to use when committing this merge
388
394
        """
389
395
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
390
 
            timezone, target_branch, patch, source_branch, message)
 
396
                                    timezone, target_branch, patch, source_branch, message)
391
397
        if patch_type not in (None, 'diff', 'bundle'):
392
398
            raise ValueError(patch_type)
393
399
        if patch_type != 'bundle' and source_branch is None:
419
425
        :return: a MergeRequest
420
426
        """
421
427
        line_iter = iter(lines)
422
 
        firstline = ""
 
428
        firstline = b""
423
429
        for line in line_iter:
424
 
            if line.startswith('# Bazaar merge directive format '):
 
430
            if line.startswith(b'# Bazaar merge directive format '):
425
431
                return _format_registry.get(line[2:].rstrip())._from_lines(
426
432
                    line_iter)
427
433
            firstline = firstline or line.strip()
435
441
            patch = None
436
442
            patch_type = None
437
443
        else:
438
 
            patch = ''.join(patch_lines)
 
444
            patch = b''.join(patch_lines)
439
445
            try:
440
446
                bundle_serializer.read_bundle(BytesIO(patch))
441
447
            except (errors.NotABundle, errors.BundleNotSupported,
452
458
            except KeyError:
453
459
                pass
454
460
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
 
461
        if 'testament_sha1' in kwargs:
 
462
            kwargs['testament_sha1'] = kwargs['testament_sha1'].encode('ascii')
455
463
        return MergeDirective(time=time, timezone=timezone,
456
464
                              patch_type=patch_type, patch=patch, **kwargs)
457
465
 
478
486
 
479
487
class MergeDirective2(BaseMergeDirective):
480
488
 
481
 
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
 
489
    _format_string = b'Bazaar merge directive format 2 (Bazaar 0.90)'
482
490
 
483
491
    def __init__(self, revision_id, testament_sha1, time, timezone,
484
492
                 target_branch, patch=None, source_branch=None, message=None,
486
494
        if source_branch is None and bundle is None:
487
495
            raise errors.NoMergeSource()
488
496
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
489
 
            timezone, target_branch, patch, source_branch, message)
 
497
                                    timezone, target_branch, patch, source_branch, message)
490
498
        self.bundle = bundle
491
499
        self.base_revision_id = base_revision_id
492
500
 
508
516
        if self.bundle is None:
509
517
            return None
510
518
        else:
511
 
            return self.bundle.decode('base-64')
 
519
            return base64.b64decode(self.bundle)
512
520
 
513
521
    @classmethod
514
522
    def _from_lines(klass, line_iter):
520
528
        except StopIteration:
521
529
            pass
522
530
        else:
523
 
            if start.startswith('# Begin patch'):
 
531
            if start.startswith(b'# Begin patch'):
524
532
                patch_lines = []
525
533
                for line in line_iter:
526
 
                    if line.startswith('# Begin bundle'):
 
534
                    if line.startswith(b'# Begin bundle'):
527
535
                        start = line
528
536
                        break
529
537
                    patch_lines.append(line)
530
538
                else:
531
539
                    start = None
532
 
                patch = ''.join(patch_lines)
 
540
                patch = b''.join(patch_lines)
533
541
            if start is not None:
534
 
                if start.startswith('# Begin bundle'):
535
 
                    bundle = ''.join(line_iter)
 
542
                if start.startswith(b'# Begin bundle'):
 
543
                    bundle = b''.join(line_iter)
536
544
                else:
537
545
                    raise errors.IllegalMergeDirectivePayload(start)
538
546
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
546
554
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
547
555
        kwargs['base_revision_id'] =\
548
556
            kwargs['base_revision_id'].encode('utf-8')
 
557
        if 'testament_sha1' in kwargs:
 
558
            kwargs['testament_sha1'] = kwargs['testament_sha1'].encode('ascii')
549
559
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
550
560
                     **kwargs)
551
561
 
552
562
    def to_lines(self):
553
563
        lines = self._to_lines(base_revision=True)
554
564
        if self.patch is not None:
555
 
            lines.append('# Begin patch\n')
 
565
            lines.append(b'# Begin patch\n')
556
566
            lines.extend(self.patch.splitlines(True))
557
567
        if self.bundle is not None:
558
 
            lines.append('# Begin bundle\n')
 
568
            lines.append(b'# Begin bundle\n')
559
569
            lines.extend(self.bundle.splitlines(True))
560
570
        return lines
561
571
 
562
572
    @classmethod
563
573
    def from_objects(klass, repository, revision_id, time, timezone,
564
 
                 target_branch, include_patch=True, include_bundle=True,
565
 
                 local_target_branch=None, public_branch=None, message=None,
566
 
                 base_revision_id=None):
 
574
                     target_branch, include_patch=True, include_bundle=True,
 
575
                     local_target_branch=None, public_branch=None, message=None,
 
576
                     base_revision_id=None):
567
577
        """Generate a merge directive from various objects
568
578
 
569
579
        :param repository: The repository containing the revision
585
595
        If the message is not supplied, the message from revision_id will be
586
596
        used for the commit.
587
597
        """
588
 
        locked = []
589
 
        try:
590
 
            repository.lock_write()
591
 
            locked.append(repository)
 
598
        with contextlib.ExitStack() as exit_stack:
 
599
            exit_stack.enter_context(repository.lock_write())
592
600
            t_revision_id = revision_id
593
 
            if revision_id == 'null:':
 
601
            if revision_id == b'null:':
594
602
                t_revision_id = None
595
603
            t = testament.StrictTestament3.from_revision(repository,
596
 
                t_revision_id)
 
604
                                                         t_revision_id)
597
605
            if local_target_branch is None:
598
606
                submit_branch = _mod_branch.Branch.open(target_branch)
599
607
            else:
600
608
                submit_branch = local_target_branch
601
 
            submit_branch.lock_read()
602
 
            locked.append(submit_branch)
 
609
            exit_stack.enter_context(submit_branch.lock_read())
603
610
            if submit_branch.get_public_branch() is not None:
604
611
                target_branch = submit_branch.get_public_branch()
605
612
            submit_revision_id = submit_branch.last_revision()
618
625
                patch = None
619
626
 
620
627
            if include_bundle:
621
 
                bundle = klass._generate_bundle(repository, revision_id,
622
 
                    ancestor_id).encode('base-64')
 
628
                bundle = base64.b64encode(klass._generate_bundle(repository, revision_id,
 
629
                                                                 ancestor_id))
623
630
            else:
624
631
                bundle = None
625
632
 
626
633
            if public_branch is not None and not include_bundle:
627
634
                public_branch_obj = _mod_branch.Branch.open(public_branch)
628
 
                public_branch_obj.lock_read()
629
 
                locked.append(public_branch_obj)
 
635
                exit_stack.enter_context(public_branch_obj.lock_read())
630
636
                if not public_branch_obj.repository.has_revision(
631
 
                    revision_id):
 
637
                        revision_id):
632
638
                    raise errors.PublicBranchOutOfDate(public_branch,
633
639
                                                       revision_id)
634
640
            testament_sha1 = t.as_sha1()
635
 
        finally:
636
 
            for entry in reversed(locked):
637
 
                entry.unlock()
638
641
        return klass(revision_id, testament_sha1, time, timezone,
639
 
            target_branch, patch, public_branch, message, bundle,
640
 
            base_revision_id)
 
642
                     target_branch, patch, public_branch, message, bundle,
 
643
                     base_revision_id)
641
644
 
642
645
    def _verify_patch(self, repository):
643
646
        calculated_patch = self._generate_diff(repository, self.revision_id,
644
647
                                               self.base_revision_id)
645
648
        # Convert line-endings to UNIX
646
 
        stored_patch = re.sub('\r\n?', '\n', self.patch)
647
 
        calculated_patch = re.sub('\r\n?', '\n', calculated_patch)
 
649
        stored_patch = re.sub(b'\r\n?', b'\n', self.patch)
 
650
        calculated_patch = re.sub(b'\r\n?', b'\n', calculated_patch)
648
651
        # Strip trailing whitespace
649
 
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
650
 
        stored_patch = re.sub(' *\n', '\n', stored_patch)
 
652
        calculated_patch = re.sub(b' *\n', b'\n', calculated_patch)
 
653
        stored_patch = re.sub(b' *\n', b'\n', stored_patch)
651
654
        return (calculated_patch == stored_patch)
652
655
 
653
656
    def get_merge_request(self, repository):
683
686
# already merge directives in the wild that used 0.19. Registering with the old
684
687
# format string to retain compatibility with those merge directives.
685
688
_format_registry.register(MergeDirective2,
686
 
                          'Bazaar merge directive format 2 (Bazaar 0.19)')
 
689
                          b'Bazaar merge directive format 2 (Bazaar 0.19)')