/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-02-18 01:57:45 UTC
  • mto: This revision was merged to the branch mainline in revision 7493.
  • Revision ID: jelmer@jelmer.uk-20200218015745-q2ss9tsk74h4nh61
drop use of future.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
 
 
18
 
from StringIO import StringIO
 
17
import base64
 
18
import contextlib
 
19
from io import BytesIO
19
20
import re
20
21
 
21
 
from bzrlib import (
 
22
from . import lazy_import
 
23
lazy_import.lazy_import(globals(), """
 
24
from breezy import (
22
25
    branch as _mod_branch,
23
26
    diff,
24
 
    errors,
 
27
    email_message,
25
28
    gpg,
26
29
    hooks,
27
30
    registry,
28
31
    revision as _mod_revision,
29
32
    rio,
30
 
    testament,
31
33
    timestamp,
32
34
    trace,
33
35
    )
34
 
from bzrlib.bundle import (
 
36
from breezy.bzr import (
 
37
    testament,
 
38
    )
 
39
from breezy.bzr.bundle import (
35
40
    serializer as bundle_serializer,
36
41
    )
37
 
from bzrlib.email_message import EmailMessage
 
42
""")
 
43
from . import (
 
44
    errors,
 
45
    )
38
46
 
39
47
 
40
48
class MergeRequestBodyParams(object):
56
64
    """Hooks for MergeDirective classes."""
57
65
 
58
66
    def __init__(self):
59
 
        hooks.Hooks.__init__(self)
60
 
        self.create_hook(hooks.HookPoint('merge_request_body',
 
67
        hooks.Hooks.__init__(self, "breezy.merge_directive",
 
68
                             "BaseMergeDirective.hooks")
 
69
        self.add_hook(
 
70
            'merge_request_body',
61
71
            "Called with a MergeRequestBodyParams when a body is needed for"
62
72
            " a merge request.  Callbacks must return a body.  If more"
63
73
            " than one callback is registered, the output of one callback is"
64
 
            " provided to the next.", (1, 15, 0), False))
 
74
            " provided to the next.", (1, 15, 0))
65
75
 
66
76
 
67
77
class BaseMergeDirective(object):
68
78
    """A request to perform a merge into a branch.
69
79
 
70
 
    This is the base class that all merge directive implementations 
 
80
    This is the base class that all merge directive implementations
71
81
    should derive from.
72
82
 
73
 
    :cvar multiple_output_files: Whether or not this merge directive 
 
83
    :cvar multiple_output_files: Whether or not this merge directive
74
84
        stores a set of revisions in more than one file
75
85
    """
76
86
 
79
89
    multiple_output_files = False
80
90
 
81
91
    def __init__(self, revision_id, testament_sha1, time, timezone,
82
 
                 target_branch, patch=None, source_branch=None, message=None,
83
 
                 bundle=None):
 
92
                 target_branch, patch=None, source_branch=None,
 
93
                 message=None, bundle=None):
84
94
        """Constructor.
85
95
 
86
96
        :param revision_id: The revision to merge
88
98
            merge.
89
99
        :param time: The current POSIX timestamp time
90
100
        :param timezone: The timezone offset
91
 
        :param target_branch: The branch to apply the merge to
 
101
        :param target_branch: Location of branch to apply the merge to
92
102
        :param patch: The text of a diff or bundle
93
103
        :param source_branch: A public location to merge the revision from
94
104
        :param message: The message to use when committing this merge
137
147
                stanza.add(key, self.__dict__[key])
138
148
        if base_revision:
139
149
            stanza.add('base_revision_id', self.base_revision_id)
140
 
        lines = ['# ' + self._format_string + '\n']
 
150
        lines = [b'# ' + self._format_string + b'\n']
141
151
        lines.extend(rio.to_patch_lines(stanza))
142
 
        lines.append('# \n')
 
152
        lines.append(b'# \n')
143
153
        return lines
144
154
 
145
155
    def write_to_directory(self, path):
151
161
 
152
162
    @classmethod
153
163
    def from_objects(klass, repository, revision_id, time, timezone,
154
 
                 target_branch, patch_type='bundle',
155
 
                 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):
156
166
        """Generate a merge directive from various objects
