/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: 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:
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
    )
 
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
38
56
 
39
57
 
40
58
class MergeRequestBodyParams(object):
56
74
    """Hooks for MergeDirective classes."""
57
75
 
58
76
    def __init__(self):
59
 
        hooks.Hooks.__init__(self)
60
 
        self.create_hook(hooks.HookPoint('merge_request_body',
 
77
        hooks.Hooks.__init__(self, "breezy.merge_directive",
 
78
                             "BaseMergeDirective.hooks")
 
79
        self.add_hook(
 
80
            'merge_request_body',
61
81
            "Called with a MergeRequestBodyParams when a body is needed for"
62
82
            " a merge request.  Callbacks must return a body.  If more"
63
83
            " than one callback is registered, the output of one callback is"
64
 
            " provided to the next.", (1, 15, 0), False))
 
84
            " provided to the next.", (1, 15, 0))
65
85
 
66
86
 
67
87
class BaseMergeDirective(object):
68
88
    """A request to perform a merge into a branch.
69
89
 
70
 
    This is the base class that all merge directive implementations 
 
90
    This is the base class that all merge directive implementations
71
91
    should derive from.
72
92
 
73
 
    :cvar multiple_output_files: Whether or not this merge directive 
 
93
    :cvar multiple_output_files: Whether or not this merge directive
74
94
        stores a set of revisions in more than one file
75
95
    """
76
96
 
79
99
    multiple_output_files = False
80
100
 
81
101
    def __init__(self, revision_id, testament_sha1, time, timezone,
82
 
                 target_branch, patch=None, source_branch=None, message=None,
83
 
                 bundle=None):
 
102
                 target_branch, patch=None, source_branch=None,
 
103
                 message=None, bundle=None):
84
104
        """Constructor.
85
105
 
86
106
        :param revision_id: The revision to merge
88
108
            merge.
89
109
        :param time: The current POSIX timestamp time
90
110
        :param timezone: The timezone offset
91
 
        :param target_branch: The branch to apply the merge to
 
111
        :param target_branch: Location of branch to apply the merge to
92
112
        :param patch: The text of a diff or bundle
93
113
        :param source_branch: A public location to merge the revision from
94
114
        :param message: The message to use when committing this merge
137
157
                stanza.add(key, self.__dict__[key])
138
158
        if base_revision:
139
159
            stanza.add('base_revision_id', self.base_revision_id)
140
 
        lines = ['# ' + self._format_string + '\n']
 
160
        lines = [b'# ' + self._format_string + b'\n']
141
161
        lines.extend(rio.to_patch_lines(stanza))
142
 
        lines.append('# \n')
 
162
        lines.append(b'# \n')
143
163
        return lines
144
164
 
145
165
    def write_to_directory(self, path):
151
171
 
152
172
    @classmethod
153
173
    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):
 
174
                     target_branch, patch_type='bundle',
 
175
                     local_target_branch=None, public_branch=None, message=None):
156
176
        """Generate a merge directive from various objects
157
177
 
158
178
        :param repository: The repository containing the revision
162
182
        :param target_branch: The url of the branch to merge into
163
183
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
164
184
            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.
 
185
        :param local_target_branch: the submit branch, either itself or a local copy
 
186
        :param public_branch: location of a public branch containing
 
187
            the target revision.
168
188
        :param message: Message to use when committing the merge
169
189
        :return: The merge directive
170
190
 
178
198
        if revision_id == _mod_revision.NULL_REVISION:
179
199
            t_revision_id = None
180
200
        t = testament.StrictTestament3.from_revision(repository, t_revision_id)
181
 
        submit_branch = _mod_branch.Branch.open(target_branch)
 
201
        if local_target_branch is None:
 
202
            submit_branch = _mod_branch.Branch.open(target_branch)
 
203
        else:
 
204
            submit_branch = local_target_branch
182
205
        if submit_branch.get_public_branch() is not None:
183
206
            target_branch = submit_branch.get_public_branch()
184
207
        if patch_type is None:
192
215
                                                submit_revision_id)
193
216
            type_handler = {'bundle': klass._generate_bundle,
194
217
                            'diff': klass._generate_diff,
195
 
                            None: lambda x, y, z: None }
 
218
                            None: lambda x, y, z: None}
196
219
            patch = type_handler[patch_type](repository, revision_id,
197
220
                                             ancestor_id)
198
221
 
203
226
                                                   revision_id)
204
227
 
205
228
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
206
 
            patch, patch_type, public_branch, message)
 
229
                     patch, patch_type, public_branch, message)
207
230
 
208
231
    def get_disk_name(self, branch):
