~bzr-pqm/bzr/bzr.dev

1551.12.36 by Aaron Bentley
Fix failing tests
1
# Copyright (C) 2007 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1551.12.36 by Aaron Bentley
Fix failing tests
16
17
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
18
from StringIO import StringIO
2520.4.105 by Aaron Bentley
Implement patch verification
19
import re
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
20
21
from bzrlib import (
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
22
    branch as _mod_branch,
23
    diff,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
24
    errors,
1551.12.16 by Aaron Bentley
Enable signing merge directives
25
    gpg,
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
26
    hooks,
2520.4.73 by Aaron Bentley
Implement new merge directive format
27
    registry,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
28
    revision as _mod_revision,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
29
    rio,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
30
    testament,
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
31
    timestamp,
4098.5.18 by Aaron Bentley
Gracefully handle mail clients that don't support bodies.
32
    trace,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
33
    )
1551.14.4 by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables'
34
from bzrlib.bundle import (
35
    serializer as bundle_serializer,
36
    )
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
37
from bzrlib.email_message import EmailMessage
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
38
39
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
40
class MergeRequestBodyParams(object):
4098.5.17 by Aaron Bentley
cleanup
41
    """Parameter object for the merge_request_body hook."""
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
42
43
    def __init__(self, body, orig_body, directive, to, basename, subject,
44
                 branch, tree=None):
45
        self.body = body
46
        self.orig_body = orig_body
47
        self.directive = directive
48
        self.branch = branch
49
        self.tree = tree
50
        self.to = to
51
        self.basename = basename
52
        self.subject = subject
53
54
55
class MergeDirectiveHooks(hooks.Hooks):
4098.5.17 by Aaron Bentley
cleanup
56
    """Hooks for MergeDirective classes."""
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
57
58
    def __init__(self):
59
        hooks.Hooks.__init__(self)
60
        self.create_hook(hooks.HookPoint('merge_request_body',
61
            "Called with a MergeRequestBodyParams when a body is needed for"
62
            " a merge request.  Callbacks must return a body.  If more"
63
            " than one callback is registered, the output of one callback is"
64
            " provided to the next.", (1, 15, 0), False))
65
66
2520.4.73 by Aaron Bentley
Implement new merge directive format
67
class _BaseMergeDirective(object):
68
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
69
    hooks = MergeDirectiveHooks()
70
2520.4.73 by Aaron Bentley
Implement new merge directive format
71
    def __init__(self, revision_id, testament_sha1, time, timezone,
72
                 target_branch, patch=None, source_branch=None, message=None,
73
                 bundle=None):
74
        """Constructor.
75
76
        :param revision_id: The revision to merge
77
        :param testament_sha1: The sha1 of the testament of the revision to
78
            merge.
79
        :param time: The current POSIX timestamp time
80
        :param timezone: The timezone offset
81
        :param target_branch: The branch to apply the merge to
82
        :param patch: The text of a diff or bundle
83
        :param source_branch: A public location to merge the revision from
84
        :param message: The message to use when committing this merge
85
        """
86
        self.revision_id = revision_id
87
        self.testament_sha1 = testament_sha1
88
        self.time = time
89
        self.timezone = timezone
90
        self.target_branch = target_branch
91
        self.patch = patch
92
        self.source_branch = source_branch
93
        self.message = message
94
2520.4.105 by Aaron Bentley
Implement patch verification
95
    def _to_lines(self, base_revision=False):
2520.4.73 by Aaron Bentley
Implement new merge directive format
96
        """Serialize as a list of lines
97
98
        :return: a list of lines
99
        """
100
        time_str = timestamp.format_patch_date(self.time, self.timezone)
101
        stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str,
102
                            target_branch=self.target_branch,
103
                            testament_sha1=self.testament_sha1)
104
        for key in ('source_branch', 'message'):
105
            if self.__dict__[key] is not None:
106
                stanza.add(key, self.__dict__[key])
2520.4.105 by Aaron Bentley
Implement patch verification
107
        if base_revision:
108
            stanza.add('base_revision_id', self.base_revision_id)
2520.4.73 by Aaron Bentley
Implement new merge directive format
109
        lines = ['# ' + self._format_string + '\n']
110
        lines.extend(rio.to_patch_lines(stanza))