157
167
 
158
168
        :param repository: The repository containing the revision
162
172
        :param target_branch: The url of the branch to merge into
163
173
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
164
174
            patch desired.
165
 
        :param local_target_branch: a local copy of the target branch
166
 
        :param public_branch: location of a public branch containing the target
167
 
            revision.
 
175
        :param local_target_branch: the submit branch, either itself or a local copy
 
176
        :param public_branch: location of a public branch containing
 
177
            the target revision.
168
178
        :param message: Message to use when committing the merge
169
179
        :return: The merge directive
170
180
 
178
188
        if revision_id == _mod_revision.NULL_REVISION:
179
189
            t_revision_id = None
180
190
        t = testament.StrictTestament3.from_revision(repository, t_revision_id)
181
 
        submit_branch = _mod_branch.Branch.open(target_branch)
 
191
        if local_target_branch is None:
 
192
            submit_branch = _mod_branch.Branch.open(target_branch)
 
193
        else:
 
194
            submit_branch = local_target_branch
182
195
        if submit_branch.get_public_branch() is not None:
183
196
            target_branch = submit_branch.get_public_branch()
184
197
        if patch_type is None:
192
205
                                                submit_revision_id)
193
206
            type_handler = {'bundle': klass._generate_bundle,
194
207
                            'diff': klass._generate_diff,
195
 
                            None: lambda x, y, z: None }
 
208
                            None: lambda x, y, z: None}
196
209
            patch = type_handler[patch_type](repository, revision_id,
197
210
                                             ancestor_id)
198
211
 
203
216
                                                   revision_id)
204
217
 
205
218
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
206
 
            patch, patch_type, public_branch, message)
 
219
                     patch, patch_type, public_branch, message)
207
220
 
208
221
    def get_disk_name(self, branch):
209
222
        """Generate a suitable basename for storing this directive on disk
215
228
        if self.revision_id == revision_id:
216
229
            revno = [revno]
217
230
        else:
218
 
            revno = branch.get_revision_id_to_revno_map().get(self.revision_id,
219
 
                ['merge'])
220
 
        nick = re.sub('(\W+)', '-', branch.nick).strip('-')
 
231
            try:
 
232
                revno = branch.revision_id_to_dotted_revno(self.revision_id)
 
233
            except errors.NoSuchRevision:
 
234
                revno = ['merge']
 
235
        nick = re.sub('(\\W+)', '-', branch.nick).strip('-')
221
236
        return '%s-%s' % (nick, '.'.join(str(n) for n in revno))
222
237
 
223
238
    @staticmethod
224
239
    def _generate_diff(repository, revision_id, ancestor_id):
225
240
        tree_1 = repository.revision_tree(ancestor_id)
226
241
        tree_2 = repository.revision_tree(revision_id)
227
 
        s = StringIO()
 
242
        s = BytesIO()
228
243
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
229
244
        return s.getvalue()
230
245
 
231
246
    @staticmethod
232
247
    def _generate_bundle(repository, revision_id, ancestor_id):
233
 
        s = StringIO()
 
248
        s = BytesIO()
234
249
        bundle_serializer.write_bundle(repository, revision_id,
235
250
                                       ancestor_id, s)
236
251
        return s.getvalue()
241
256
        :param branch: The source branch, to get the signing strategy
242
257
        :return: a string
243
258
        """
244
 
        my_gpg = gpg.GPGStrategy(branch.get_config())
245
 
        return my_gpg.sign(''.join(self.to_lines()))
 
259
        my_gpg = gpg.GPGStrategy(branch.get_config_stack())
 
260
        return my_gpg.sign(b''.join(self.to_lines()), gpg.MODE_CLEAR)
246
261
 
247
262
    def to_email(self, mail_to, branch, sign=False):
248
263
        """Serialize as an email message.
253
268
        :param sign: If True, gpg-sign the email
254
269
        :return: an email message
255
270
        """
256
 
        mail_from = branch.get_config().username()
 