209
232
        """Generate a suitable basename for storing this directive on disk
215
238
        if self.revision_id == revision_id:
216
239
            revno = [revno]
217
240
        else:
218
 
            revno = branch.get_revision_id_to_revno_map().get(self.revision_id,
219
 
                ['merge'])
220
 
        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('-')
221
246
        return '%s-%s' % (nick, '.'.join(str(n) for n in revno))
222
247
 
223
248
    @staticmethod
224
249
    def _generate_diff(repository, revision_id, ancestor_id):
225
250
        tree_1 = repository.revision_tree(ancestor_id)
226
251
        tree_2 = repository.revision_tree(revision_id)
227
 
        s = StringIO()
 
252
        s = BytesIO()
228
253
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
229
254
        return s.getvalue()
230
255
 
231
256
    @staticmethod
232
257
    def _generate_bundle(repository, revision_id, ancestor_id):
233
 
        s = StringIO()
 
258
        s = BytesIO()
234
259
        bundle_serializer.write_bundle(repository, revision_id,
235
260
                                       ancestor_id, s)
236
261
        return s.getvalue()
241
266
        :param branch: The source branch, to get the signing strategy
242
267
        :return: a string
243
268
        """
244
 
        my_gpg = gpg.GPGStrategy(branch.get_config())
245
 
        return my_gpg.sign(''.join(self.to_lines()))
 
269
        my_gpg = gpg.GPGStrategy(branch.get_config_stack())
 
270
        return my_gpg.sign(b''.join(self.to_lines()), gpg.MODE_CLEAR)
246
271
 
247
272
    def to_email(self, mail_to, branch, sign=False):
248
273
        """Serialize as an email message.
253
278
        :param sign: If True, gpg-sign the email
254
279
        :return: an email message
255
280
        """
256
 
        mail_from = branch.get_config().username()
 
281
        mail_from = branch.get_config_stack().get('email')
257
282
        if self.message is not None:
258
283
            subject = self.message
259
284
        else:
262
287
        if sign:
263
288
            body = self.to_signed(branch)
264
289
        else:
265
 
            body = ''.join(self.to_lines())
266
 
        message = EmailMessage(mail_from, mail_to, subject, body)
 
290
            body = b''.join(self.to_lines())
 
291
        message = email_message.EmailMessage(mail_from, mail_to, subject,
 
292
                                             body)
267
293
        return message
268
294
 
269
295
    def install_revisions(self, target_repo):
271
297
        if not target_repo.has_revision(self.revision_id):
272
298
            if self.patch_type == 'bundle':
273
299
                info = bundle_serializer.read_bundle(
274
 
                    StringIO(self.get_raw_bundle()))
 
300
                    BytesIO(self.get_raw_bundle()))
275
301
                # We don't use the bundle's target revision, because
276
302
                # MergeDirective.revision_id is authoritative.
277
303
                try:
289
315
                                           info.real_revisions)
290
316
                    for revision in info.real_revisions:
291
317
                        for parent_id in revision.parent_ids:
292
 
                            if (parent_id not in bundle_revisions and
293
 
                                not target_repo.has_revision(parent_id)):
 
318
                            if (parent_id not in bundle_revisions
 
319
                                    and not target_repo.has_revision(parent_id)):
294
320
                                missing_revisions.append(parent_id)
295
321
                    # reverse missing revisions to try to get heads first
296
322
                    unique_missing = []
335
361
        elif len(self.hooks['merge_request_body']) > 0:
336
362
            trace.warning('Cannot run merge_request_body hooks because mail'
337
363
                          ' client %s does not support message bodies.',
338
 
                        mail_client.__class__.__name__)
 
364
                          mail_client.__class__.__name__)
339
365
        mail_client.compose_merge_request(to, subject,
340
 
                                          ''.join(self.to_lines()),
 
366
                                          b''.join(self.to_lines()),
341
367
                                          basename, body)
342
368
 
343
369
 
357
383
    directly using the standard patch program.
358
384
    """
359
385
 
360
 
    _format_string = 'Bazaar merge directive format 1'
 
386
    _format_string = b'Bazaar merge directive format 1'
361
387
 
362
388
    def __init__(self, revision_id, testament_sha1, time, timezone,
363
389
                 target_branch, patch=None, patch_type=None,
369
395
            merge.
370
396
        :param time: The current POSIX timestamp time
371
397
        :param timezone: The timezone offset
372
 
        :param target_branch: The branch to apply the merge to
 
398
        :param target_branch: Location of the branch to apply the merge to
373
399
        :param patch: The text of a diff or bundle
374
400
        :param patch_type: None, "diff" or "bundle", depending on the contents
375
401
            of patch
377
403
        :param message: The message to use when committing this merge
378
404
        """