111
        lines.append('# \n')
112
        return lines
113
114
    @classmethod
115
    def from_objects(klass, repository, revision_id, time, timezone,
116
                 target_branch, patch_type='bundle',
117
                 local_target_branch=None, public_branch=None, message=None):
118
        """Generate a merge directive from various objects
119
120
        :param repository: The repository containing the revision
121
        :param revision_id: The revision to merge
122
        :param time: The POSIX timestamp of the date the request was issued.
123
        :param timezone: The timezone of the request
124
        :param target_branch: The url of the branch to merge into
125
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
126
            patch desired.
127
        :param local_target_branch: a local copy of the target branch
128
        :param public_branch: location of a public branch containing the target
129
            revision.
130
        :param message: Message to use when committing the merge
131
        :return: The merge directive
132
133
        The public branch is always used if supplied.  If the patch_type is
134
        not 'bundle', the public branch must be supplied, and will be verified.
135
136
        If the message is not supplied, the message from revision_id will be
137
        used for the commit.
138
        """
139
        t_revision_id = revision_id
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
140
        if revision_id == _mod_revision.NULL_REVISION:
2520.4.73 by Aaron Bentley
Implement new merge directive format
141
            t_revision_id = None
142
        t = testament.StrictTestament3.from_revision(repository, t_revision_id)
143
        submit_branch = _mod_branch.Branch.open(target_branch)
144
        if submit_branch.get_public_branch() is not None:
145
            target_branch = submit_branch.get_public_branch()
146
        if patch_type is None:
147
            patch = None
148
        else:
149
            submit_revision_id = submit_branch.last_revision()
150
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
151
            repository.fetch(submit_branch.repository, submit_revision_id)
152
            graph = repository.get_graph()
153
            ancestor_id = graph.find_unique_lca(revision_id,
154
                                                submit_revision_id)
155
            type_handler = {'bundle': klass._generate_bundle,
156
                            'diff': klass._generate_diff,
157
                            None: lambda x, y, z: None }
158
            patch = type_handler[patch_type](repository, revision_id,
159
                                             ancestor_id)
160
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
161
        if public_branch is not None and patch_type != 'bundle':
162
            public_branch_obj = _mod_branch.Branch.open(public_branch)
163
            if not public_branch_obj.repository.has_revision(revision_id):
164
                raise errors.PublicBranchOutOfDate(public_branch,
165
                                                   revision_id)
2520.4.73 by Aaron Bentley
Implement new merge directive format
166
167
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
168
            patch, patch_type, public_branch, message)
169
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
170
    def get_disk_name(self, branch):
171
        """Generate a suitable basename for storing this directive on disk
172
173
        :param branch: The Branch this merge directive was generated fro
174
        :return: A string
175
        """
176
        revno, revision_id = branch.last_revision_info()
177
        if self.revision_id == revision_id:
3251.2.2 by Aaron Bentley
Fix bug in last revno handling
178
            revno = [revno]
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
179
        else:
180
            revno = branch.get_revision_id_to_revno_map().get(self.revision_id,
181
                ['merge'])
3449.4.1 by Lukáš Lalinský
Sanitize branch nick before using it as an attachment filename in ``bzr send``
182
        nick = re.sub('(\W+)', '-', branch.nick).strip('-')
183
        return '%s-%s' % (nick, '.'.join(str(n) for n in revno))
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
184
2520.4.73 by Aaron Bentley
Implement new merge directive format
185
    @staticmethod
186
    def _generate_diff(repository, revision_id, ancestor_id):
187
        tree_1 = repository.revision_tree(ancestor_id)
188
        tree_2 = repository.revision_tree(revision_id)
189
        s = StringIO()
190
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
191
        return s.getvalue()
192
193
    @staticmethod
194
    def _generate_bundle(repository, revision_id, ancestor_id):
195
        s = StringIO()
196
        bundle_serializer.write_bundle(repository, revision_id,
197
                                       ancestor_id, s)
198
        return s.getvalue()
199
2520.4.80 by Aaron Bentley
Improve merge directive tests
200
    def to_signed(self, branch):
201
        """Serialize as a signed string.
202
203
        :param branch: The source branch, to get the signing strategy
204
        :return: a string
205
        """