271
        mail_from = branch.get_config_stack().get('email')
257
272
        if self.message is not None:
258
273
            subject = self.message
259
274
        else:
262
277
        if sign:
263
278
            body = self.to_signed(branch)
264
279
        else:
265
 
            body = ''.join(self.to_lines())
266
 
        message = EmailMessage(mail_from, mail_to, subject, body)
 
280
            body = b''.join(self.to_lines())
 
281
        message = email_message.EmailMessage(mail_from, mail_to, subject,
 
282
                                             body)
267
283
        return message
268
284
 
269
285
    def install_revisions(self, target_repo):
271
287
        if not target_repo.has_revision(self.revision_id):
272
288
            if self.patch_type == 'bundle':
273
289
                info = bundle_serializer.read_bundle(
274
 
                    StringIO(self.get_raw_bundle()))
 
290
                    BytesIO(self.get_raw_bundle()))
275
291
                # We don't use the bundle's target revision, because
276
292
                # MergeDirective.revision_id is authoritative.
277
293
                try:
289
305
                                           info.real_revisions)
290
306
                    for revision in info.real_revisions:
291
307
                        for parent_id in revision.parent_ids:
292
 
                            if (parent_id not in bundle_revisions and
293
 
                                not target_repo.has_revision(parent_id)):
 
308
                            if (parent_id not in bundle_revisions
 
309
                                    and not target_repo.has_revision(parent_id)):
294
310
                                missing_revisions.append(parent_id)
295
311
                    # reverse missing revisions to try to get heads first
296
312
                    unique_missing = []
335
351
        elif len(self.hooks['merge_request_body']) > 0:
336
352
            trace.warning('Cannot run merge_request_body hooks because mail'
337
353
                          ' client %s does not support message bodies.',
338
 
                        mail_client.__class__.__name__)
 
354
                          mail_client.__class__.__name__)
339
355
        mail_client.compose_merge_request(to, subject,
340
 
                                          ''.join(self.to_lines()),
 
356
                                          b''.join(self.to_lines()),
341
357
                                          basename, body)
342
358
 
343
359
 
357
373
    directly using the standard patch program.
358
374
    """
359
375
 
360
 
    _format_string = 'Bazaar merge directive format 1'
 
376
    _format_string = b'Bazaar merge directive format 1'
361
377
 
362
378
    def __init__(self, revision_id, testament_sha1, time, timezone,
363
379
                 target_branch, patch=None, patch_type=None,
369
385
            merge.
370
386
        :param time: The current POSIX timestamp time
371
387
        :param timezone: The timezone offset
372
 
        :param target_branch: The branch to apply the merge to
 
388
        :param target_branch: Location of the branch to apply the merge to
373
389
        :param patch: The text of a diff or bundle
374
390
        :param patch_type: None, "diff" or "bundle", depending on the contents
375
391
            of patch
377
393
        :param message: The message to use when committing this merge
378
394
        """
379
395
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
380
 
            timezone, target_branch, patch, source_branch, message)
 
396
                                    timezone, target_branch, patch, source_branch, message)
381
397
        if patch_type not in (None, 'diff', 'bundle'):
382
398
            raise ValueError(patch_type)
383
399
        if patch_type != 'bundle' and source_branch is None:
409
425
        :return: a MergeRequest