379
405
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
380
 
            timezone, target_branch, patch, source_branch, message)
 
406
                                    timezone, target_branch, patch, source_branch, message)
381
407
        if patch_type not in (None, 'diff', 'bundle'):
382
408
            raise ValueError(patch_type)
383
409
        if patch_type != 'bundle' and source_branch is None:
409
435
        :return: a MergeRequest
410
436
        """
411
437
        line_iter = iter(lines)
412
 
        firstline = ""
 
438
        firstline = b""
413
439
        for line in line_iter:
414
 
            if line.startswith('# Bazaar merge directive format '):
 
440
            if line.startswith(b'# Bazaar merge directive format '):
415
441
                return _format_registry.get(line[2:].rstrip())._from_lines(
416
442
                    line_iter)
417
443
            firstline = firstline or line.strip()
425
451
            patch = None
426
452
            patch_type = None
427
453
        else:
428
 
            patch = ''.join(patch_lines)
 
454
            patch = b''.join(patch_lines)
429
455
            try:
430
 
                bundle_serializer.read_bundle(StringIO(patch))
 
456
                bundle_serializer.read_bundle(BytesIO(patch))
431
457
            except (errors.NotABundle, errors.BundleNotSupported,
432
458
                    errors.BadBundle):
433
459
                patch_type = 'diff'
442
468
            except KeyError:
443
469
                pass
444
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')
445
473
        return MergeDirective(time=time, timezone=timezone,
446
474
                              patch_type=patch_type, patch=patch, **kwargs)
447
475
 
453
481
 
454
482
    @staticmethod
455
483
    def _generate_bundle(repository, revision_id, ancestor_id):
456
 
        s = StringIO()
 
484
        s = BytesIO()
457
485
        bundle_serializer.write_bundle(repository, revision_id,
458
486
                                       ancestor_id, s, '0.9')
459
487
        return s.getvalue()
468
496
 
469
497
class MergeDirective2(BaseMergeDirective):
470
498
 
471
 
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
 
499
    _format_string = b'Bazaar merge directive format 2 (Bazaar 0.90)'
472
500
 
473
501
    def __init__(self, revision_id, testament_sha1, time, timezone,
474
502
                 target_branch, patch=None, source_branch=None, message=None,
476
504
        if source_branch is None and bundle is None:
477
505
            raise errors.NoMergeSource()
478
506
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
479
 
            timezone, target_branch, patch, source_branch, message)
 
507
                                    timezone, target_branch, patch, source_branch, message)
480
508
        self.bundle = bundle
481
509
        self.base_revision_id = base_revision_id
482
510
 
498
526
        if self.bundle is None:
499
527
            return None
500
528
        else:
501
 
            return self.bundle.decode('base-64')
 
529
            return base64.b64decode(self.bundle)
502
530
 
503
531
    @classmethod
504
532
    def _from_lines(klass, line_iter):
506
534
        patch = None
507
535
        bundle = None
508
536
        try:
509
 
            start = line_iter.next()
 
537
            start = next(line_iter)
510
538
        except StopIteration:
511
539
            pass
512
540
        else:
513
 
            if start.startswith('# Begin patch'):
 
541
            if start.startswith(b'# Begin patch'):
514
542
                patch_lines = []
515
543
                for line in line_iter:
516
 
                    if line.startswith('# Begin bundle'):
 
544
                    if line.startswith(b'# Begin bundle'):
517
545
                        start = line
518
546
                        break
519
547
                    patch_lines.append(line)
520
548
                else:
521
549
                    start = None
522
 
                patch = ''.join(patch_lines)
 
550
                patch = b''.join(patch_lines)
523
551
            if start is not None:
524
 
                if start.startswith('# Begin bundle'):
525
 
                    bundle = ''.join(line_iter)
 
552
                if start.startswith(b'# Begin bundle'):
 
553
                    bundle = b''.join(line_iter)
526
554
                else:
527
 
                    raise errors.IllegalMergeDirectivePayload(start)
 
555
                    raise IllegalMergeDirectivePayload(start)
528
556
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
529
557
        kwargs = {}
530
558
        for key in ('revision_id', 'testament_sha1', 'target_branch',
536
564
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
537
565
        kwargs['base_revision_id'] =\
538
566
            kwargs['base_revision_id'].encode('utf-8')
 
567
        if 'testament_sha1' in kwargs:
 
568
            kwargs['testament_sha1'] = kwargs['testament_sha1'].encode('ascii')
539
569
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
540
570
                     **kwargs)
541
571
 
542
572
    def to_lines(self):
543
573
        lines = self._to_lines(base_revision=True)
544
574
        if self.patch is not None:
545
 
            lines.append('# Begin patch\n')
 
575
            lines.append(b'# Begin patch\n')
546
576
            lines.extend(self.patch.splitlines(True))
547
577
        if self.bundle is not None:
548
 
            lines.append('# Begin bundle\n')
 
578
            lines.append(b'# Begin bundle\n')
549
579
            lines.extend(self.bundle.splitlines(True))
550
580
        return lines
551
581
 
552
582
    @classmethod
553
583
    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):
 
584
                     target_branch, include_patch=True, include_bundle=True,
 
585
                     local_target_branch=None, public_branch=None, message=None,
 
586
                     base_revision_id=None):
557
587
        """Generate a merge directive from various objects
