~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge_directive.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-09 21:42:24 UTC
  • mto: This revision was merged to the branch mainline in revision 3543.
  • Revision ID: john@arbash-meinel.com-20080709214224-r75k87r6a01pfc3h
Restore a real weave merge to 'bzr merge --weave'.

To do so efficiently, we only add the simple LCAs to the final weave
object, unless we run into complexities with the merge graph.
This gives the same effective result as adding all the texts,
with the advantage of not having to extract all of them.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
from __future__ import absolute_import
18
17
 
19
18
from StringIO import StringIO
20
19
import re
21
20
 
22
 
from bzrlib import lazy_import
23
 
lazy_import.lazy_import(globals(), """
24
21
from bzrlib import (
25
22
    branch as _mod_branch,
26
23
    diff,
27
 
    email_message,
28
24
    errors,
29
25
    gpg,
30
 
    hooks,
31
26
    registry,
32
27
    revision as _mod_revision,
33
28
    rio,
34
29
    testament,
35
30
    timestamp,
36
 
    trace,
37
31
    )
38
32
from bzrlib.bundle import (
39
33
    serializer as bundle_serializer,
40
34
    )
41
 
""")
42
 
 
43
 
 
44
 
class MergeRequestBodyParams(object):
45
 
    """Parameter object for the merge_request_body hook."""
46
 
 
47
 
    def __init__(self, body, orig_body, directive, to, basename, subject,
48
 
                 branch, tree=None):
49
 
        self.body = body
50
 
        self.orig_body = orig_body
51
 
        self.directive = directive
52
 
        self.branch = branch
53
 
        self.tree = tree
54
 
        self.to = to
55
 
        self.basename = basename
56
 
        self.subject = subject
57
 
 
58
 
 
59
 
class MergeDirectiveHooks(hooks.Hooks):
60
 
    """Hooks for MergeDirective classes."""
61
 
 
62
 
    def __init__(self):
63
 
        hooks.Hooks.__init__(self, "bzrlib.merge_directive", "BaseMergeDirective.hooks")
64
 
        self.add_hook('merge_request_body',
65
 
            "Called with a MergeRequestBodyParams when a body is needed for"
66
 
            " a merge request.  Callbacks must return a body.  If more"
67
 
            " than one callback is registered, the output of one callback is"
68
 
            " provided to the next.", (1, 15, 0))
69
 
 
70
 
 
71
 
class BaseMergeDirective(object):
72
 
    """A request to perform a merge into a branch.
73
 
 
74
 
    This is the base class that all merge directive implementations 
75
 
    should derive from.
76
 
 
77
 
    :cvar multiple_output_files: Whether or not this merge directive 
78
 
        stores a set of revisions in more than one file
79
 
    """
80
 
 
81
 
    hooks = MergeDirectiveHooks()
82
 
 
83
 
    multiple_output_files = False
 
35
from bzrlib.email_message import EmailMessage
 
36
 
 
37
 
 
38
class _BaseMergeDirective(object):
84
39
 
85
40
    def __init__(self, revision_id, testament_sha1, time, timezone,
86
 
                 target_branch, patch=None, source_branch=None,
87
 
                 message=None, bundle=None):
 
41
                 target_branch, patch=None, source_branch=None, message=None,
 
42
                 bundle=None):
88
43
        """Constructor.
89
44
 
90
45
        :param revision_id: The revision to merge
92
47
            merge.
93
48
        :param time: The current POSIX timestamp time
94
49
        :param timezone: The timezone offset
95
 
        :param target_branch: Location of branch to apply the merge to
 
50
        :param target_branch: The branch to apply the merge to
96
51
        :param patch: The text of a diff or bundle
97
52
        :param source_branch: A public location to merge the revision from
98
53
        :param message: The message to use when committing this merge
106
61
        self.source_branch = source_branch
107
62
        self.message = message
108
63
 
109
 
    def to_lines(self):
110
 
        """Serialize as a list of lines
111
 
 
112
 
        :return: a list of lines
113
 
        """
114
 
        raise NotImplementedError(self.to_lines)
115
 
 
116
 
    def to_files(self):
117
 
        """Serialize as a set of files.
118
 
 
119
 
        :return: List of tuples with filename and contents as lines
120
 
        """
121
 
        raise NotImplementedError(self.to_files)
122
 
 
123
 
    def get_raw_bundle(self):
124
 
        """Return the bundle for this merge directive.
125
 
 
126
 
        :return: bundle text or None if there is no bundle
127
 
        """
128
 
        return None
129
 
 
130
64
    def _to_lines(self, base_revision=False):
131
65
        """Serialize as a list of lines
132
66
 
146
80
        lines.append('# \n')
147
81
        return lines
148
82
 
149
 
    def write_to_directory(self, path):