206
        my_gpg = gpg.GPGStrategy(branch.get_config())
207
        return my_gpg.sign(''.join(self.to_lines()))
208
209
    def to_email(self, mail_to, branch, sign=False):
210
        """Serialize as an email message.
211
212
        :param mail_to: The address to mail the message to
213
        :param branch: The source branch, to get the signing strategy and
214
            source email address
215
        :param sign: If True, gpg-sign the email
216
        :return: an email message
217
        """
218
        mail_from = branch.get_config().username()
219
        if self.message is not None:
2625.6.2 by Adeodato Simó
Merge bzr.dev, resolving conflicts and updating test_merge_directive.py.
220
            subject = self.message
2520.4.80 by Aaron Bentley
Improve merge directive tests
221
        else:
222
            revision = branch.repository.get_revision(self.revision_id)
2625.6.2 by Adeodato Simó
Merge bzr.dev, resolving conflicts and updating test_merge_directive.py.
223
            subject = revision.message
2520.4.80 by Aaron Bentley
Improve merge directive tests
224
        if sign:
225
            body = self.to_signed(branch)
226
        else:
227
            body = ''.join(self.to_lines())
2625.6.2 by Adeodato Simó
Merge bzr.dev, resolving conflicts and updating test_merge_directive.py.
228
        message = EmailMessage(mail_from, mail_to, subject, body)
2520.4.80 by Aaron Bentley
Improve merge directive tests
229
        return message
230
231
    def install_revisions(self, target_repo):
232
        """Install revisions and return the target revision"""
233
        if not target_repo.has_revision(self.revision_id):
234
            if self.patch_type == 'bundle':
235
                info = bundle_serializer.read_bundle(
236
                    StringIO(self.get_raw_bundle()))
237
                # We don't use the bundle's target revision, because
238
                # MergeDirective.revision_id is authoritative.
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
239
                try:
240
                    info.install_revisions(target_repo, stream_input=False)
241
                except errors.RevisionNotPresent:
242
                    # At least one dependency isn't present.  Try installing
243
                    # missing revisions from the submit branch
3535.8.1 by James Westby
Handle something that isn't a branch being specified in target_branch.
244
                    try:
245
                        submit_branch = \
246
                            _mod_branch.Branch.open(self.target_branch)
247
                    except errors.NotBranchError:
248
                        raise errors.TargetNotBranch(self.target_branch)
1551.19.20 by Aaron Bentley
Updates from review
249
                    missing_revisions = []
250
                    bundle_revisions = set(r.revision_id for r in
251
                                           info.real_revisions)
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
252
                    for revision in info.real_revisions:
253
                        for parent_id in revision.parent_ids:
1551.19.20 by Aaron Bentley
Updates from review
254
                            if (parent_id not in bundle_revisions and
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
255
                                not target_repo.has_revision(parent_id)):
256
                                missing_revisions.append(parent_id)
1551.19.20 by Aaron Bentley
Updates from review
257
                    # reverse missing revisions to try to get heads first
258
                    unique_missing = []
259
                    unique_missing_set = set()
260
                    for revision in reversed(missing_revisions):
261
                        if revision in unique_missing_set:
262
                            continue
263
                        unique_missing.append(revision)
264
                        unique_missing_set.add(revision)
265
                    for missing_revision in unique_missing:
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
266
                        target_repo.fetch(submit_branch.repository,
267
                                          missing_revision)
268
                    info.install_revisions(target_repo, stream_input=False)
2520.4.80 by Aaron Bentley
Improve merge directive tests
269
            else:
270
                source_branch = _mod_branch.Branch.open(self.source_branch)
271
                target_repo.fetch(source_branch.repository, self.revision_id)
272
        return self.revision_id
273
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
274
    def compose_merge_request(self, mail_client, to, body, branch, tree=None):
4098.5.17 by Aaron Bentley
cleanup
275
        """Compose a request to merge this directive.
276
277
        :param mail_client: The mail client to use for composing this request.
278
        :param to: The address to compose the request to.
279
        :param branch: The Branch that was used to produce this directive.
280
        :param tree: The Tree (if any) for the Branch used to produce this
281
            directive.
282
        """
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
283
        basename = self.get_disk_name(branch)
284
        subject = '[MERGE] '