410
426
        """
411
427
        line_iter = iter(lines)
412
 
        firstline = ""
 
428
        firstline = b""
413
429
        for line in line_iter:
414
 
            if line.startswith('# Bazaar merge directive format '):
 
430
            if line.startswith(b'# Bazaar merge directive format '):
415
431
                return _format_registry.get(line[2:].rstrip())._from_lines(
416
432
                    line_iter)
417
433
            firstline = firstline or line.strip()
425
441
            patch = None
426
442
            patch_type = None
427
443
        else:
428
 
            patch = ''.join(patch_lines)
 
444
            patch = b''.join(patch_lines)
429
445
            try:
430
 
                bundle_serializer.read_bundle(StringIO(patch))
 
446
                bundle_serializer.read_bundle(BytesIO(patch))
431
447
            except (errors.NotABundle, errors.BundleNotSupported,
432
448
                    errors.BadBundle):
433
449
                patch_type = 'diff'
442
458
            except KeyError:
443
459
                pass
444
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')
445
463
        return MergeDirective(time=time, timezone=timezone,
446
464
                              patch_type=patch_type, patch=patch, **kwargs)
447
465
 
453
471
 
454
472
    @staticmethod
455
473
    def _generate_bundle(repository, revision_id, ancestor_id):
456
 
        s = StringIO()
 
474
        s = BytesIO()
457
475
        bundle_serializer.write_bundle(repository, revision_id,
458
476
                                       ancestor_id, s, '0.9')
459
477
        return s.getvalue()
468
486
 
469
487
class MergeDirective2(BaseMergeDirective):
470
488
 
471
 
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
 
489
    _format_string = b'Bazaar merge directive format 2 (Bazaar 0.90)'
472
490
 
473
491
    def __init__(self, revision_id, testament_sha1, time, timezone,
474
492
                 target_branch, patch=None, source_branch=None, message=None,
476
494
        if source_branch is None and bundle is None:
477
495
            raise errors.NoMergeSource()
478
496
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
479
 
            timezone, target_branch, patch, source_branch, message)
 
497
                                    timezone, target_branch, patch, source_branch, message)
480
498
        self.bundle = bundle
481
499
        self.base_revision_id = base_revision_id
482
500
 
498
516
        if self.bundle is None:
499
517
            return None
500
518
        else:
501
 
            return self.bundle.decode('base-64')
 
519
            return base64.b64decode(self.bundle)
502
520
 
503
521
    @classmethod
504
522
    def _from_lines(klass, line_iter):
506
524
        patch = None
507
525
        bundle = None
508
526
        try:
509
 
            start = line_iter.next()
 
527
            start = next(line_iter)
510
528
        except StopIteration:
511
529
            pass
512
530
        else:
513
 
            if start.startswith('# Begin patch'):
 
531
            if start.startswith(b'# Begin patch'):
514
532
                patch_lines = []
515
533
                for line in line_iter:
516
 
                    if line.startswith('# Begin bundle'):
 
534
                    if line.startswith(b'# Begin bundle'):
517
535
                        start = line
518
536
                        break
519
537
                    patch_lines.append(line)
520
538
                else:
521
539
                    start = None
522
 
                patch = ''.join(patch_lines)
 
540
                patch = b''.join(patch_lines)
523
541
            if start is not None:
524
 
                if start.startswith('# Begin bundle'):
525
 
                    bundle = ''.join(line_iter)
 
542
                if start.startswith(b'# Begin bundle'):
 
543
                    bundle = b''.join(line_iter)
526
544
                else:
527
545
                    raise errors.IllegalMergeDirectivePayload(start)
528
546
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
536
554
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
537
555
        kwargs['base_revision_id'] =\
538
556
            kwargs['base_revision_id'].encode('utf-8')
 
557
        if 'testament_sha1' in kwargs:
 
558
            kwargs['testament_sha1'] = kwargs['testament_sha1'].encode('ascii')
539
559
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
540
560
                     **kwargs)
541
561
 
542
562
    def to_lines(self):
543
563
        lines = self._to_lines(base_revision=True)
544
564
        if self.patch is not None:
545
 
            lines.append('# Begin patch\n')
 
565
            lines.append(b'# Begin patch\n')
546
566
            lines.extend(self.patch.splitlines(True))
547
567
        if self.bundle is not None:
548
 
            lines.append('# Begin bundle\n')
 
568
            lines.append(b'# Begin bundle\n')
549
569
            lines.extend(self.bundle.splitlines(True))
550
570
        return lines
551
571
 
552
572
    @classmethod
553
573
    def from_objects(klass, repository, revision_id, time, timezone,
554
 
                 target_branch, include_patch=True, include_bundle=True,
555
 
                 local_target_branch=None, public_branch=None, message=None,
556
 
                 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):
557
577
        """Generate a merge directive from various objects
558
578
 