150
 
        """Write this merge directive to a series of files in a directory.
151
 
 
152
 
        :param path: Filesystem path to write to
153
 
        """
154
 
        raise NotImplementedError(self.write_to_directory)
155
 
 
156
83
    @classmethod
157
84
    def from_objects(klass, repository, revision_id, time, timezone,
158
85
                 target_branch, patch_type='bundle',
166
93
        :param target_branch: The url of the branch to merge into
167
94
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
168
95
            patch desired.
169
 
        :param local_target_branch: the submit branch, either itself or a local copy
170
 
        :param public_branch: location of a public branch containing
171
 
            the target revision.
 
96
        :param local_target_branch: a local copy of the target branch
 
97
        :param public_branch: location of a public branch containing the target
 
98
            revision.
172
99
        :param message: Message to use when committing the merge
173
100
        :return: The merge directive
174
101
 
182
109
        if revision_id == _mod_revision.NULL_REVISION:
183
110
            t_revision_id = None
184
111
        t = testament.StrictTestament3.from_revision(repository, t_revision_id)
185
 
        if local_target_branch is None:
186
 
            submit_branch = _mod_branch.Branch.open(target_branch)
187
 
        else:
188
 
            submit_branch = local_target_branch
 
112
        submit_branch = _mod_branch.Branch.open(target_branch)
189
113
        if submit_branch.get_public_branch() is not None:
190
114
            target_branch = submit_branch.get_public_branch()
191
115
        if patch_type is None:
248
172
        :param branch: The source branch, to get the signing strategy
249
173
        :return: a string
250
174
        """
251
 
        my_gpg = gpg.GPGStrategy(branch.get_config_stack())
 
175
        my_gpg = gpg.GPGStrategy(branch.get_config())
252
176
        return my_gpg.sign(''.join(self.to_lines()))
253
177
 
254
178
    def to_email(self, mail_to, branch, sign=False):
260
184
        :param sign: If True, gpg-sign the email
261
185
        :return: an email message
262
186
        """
263
 
        mail_from = branch.get_config_stack().get('email')
 
187
        mail_from = branch.get_config().username()
264
188
        if self.message is not None:
265
189
            subject = self.message
266
190
        else:
270
194
            body = self.to_signed(branch)
271
195
        else:
272
196
            body = ''.join(self.to_lines())
273
 
        message = email_message.EmailMessage(mail_from, mail_to, subject,
274
 
            body)
 
197
        message = EmailMessage(mail_from, mail_to, subject, body)
275
198
        return message
276
199
 
277
200
    def install_revisions(self, target_repo):
287
210
                except errors.RevisionNotPresent:
288
211
                    # At least one dependency isn't present.  Try installing
289
212
                    # missing revisions from the submit branch
290
 
                    try:
291
 
                        submit_branch = \
292
 
                            _mod_branch.Branch.open(self.target_branch)
293
 
                    except errors.NotBranchError:
294
 
                        raise errors.TargetNotBranch(self.target_branch)
 
213
                    submit_branch = _mod_branch.Branch.open(self.target_branch)
295
214
                    missing_revisions = []
296
215
                    bundle_revisions = set(r.revision_id for r in
297
216
                                           info.real_revisions)
317
236
                target_repo.fetch(source_branch.repository, self.revision_id)
318
237
        return self.revision_id
319
238
 
320
 
    def compose_merge_request(self, mail_client, to, body, branch, tree=None):
321
 
        """Compose a request to merge this directive.
322
 
 
323
 
        :param mail_client: The mail client to use for composing this request.
324
 
        :param to: The address to compose the request to.
325
 
        :param branch: The Branch that was used to produce this directive.
326
 
        :param tree: The Tree (if any) for the Branch used to produce this
327
 
            directive.
328
 
        """
329
 
        basename = self.get_disk_name(branch)
330
 
        subject = '[MERGE] '
331
 
        if self.message is not None:
332
 
            subject += self.message
333
 
        else:
334
 
            revision = branch.repository.get_revision(self.revision_id)
335
 
            subject += revision.get_summary()
336
 
        if getattr(mail_client, 'supports_body', False):
337
 
            orig_body = body
338
 
            for hook in self.hooks['merge_request_body']:
339
 
                params = MergeRequestBodyParams(body, orig_body, self,
340
 
                                                to, basename, subject, branch,
341
 
                                                tree)
342
 
                body = hook(params)
343
 
        elif len(self.hooks['merge_request_body']) > 0:
344
 
            trace.warning('Cannot run merge_request_body hooks because mail'
345
 
                          ' client %s does not support message bodies.',
346
 
                        mail_client.__class__.__name__)
347
 
        mail_client.compose_merge_request(to, subject,
348
 
                                          ''.join(self.to_lines()),
349
 
                                          basename, body)
350
 
 
351
 
 
352
 
class MergeDirective(BaseMergeDirective):
 
239
 
 
240
class MergeDirective(_BaseMergeDirective):
353
241
 
354
242
    """A request to perform a merge into a branch.
355
243
 
377
265
            merge.
378
266
        :param time: The current POSIX timestamp time
379
267
        :param timezone: The timezone offset
380
 
        :param target_branch: Location of the branch to apply the merge to
 
268
        :param target_branch: The branch to apply the merge to
381
269
        :param patch: The text of a diff or bundle
382
270
        :param patch_type: None, "diff" or "bundle", depending on the contents
383
271
            of patch
384
272
        :param source_branch: A public location to merge the revision from
385
273
        :param message: The message to use when committing this merge
386
274
        """
387
 
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
 
275
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
388
276
            timezone, target_branch, patch, source_branch, message)
