~bzr-pqm/bzr/bzr.dev

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