285
        if self.message is not None:
286
            subject += self.message
287
        else:
288
            revision = branch.repository.get_revision(self.revision_id)
289
            subject += revision.get_summary()
4098.5.18 by Aaron Bentley
Gracefully handle mail clients that don't support bodies.
290
        if getattr(mail_client, 'supports_body', False):
291
            orig_body = body
292
            for hook in self.hooks['merge_request_body']:
293
                params = MergeRequestBodyParams(body, orig_body, self,
294
                                                to, basename, subject, branch,
295
                                                tree)
296
                body = hook(params)
297
        elif len(self.hooks['merge_request_body']) > 0:
298
            trace.warning('Cannot run merge_request_body hooks because mail'
299
                          ' client %s does not support message bodies.',
300
                        mail_client.__class__.__name__)
4098.5.16 by Aaron Bentley
Move hook to MergeDirective, implement MergeDirective.compose_merge_request.
301
        mail_client.compose_merge_request(to, subject,
302
                                          ''.join(self.to_lines()),
303
                                          basename, body)
304
2520.4.73 by Aaron Bentley
Implement new merge directive format
305
306
class MergeDirective(_BaseMergeDirective):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
307
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
308
    """A request to perform a merge into a branch.
309
310
    Designed to be serialized and mailed.  It provides all the information
311
    needed to perform a merge automatically, by providing at minimum a revision
312
    bundle or the location of a branch.
313
314
    The serialization format is robust against certain common forms of
315
    deterioration caused by mailing.
316
317
    The format is also designed to be patch-compatible.  If the directive
318
    includes a diff or revision bundle, it should be possible to apply it
319
    directly using the standard patch program.
320
    """
321
1551.12.45 by Aaron Bentley
Change format marker to not experimental
322
    _format_string = 'Bazaar merge directive format 1'
1551.12.12 by Aaron Bentley
Add format header
323
1551.12.4 by Aaron Bentley
Add failing test
324
    def __init__(self, revision_id, testament_sha1, time, timezone,
1551.12.13 by Aaron Bentley
Rename fields
325
                 target_branch, patch=None, patch_type=None,
2520.4.73 by Aaron Bentley
Implement new merge directive format
326
                 source_branch=None, message=None, bundle=None):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
327
        """Constructor.
328
329
        :param revision_id: The revision to merge
330
        :param testament_sha1: The sha1 of the testament of the revision to
331
            merge.
332
        :param time: The current POSIX timestamp time
333
        :param timezone: The timezone offset
334
        :param target_branch: The branch to apply the merge to
335
        :param patch: The text of a diff or bundle
336
        :param patch_type: None, "diff" or "bundle", depending on the contents
337
            of patch
338
        :param source_branch: A public location to merge the revision from
339
        :param message: The message to use when committing this merge
340
        """
2520.4.73 by Aaron Bentley
Implement new merge directive format
341
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
342
            timezone, target_branch, patch, source_branch, message)
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
343
        if patch_type not in (None, 'diff', 'bundle'):
344
            raise ValueError(patch_type)
1551.12.13 by Aaron Bentley
Rename fields
345
        if patch_type != 'bundle' and source_branch is None:
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
346
            raise errors.NoMergeSource()
347
        if patch_type is not None and patch is None:
348
            raise errors.PatchMissing(patch_type)
349
        self.patch_type = patch_type
2520.4.73 by Aaron Bentley
Implement new merge directive format
350
351
    def clear_payload(self):
352
        self.patch = None
353
        self.patch_type = None
354
2520.4.80 by Aaron Bentley
Improve merge directive tests
355
    def get_raw_bundle(self):
356
        return self.bundle
357
2520.4.73 by Aaron Bentley
Implement new merge directive format
358
    def _bundle(self):
359
        if self.patch_type == 'bundle':
360
            return self.patch
361
        else:
362
            return None
363
364
    bundle = property(_bundle)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
365
1551.12.12 by Aaron Bentley
Add format header
366
    @classmethod
367
    def from_lines(klass, lines):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
368
        """Deserialize a MergeRequest from an iterable of lines
369
370
        :param lines: An iterable of lines
371
        :return: a MergeRequest
372
        """
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
373
        line_iter = iter(lines)