559
579
        :param repository: The repository containing the revision
563
583
        :param target_branch: The url of the branch to merge into
564
584
        :param include_patch: If true, include a preview patch
565
585
        :param include_bundle: If true, include a bundle
566
 
        :param local_target_branch: a local copy of the target branch
567
 
        :param public_branch: location of a public branch containing the target
568
 
            revision.
 
586
        :param local_target_branch: the target branch, either itself or a local copy
 
587
        :param public_branch: location of a public branch containing
 
588
            the target revision.
569
589
        :param message: Message to use when committing the merge
570
590
        :return: The merge directive
571
591
 
575
595
        If the message is not supplied, the message from revision_id will be
576
596
        used for the commit.
577
597
        """
578
 
        locked = []
579
 
        try:
580
 
            repository.lock_write()
581
 
            locked.append(repository)
 
598
        with contextlib.ExitStack() as exit_stack:
 
599
            exit_stack.enter_context(repository.lock_write())
582
600
            t_revision_id = revision_id
583
 
            if revision_id == 'null:':
 
601
            if revision_id == b'null:':
584
602
                t_revision_id = None
585
603
            t = testament.StrictTestament3.from_revision(repository,
586
 
                t_revision_id)
587
 
            submit_branch = _mod_branch.Branch.open(target_branch)
588
 
            submit_branch.lock_read()
589
 
            locked.append(submit_branch)
 
604
                                                         t_revision_id)
 
605
            if local_target_branch is None:
 
606
                submit_branch = _mod_branch.Branch.open(target_branch)
 
607
            else:
 
608
                submit_branch = local_target_branch
 
609
            exit_stack.enter_context(submit_branch.lock_read())
590
610
            if submit_branch.get_public_branch() is not None:
591
611
                target_branch = submit_branch.get_public_branch()
592
612
            submit_revision_id = submit_branch.last_revision()
605
625
                patch = None
606
626
 
607
627
            if include_bundle:
608
 
                bundle = klass._generate_bundle(repository, revision_id,
609
 
                    ancestor_id).encode('base-64')
 
628
                bundle = base64.b64encode(klass._generate_bundle(repository, revision_id,
 
629
                                                                 ancestor_id))
610
630
            else:
611
631
                bundle = None
612
632
 
613
633
            if public_branch is not None and not include_bundle:
614
634
                public_branch_obj = _mod_branch.Branch.open(public_branch)
615
 
                public_branch_obj.lock_read()
616
 
                locked.append(public_branch_obj)
 
635
                exit_stack.enter_context(public_branch_obj.lock_read())
617
636
                if not public_branch_obj.repository.has_revision(
618
 
                    revision_id):
 
637
                        revision_id):
619
638
                    raise errors.PublicBranchOutOfDate(public_branch,
620
639
                                                       revision_id)
621
640
            testament_sha1 = t.as_sha1()
622
 
        finally:
623
 
            for entry in reversed(locked):
624
 
                entry.unlock()
625
641
        return klass(revision_id, testament_sha1, time, timezone,
626
 
            target_branch, patch, public_branch, message, bundle,
627
 
            base_revision_id)
 
642
                     target_branch, patch, public_branch, message, bundle,
 
643
                     base_revision_id)
628
644
 
629
645
    def _verify_patch(self, repository):
630
646
        calculated_patch = self._generate_diff(repository, self.revision_id,
631
647
                                               self.base_revision_id)
632
648
        # Convert line-endings to UNIX
633
 
        stored_patch = re.sub('\r\n?', '\n', self.patch)
634
 
        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)
635
651
        # Strip trailing whitespace
636
 
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
637
 
        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)
638
654
        return (calculated_patch == stored_patch)
639
655
 
640
656
    def get_merge_request(self, repository):
670
686
# already merge directives in the wild that used 0.19. Registering with the old
671
687
# format string to retain compatibility with those merge directives.
672
688
_format_registry.register(MergeDirective2,
673
 
                          'Bazaar merge directive format 2 (Bazaar 0.19)')
 
689
                          b'Bazaar merge directive format 2 (Bazaar 0.19)')