558
588
 
559
589
        :param repository: The repository containing the revision
563
593
        :param target_branch: The url of the branch to merge into
564
594
        :param include_patch: If true, include a preview patch
565
595
        :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.
 
596
        :param local_target_branch: the target branch, either itself or a local copy
 
597
        :param public_branch: location of a public branch containing
 
598
            the target revision.
569
599
        :param message: Message to use when committing the merge
570
600
        :return: The merge directive
571
601
 
575
605
        If the message is not supplied, the message from revision_id will be
576
606
        used for the commit.
577
607
        """
578
 
        locked = []
579
 
        try:
580
 
            repository.lock_write()
581
 
            locked.append(repository)
 
608
        with contextlib.ExitStack() as exit_stack:
 
609
            exit_stack.enter_context(repository.lock_write())
582
610
            t_revision_id = revision_id
583
 
            if revision_id == 'null:':
 
611
            if revision_id == b'null:':
584
612
                t_revision_id = None
585
613
            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)
 
614
                                                         t_revision_id)
 
615
            if local_target_branch is None:
 
616
                submit_branch = _mod_branch.Branch.open(target_branch)
 
617
            else:
 
618
                submit_branch = local_target_branch
 
619
            exit_stack.enter_context(submit_branch.lock_read())
590
620
            if submit_branch.get_public_branch() is not None:
591
621
                target_branch = submit_branch.get_public_branch()
592
622
            submit_revision_id = submit_branch.last_revision()
605
635
                patch = None
606
636
 
607
637
            if include_bundle:
608
 
                bundle = klass._generate_bundle(repository, revision_id,
609
 
                    ancestor_id).encode('base-64')
 
638
                bundle = base64.b64encode(klass._generate_bundle(repository, revision_id,
 
639
                                                                 ancestor_id))
610
640
            else:
611
641
                bundle = None
612
642
 
613
643
            if public_branch is not None and not include_bundle:
614
644
                public_branch_obj = _mod_branch.Branch.open(public_branch)
615
 
                public_branch_obj.lock_read()
616
 
                locked.append(public_branch_obj)
 
645
                exit_stack.enter_context(public_branch_obj.lock_read())
617
646
                if not public_branch_obj.repository.has_revision(
618
 
                    revision_id):
 
647
                        revision_id):
619
648
                    raise errors.PublicBranchOutOfDate(public_branch,
620
649
                                                       revision_id)
621
650
            testament_sha1 = t.as_sha1()
622
 
        finally:
623
 
            for entry in reversed(locked):
624
 
                entry.unlock()
625
651
        return klass(revision_id, testament_sha1, time, timezone,
626
 
            target_branch, patch, public_branch, message, bundle,
627
 
            base_revision_id)
 
652
                     target_branch, patch, public_branch, message, bundle,
 
653
                     base_revision_id)
628
654
 
629
655
    def _verify_patch(self, repository):
630
656
        calculated_patch = self._generate_diff(repository, self.revision_id,
631
657
                                               self.base_revision_id)
632
658
        # 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)
 
659
        stored_patch = re.sub(b'\r\n?', b'\n', self.patch)
 
660
        calculated_patch = re.sub(b'\r\n?', b'\n', calculated_patch)
635
661
        # Strip trailing whitespace
636
 
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
637
 
        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)
638
664
        return (calculated_patch == stored_patch)
639
665
 
640
666
    def get_merge_request(self, repository):
670
696
# already merge directives in the wild that used 0.19. Registering with the old
671
697
# format string to retain compatibility with those merge directives.
672
698
_format_registry.register(MergeDirective2,
673
 
                          'Bazaar merge directive format 2 (Bazaar 0.19)')
 
699
                          b'Bazaar merge directive format 2 (Bazaar 0.19)')