389
277
        if patch_type not in (None, 'diff', 'bundle'):
390
278
            raise ValueError(patch_type)
417
305
        :return: a MergeRequest
418
306
        """
419
307
        line_iter = iter(lines)
420
 
        firstline = ""
421
308
        for line in line_iter:
422
309
            if line.startswith('# Bazaar merge directive format '):
423
 
                return _format_registry.get(line[2:].rstrip())._from_lines(
424
 
                    line_iter)
425
 
            firstline = firstline or line.strip()
426
 
        raise errors.NotAMergeDirective(firstline)
 
310
                break
 
311
        else:
 
312
            if len(lines) > 0:
 
313
                raise errors.NotAMergeDirective(lines[0])
 
314
            else:
 
315
                raise errors.NotAMergeDirective('')
 
316
        return _format_registry.get(line[2:].rstrip())._from_lines(line_iter)
427
317
 
428
318
    @classmethod
429
319
    def _from_lines(klass, line_iter):
474
364
        return None, self.revision_id, 'inapplicable'
475
365
 
476
366
 
477
 
class MergeDirective2(BaseMergeDirective):
 
367
class MergeDirective2(_BaseMergeDirective):
478
368
 
479
369
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
480
370
 
483
373
                 bundle=None, base_revision_id=None):
484
374
        if source_branch is None and bundle is None:
485
375
            raise errors.NoMergeSource()
486
 
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
 
376
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
487
377
            timezone, target_branch, patch, source_branch, message)
488
378
        self.bundle = bundle
489
379
        self.base_revision_id = base_revision_id
571
461
        :param target_branch: The url of the branch to merge into
572
462
        :param include_patch: If true, include a preview patch
573
463
        :param include_bundle: If true, include a bundle
574
 
        :param local_target_branch: the target branch, either itself or a local copy
575
 
        :param public_branch: location of a public branch containing
576
 
            the target revision.
 
464
        :param local_target_branch: a local copy of the target branch
 
465
        :param public_branch: location of a public branch containing the target
 
466
            revision.
577
467
        :param message: Message to use when committing the merge
578
468
        :return: The merge directive
579
469
 
592
482
                t_revision_id = None
593
483
            t = testament.StrictTestament3.from_revision(repository,
594
484
                t_revision_id)
595
 
            if local_target_branch is None:
596
 
                submit_branch = _mod_branch.Branch.open(target_branch)
597
 
            else:
598
 
                submit_branch = local_target_branch
 
485
            submit_branch = _mod_branch.Branch.open(target_branch)
599
486
            submit_branch.lock_read()
600
487
            locked.append(submit_branch)
601
488
            if submit_branch.get_public_branch() is not None:
629
516
                    revision_id):
630
517
                    raise errors.PublicBranchOutOfDate(public_branch,
631
518
                                                       revision_id)
632
 
            testament_sha1 = t.as_sha1()
633
519
        finally:
634
520
            for entry in reversed(locked):
635
521
                entry.unlock()
636
 
        return klass(revision_id, testament_sha1, time, timezone,
637
 
            target_branch, patch, public_branch, message, bundle,
638
 
            base_revision_id)
 
522
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
 
523
            patch, public_branch, message, bundle, base_revision_id)
639
524
 
640
525
    def _verify_patch(self, repository):
641
526
        calculated_patch = self._generate_diff(repository, self.revision_id,
677
562
_format_registry = MergeDirectiveFormatRegistry()
678
563
_format_registry.register(MergeDirective)
679
564
_format_registry.register(MergeDirective2)
680
 
# 0.19 never existed.  It got renamed to 0.90.  But by that point, there were
681
 
# already merge directives in the wild that used 0.19. Registering with the old
682
 
# format string to retain compatibility with those merge directives.
683
565
_format_registry.register(MergeDirective2,
684
566
                          'Bazaar merge directive format 2 (Bazaar 0.19)')