4792.7.3 by Martin
MergeDirective.from_lines claims to want an iterable but currently requires a list, rewrite so it really wants an iterable
374
        firstline = ""
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
375
        for line in line_iter:
2520.4.73 by Aaron Bentley
Implement new merge directive format
376
            if line.startswith('# Bazaar merge directive format '):
4792.7.3 by Martin
MergeDirective.from_lines claims to want an iterable but currently requires a list, rewrite so it really wants an iterable
377
                return _format_registry.get(line[2:].rstrip())._from_lines(
378
                    line_iter)
379
            firstline = firstline or line.strip()
380
        raise errors.NotAMergeDirective(firstline)
2520.4.73 by Aaron Bentley
Implement new merge directive format
381
382
    @classmethod
383
    def _from_lines(klass, line_iter):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
384
        stanza = rio.read_patch_stanza(line_iter)
385
        patch_lines = list(line_iter)
386
        if len(patch_lines) == 0:
387
            patch = None
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
388
            patch_type = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
389
        else:
390
            patch = ''.join(patch_lines)
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
391
            try:
392
                bundle_serializer.read_bundle(StringIO(patch))
1551.15.29 by Aaron Bentley
Make merge directives robust against broken bundles
393
            except (errors.NotABundle, errors.BundleNotSupported,
394
                    errors.BadBundle):
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
395
                patch_type = 'diff'
396
            else:
397
                patch_type = 'bundle'
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
398
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
399
        kwargs = {}
1551.12.13 by Aaron Bentley
Rename fields
400
        for key in ('revision_id', 'testament_sha1', 'target_branch',
1551.12.26 by Aaron Bentley
Get email working, with optional message
401
                    'source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
402
            try:
403
                kwargs[key] = stanza.get(key)
404
            except KeyError:
405
                pass
1551.12.54 by Aaron Bentley
Decoded revision ids are utf-8
406
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
407
        return MergeDirective(time=time, timezone=timezone,
408
                              patch_type=patch_type, patch=patch, **kwargs)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
409
410
    def to_lines(self):
2520.4.73 by Aaron Bentley
Implement new merge directive format
411
        lines = self._to_lines()
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
412
        if self.patch is not None:
413
            lines.extend(self.patch.splitlines(True))
414
        return lines
415
2520.4.73 by Aaron Bentley
Implement new merge directive format
416
    @staticmethod
417
    def _generate_bundle(repository, revision_id, ancestor_id):
418
        s = StringIO()
419
        bundle_serializer.write_bundle(repository, revision_id,
420
                                       ancestor_id, s, '0.9')
421
        return s.getvalue()
422
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
423
    def get_merge_request(self, repository):
424
        """Provide data for performing a merge
425
426
        Returns suggested base, suggested target, and patch verification status
427
        """
428
        return None, self.revision_id, 'inapplicable'
429
2520.4.76 by Aaron Bentley
Move base64-encoding into merge directives
430
2520.4.73 by Aaron Bentley
Implement new merge directive format
431
class MergeDirective2(_BaseMergeDirective):
432
2687.2.2 by Martin Pool
Fix up other references to 0.19
433
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
2520.4.73 by Aaron Bentley
Implement new merge directive format
434
435
    def __init__(self, revision_id, testament_sha1, time, timezone,
436
                 target_branch, patch=None, source_branch=None, message=None,
2520.4.105 by Aaron Bentley
Implement patch verification
437
                 bundle=None, base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
438
        if source_branch is None and bundle is None:
439
            raise errors.NoMergeSource()
440
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
441
            timezone, target_branch, patch, source_branch, message)
442
        self.bundle = bundle
2520.4.105 by Aaron Bentley
Implement patch verification
443
        self.base_revision_id = base_revision_id
2520.4.73 by Aaron Bentley
Implement new merge directive format
444
445
    def _patch_type(self):
446
        if self.bundle is not None:
447
            return 'bundle'
448
        elif self.patch is not None:
449
            return 'diff'
450
        else:
451
            return None
452
453
    patch_type = property(_patch_type)
454
455
    def clear_payload(self):
456
        self.patch = None
457
        self.bundle = None
458
2520.4.80 by Aaron Bentley
Improve merge directive tests
459
    def get_raw_bundle(self):
460
        if self.bundle is None:
461
            return None
462
        else:
463
            return self.bundle.decode('base-64')
464
2520.4.73 by Aaron Bentley
Implement new merge directive format
465
    @classmethod
466
    def _from_lines(klass, line_iter):
467
        stanza = rio.read_patch_stanza(line_iter)
468
        patch = None
469
        bundle = None
470
        try:
471
            start = line_iter.next()
472
        except StopIteration:
473
            pass
474
        else:
475
            if start.startswith('# Begin patch'):
476
                patch_lines = []
477
                for line in line_iter:
478
                    if line.startswith('# Begin bundle'):
479
                        start = line
480
                        break
481
                    patch_lines.append(line)
482
                else:
483
                    start = None
484
                patch = ''.join(patch_lines)
485
            if start is not None:
486
                if start.startswith('# Begin bundle'):
487
                    bundle = ''.join(line_iter)
488
                else:
489
                    raise errors.IllegalMergeDirectivePayload(start)
490
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
491
        kwargs = {}
492
        for key in ('revision_id', 'testament_sha1', 'target_branch',
2520.4.105 by Aaron Bentley
Implement patch verification
493
                    'source_branch', 'message', 'base_revision_id'):
2520.4.73 by Aaron Bentley
Implement new merge directive format
494
            try:
495
                kwargs[key] = stanza.get(key)
496
            except KeyError:
497
                pass
498
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
2520.4.105 by Aaron Bentley
Implement patch verification
499
        kwargs['base_revision_id'] =\
500
            kwargs['base_revision_id'].encode('utf-8')
2520.4.73 by Aaron Bentley
Implement new merge directive format
501
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
502
                     **kwargs)
503
504
    def to_lines(self):
2520.4.105 by Aaron Bentley
Implement patch verification
505
        lines = self._to_lines(base_revision=True)
2520.4.73 by Aaron Bentley
Implement new merge directive format
506
        if self.patch is not None:
507
            lines.append('# Begin patch\n')
508
            lines.extend(self.patch.splitlines(True))
509
        if self.bundle is not None:
510
            lines.append('# Begin bundle\n')
511
            lines.extend(self.bundle.splitlines(True))
512
        return lines
513
514
    @classmethod
515
    def from_objects(klass, repository, revision_id, time, timezone,
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
516
                 target_branch, include_patch=True, include_bundle=True,
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
517
                 local_target_branch=None, public_branch=None, message=None,
518
                 base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
519
        """Generate a merge directive from various objects
520
521
        :param repository: The repository containing the revision
522
        :param revision_id: The revision to merge
523
        :param time: The POSIX timestamp of the date the request was issued.
524
        :param timezone: The timezone of the request
525
        :param target_branch: The url of the branch to merge into
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
526
        :param include_patch: If true, include a preview patch
527
        :param include_bundle: If true, include a bundle
2520.4.73 by Aaron Bentley
Implement new merge directive format
528
        :param local_target_branch: a local copy of the target branch
529
        :param public_branch: location of a public branch containing the target
530
            revision.
531
        :param message: Message to use when committing the merge
532
        :return: The merge directive
533
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
534
        The public branch is always used if supplied.  If no bundle is
535
        included, the public branch must be supplied, and will be verified.
2520.4.73 by Aaron Bentley
Implement new merge directive format
536
537
        If the message is not supplied, the message from revision_id will be
538
        used for the commit.
539
        """
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
540
        locked = []
541
        try:
542
            repository.lock_write()
543
            locked.append(repository)
544
            t_revision_id = revision_id
545
            if revision_id == 'null:':
546
                t_revision_id = None
547
            t = testament.StrictTestament3.from_revision(repository,
548
                t_revision_id)
549
            submit_branch = _mod_branch.Branch.open(target_branch)
550
            submit_branch.lock_read()
551
            locked.append(submit_branch)
552
            if submit_branch.get_public_branch() is not None:
553
                target_branch = submit_branch.get_public_branch()
2520.4.105 by Aaron Bentley
Implement patch verification
554
            submit_revision_id = submit_branch.last_revision()
555
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
556
            graph = repository.get_graph(submit_branch.repository)
557
            ancestor_id = graph.find_unique_lca(revision_id,
558
                                                submit_revision_id)
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
559
            if base_revision_id is None:
560
                base_revision_id = ancestor_id
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
561
            if (include_patch, include_bundle) != (False, False):
562
                repository.fetch(submit_branch.repository, submit_revision_id)
563
            if include_patch:
564
                patch = klass._generate_diff(repository, revision_id,
565
                                             base_revision_id)
566
            else:
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
567
                patch = None
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
568
569
            if include_bundle:
570
                bundle = klass._generate_bundle(repository, revision_id,
571
                    ancestor_id).encode('base-64')
572
            else:
2520.4.73 by Aaron Bentley
Implement new merge directive format
573
                bundle = None
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
574
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
575
            if public_branch is not None and not include_bundle:
576
                public_branch_obj = _mod_branch.Branch.open(public_branch)
577
                public_branch_obj.lock_read()
578
                locked.append(public_branch_obj)
579
                if not public_branch_obj.repository.has_revision(
580
                    revision_id):
581
                    raise errors.PublicBranchOutOfDate(public_branch,
582
                                                       revision_id)
4634.90.3 by Andrew Bennetts
Fix other bugs revealed by clearing chk_map page cache during blackbox tests.
583
            testament_sha1 = t.as_sha1()
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
584
        finally:
585
            for entry in reversed(locked):
586
                entry.unlock()
4634.90.3 by Andrew Bennetts
Fix other bugs revealed by clearing chk_map page cache during blackbox tests.
587
        return klass(revision_id, testament_sha1, time, timezone,
588
            target_branch, patch, public_branch, message, bundle,
589
            base_revision_id)
2520.4.105 by Aaron Bentley
Implement patch verification
590
591
    def _verify_patch(self, repository):
592
        calculated_patch = self._generate_diff(repository, self.revision_id,
593
                                               self.base_revision_id)
594
        # Convert line-endings to UNIX
595
        stored_patch = re.sub('\r\n?', '\n', self.patch)
2520.7.2 by Aaron Bentley
Restore patch verification for CR, CRLF files
596
        calculated_patch = re.sub('\r\n?', '\n', calculated_patch)
2520.4.105 by Aaron Bentley
Implement patch verification
597
        # Strip trailing whitespace
598
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
599
        stored_patch = re.sub(' *\n', '\n', stored_patch)
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
600
        return (calculated_patch == stored_patch)
601
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
602
    def get_merge_request(self, repository):
603
        """Provide data for performing a merge
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
604
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
605
        Returns suggested base, suggested target, and patch verification status
606
        """
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
607
        verified = self._maybe_verify(repository)
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
608
        return self.base_revision_id, self.revision_id, verified
2520.4.105 by Aaron Bentley
Implement patch verification
609
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
610
    def _maybe_verify(self, repository):
611
        if self.patch is not None:
612
            if self._verify_patch(repository):
613
                return 'verified'
614
            else:
615
                return 'failed'
616
        else:
617
            return 'inapplicable'
618
2520.4.73 by Aaron Bentley
Implement new merge directive format
619
620
class MergeDirectiveFormatRegistry(registry.Registry):
621
2694.1.1 by Aaron Bentley
Restore support for Merge directive 2 / 0.19
622
    def register(self, directive, format_string=None):
623
        if format_string is None:
2694.1.3 by Aaron Bentley
Fix whitespace
624
            format_string = directive._format_string
2694.1.1 by Aaron Bentley
Restore support for Merge directive 2 / 0.19
625
        registry.Registry.register(self, format_string, directive)
2520.4.73 by Aaron Bentley
Implement new merge directive format
626
627
628
_format_registry = MergeDirectiveFormatRegistry()
629
_format_registry.register(MergeDirective)
630
_format_registry.register(MergeDirective2)
4241.14.12 by Vincent Ladeuil
Far too many modifications for a single commit, need to restart.
631
# 0.19 never existed.  It got renamed to 0.90.  But by that point, there were
632
# already merge directives in the wild that used 0.19. Registering with the old
633
# format string to retain compatibility with those merge directives.
2694.1.1 by Aaron Bentley
Restore support for Merge directive 2 / 0.19
634
_format_registry.register(MergeDirective2,
635
                          'Bazaar merge directive format 2 (Bazaar 0.19)')