~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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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,
2520.4.73 by Aaron Bentley
Implement new merge directive format
26
    registry,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
27
    revision as _mod_revision,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
28
    rio,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
29
    testament,
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
30
    timestamp,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
31
    )
1551.14.4 by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables'
32
from bzrlib.bundle import (
33
    serializer as bundle_serializer,
34
    )
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
35
from bzrlib.email_message import EmailMessage
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
36
37
2520.4.73 by Aaron Bentley
Implement new merge directive format
38
class _BaseMergeDirective(object):
39
40
    def __init__(self, revision_id, testament_sha1, time, timezone,
41
                 target_branch, patch=None, source_branch=None, message=None,
42
                 bundle=None):
43
        """Constructor.
44
45
        :param revision_id: The revision to merge
46
        :param testament_sha1: The sha1 of the testament of the revision to
47
            merge.
48
        :param time: The current POSIX timestamp time
49
        :param timezone: The timezone offset
50
        :param target_branch: The branch to apply the merge to
51
        :param patch: The text of a diff or bundle
52
        :param source_branch: A public location to merge the revision from
53
        :param message: The message to use when committing this merge
54
        """
55
        self.revision_id = revision_id
56
        self.testament_sha1 = testament_sha1
57
        self.time = time
58
        self.timezone = timezone
59
        self.target_branch = target_branch
60
        self.patch = patch
61
        self.source_branch = source_branch
62
        self.message = message
63
2520.4.105 by Aaron Bentley
Implement patch verification
64
    def _to_lines(self, base_revision=False):
2520.4.73 by Aaron Bentley
Implement new merge directive format
65
        """Serialize as a list of lines
66
67
        :return: a list of lines
68
        """
69
        time_str = timestamp.format_patch_date(self.time, self.timezone)
70
        stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str,
71
                            target_branch=self.target_branch,
72
                            testament_sha1=self.testament_sha1)
73
        for key in ('source_branch', 'message'):
74
            if self.__dict__[key] is not None:
75
                stanza.add(key, self.__dict__[key])
2520.4.105 by Aaron Bentley
Implement patch verification
76
        if base_revision:
77
            stanza.add('base_revision_id', self.base_revision_id)
2520.4.73 by Aaron Bentley
Implement new merge directive format
78
        lines = ['# ' + self._format_string + '\n']
79
        lines.extend(rio.to_patch_lines(stanza))
80
        lines.append('# \n')
81
        return lines
82
83
    @classmethod
84
    def from_objects(klass, repository, revision_id, time, timezone,
85
                 target_branch, patch_type='bundle',
86
                 local_target_branch=None, public_branch=None, message=None):
87
        """Generate a merge directive from various objects
88
89
        :param repository: The repository containing the revision
90
        :param revision_id: The revision to merge
91
        :param time: The POSIX timestamp of the date the request was issued.
92
        :param timezone: The timezone of the request
93
        :param target_branch: The url of the branch to merge into
94
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
95
            patch desired.
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.
99
        :param message: Message to use when committing the merge
100
        :return: The merge directive
101
102
        The public branch is always used if supplied.  If the patch_type is
103
        not 'bundle', the public branch must be supplied, and will be verified.
104
105
        If the message is not supplied, the message from revision_id will be
106
        used for the commit.
107
        """
108
        t_revision_id = revision_id
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
109
        if revision_id == _mod_revision.NULL_REVISION:
2520.4.73 by Aaron Bentley
Implement new merge directive format
110
            t_revision_id = None
111
        t = testament.StrictTestament3.from_revision(repository, t_revision_id)
112
        submit_branch = _mod_branch.Branch.open(target_branch)
113
        if submit_branch.get_public_branch() is not None:
114
            target_branch = submit_branch.get_public_branch()
115
        if patch_type is None:
116
            patch = None
117
        else:
118
            submit_revision_id = submit_branch.last_revision()
119
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
120
            repository.fetch(submit_branch.repository, submit_revision_id)
121
            graph = repository.get_graph()
122
            ancestor_id = graph.find_unique_lca(revision_id,
123
                                                submit_revision_id)
124
            type_handler = {'bundle': klass._generate_bundle,
125
                            'diff': klass._generate_diff,
126
                            None: lambda x, y, z: None }
127
            patch = type_handler[patch_type](repository, revision_id,
128
                                             ancestor_id)
129
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
130
        if public_branch is not None and patch_type != 'bundle':
131
            public_branch_obj = _mod_branch.Branch.open(public_branch)
132
            if not public_branch_obj.repository.has_revision(revision_id):
133
                raise errors.PublicBranchOutOfDate(public_branch,
134
                                                   revision_id)
2520.4.73 by Aaron Bentley
Implement new merge directive format
135
136
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
137
            patch, patch_type, public_branch, message)
138
139
    @staticmethod
140
    def _generate_diff(repository, revision_id, ancestor_id):
141
        tree_1 = repository.revision_tree(ancestor_id)
142
        tree_2 = repository.revision_tree(revision_id)
143
        s = StringIO()
144
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
145
        return s.getvalue()
146
147
    @staticmethod
148
    def _generate_bundle(repository, revision_id, ancestor_id):
149
        s = StringIO()
150
        bundle_serializer.write_bundle(repository, revision_id,
151
                                       ancestor_id, s)
152
        return s.getvalue()
153
2520.4.80 by Aaron Bentley
Improve merge directive tests
154
    def to_signed(self, branch):
155
        """Serialize as a signed string.
156
157
        :param branch: The source branch, to get the signing strategy
158
        :return: a string
159
        """
160
        my_gpg = gpg.GPGStrategy(branch.get_config())
161
        return my_gpg.sign(''.join(self.to_lines()))
162
163
    def to_email(self, mail_to, branch, sign=False):
164
        """Serialize as an email message.
165
166
        :param mail_to: The address to mail the message to
167
        :param branch: The source branch, to get the signing strategy and
168
            source email address
169
        :param sign: If True, gpg-sign the email
170
        :return: an email message
171
        """
172
        mail_from = branch.get_config().username()
173
        if self.message is not None:
2625.6.2 by Adeodato Simó
Merge bzr.dev, resolving conflicts and updating test_merge_directive.py.
174
            subject = self.message
2520.4.80 by Aaron Bentley
Improve merge directive tests
175
        else:
176
            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.
177
            subject = revision.message
2520.4.80 by Aaron Bentley
Improve merge directive tests
178
        if sign:
179
            body = self.to_signed(branch)
180
        else:
181
            body = ''.join(self.to_lines())
2625.6.2 by Adeodato Simó
Merge bzr.dev, resolving conflicts and updating test_merge_directive.py.
182
        message = EmailMessage(mail_from, mail_to, subject, body)
2520.4.80 by Aaron Bentley
Improve merge directive tests
183
        return message
184
185
    def install_revisions(self, target_repo):
186
        """Install revisions and return the target revision"""
187
        if not target_repo.has_revision(self.revision_id):
188
            if self.patch_type == 'bundle':
189
                info = bundle_serializer.read_bundle(
190
                    StringIO(self.get_raw_bundle()))
191
                # We don't use the bundle's target revision, because
192
                # MergeDirective.revision_id is authoritative.
193
                info.install_revisions(target_repo)
194
            else:
195
                source_branch = _mod_branch.Branch.open(self.source_branch)
196
                target_repo.fetch(source_branch.repository, self.revision_id)
197
        return self.revision_id
198
2520.4.73 by Aaron Bentley
Implement new merge directive format
199
200
class MergeDirective(_BaseMergeDirective):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
201
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
202
    """A request to perform a merge into a branch.
203
204
    Designed to be serialized and mailed.  It provides all the information
205
    needed to perform a merge automatically, by providing at minimum a revision
206
    bundle or the location of a branch.
207
208
    The serialization format is robust against certain common forms of
209
    deterioration caused by mailing.
210
211
    The format is also designed to be patch-compatible.  If the directive
212
    includes a diff or revision bundle, it should be possible to apply it
213
    directly using the standard patch program.
214
    """
215
1551.12.45 by Aaron Bentley
Change format marker to not experimental
216
    _format_string = 'Bazaar merge directive format 1'
1551.12.12 by Aaron Bentley
Add format header
217
1551.12.4 by Aaron Bentley
Add failing test
218
    def __init__(self, revision_id, testament_sha1, time, timezone,
1551.12.13 by Aaron Bentley
Rename fields
219
                 target_branch, patch=None, patch_type=None,
2520.4.73 by Aaron Bentley
Implement new merge directive format
220
                 source_branch=None, message=None, bundle=None):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
221
        """Constructor.
222
223
        :param revision_id: The revision to merge
224
        :param testament_sha1: The sha1 of the testament of the revision to
225
            merge.
226
        :param time: The current POSIX timestamp time
227
        :param timezone: The timezone offset
228
        :param target_branch: The branch to apply the merge to
229
        :param patch: The text of a diff or bundle
230
        :param patch_type: None, "diff" or "bundle", depending on the contents
231
            of patch
232
        :param source_branch: A public location to merge the revision from
233
        :param message: The message to use when committing this merge
234
        """
2520.4.73 by Aaron Bentley
Implement new merge directive format
235
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
236
            timezone, target_branch, patch, source_branch, message)
237
        assert patch_type in (None, 'diff', 'bundle'), patch_type
1551.12.13 by Aaron Bentley
Rename fields
238
        if patch_type != 'bundle' and source_branch is None:
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
239
            raise errors.NoMergeSource()
240
        if patch_type is not None and patch is None:
241
            raise errors.PatchMissing(patch_type)
242
        self.patch_type = patch_type
2520.4.73 by Aaron Bentley
Implement new merge directive format
243
244
    def clear_payload(self):
245
        self.patch = None
246
        self.patch_type = None
247
2520.4.80 by Aaron Bentley
Improve merge directive tests
248
    def get_raw_bundle(self):
249
        return self.bundle
250
2520.4.73 by Aaron Bentley
Implement new merge directive format
251
    def _bundle(self):
252
        if self.patch_type == 'bundle':
253
            return self.patch
254
        else:
255
            return None
256
257
    bundle = property(_bundle)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
258
1551.12.12 by Aaron Bentley
Add format header
259
    @classmethod
260
    def from_lines(klass, lines):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
261
        """Deserialize a MergeRequest from an iterable of lines
262
263
        :param lines: An iterable of lines
264
        :return: a MergeRequest
265
        """
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
266
        line_iter = iter(lines)
267
        for line in line_iter:
2520.4.73 by Aaron Bentley
Implement new merge directive format
268
            if line.startswith('# Bazaar merge directive format '):
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
269
                break
270
        else:
1551.12.59 by Aaron Bentley
Correctly handle empty merge directive texts
271
            if len(lines) > 0:
272
                raise errors.NotAMergeDirective(lines[0])
273
            else:
274
                raise errors.NotAMergeDirective('')
2520.4.73 by Aaron Bentley
Implement new merge directive format
275
        return _format_registry.get(line[2:].rstrip())._from_lines(line_iter)
276
277
    @classmethod
278
    def _from_lines(klass, line_iter):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
279
        stanza = rio.read_patch_stanza(line_iter)
280
        patch_lines = list(line_iter)
281
        if len(patch_lines) == 0:
282
            patch = None
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
283
            patch_type = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
284
        else:
285
            patch = ''.join(patch_lines)
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
286
            try:
287
                bundle_serializer.read_bundle(StringIO(patch))
1551.15.29 by Aaron Bentley
Make merge directives robust against broken bundles
288
            except (errors.NotABundle, errors.BundleNotSupported,
289
                    errors.BadBundle):
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
290
                patch_type = 'diff'
291
            else:
292
                patch_type = 'bundle'
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
293
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
294
        kwargs = {}
1551.12.13 by Aaron Bentley
Rename fields
295
        for key in ('revision_id', 'testament_sha1', 'target_branch',
1551.12.26 by Aaron Bentley
Get email working, with optional message
296
                    'source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
297
            try:
298
                kwargs[key] = stanza.get(key)
299
            except KeyError:
300
                pass
1551.12.54 by Aaron Bentley
Decoded revision ids are utf-8
301
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
302
        return MergeDirective(time=time, timezone=timezone,
303
                              patch_type=patch_type, patch=patch, **kwargs)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
304
305
    def to_lines(self):
2520.4.73 by Aaron Bentley
Implement new merge directive format
306
        lines = self._to_lines()
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
307
        if self.patch is not None:
308
            lines.extend(self.patch.splitlines(True))
309
        return lines
310
2520.4.73 by Aaron Bentley
Implement new merge directive format
311
    @staticmethod
312
    def _generate_bundle(repository, revision_id, ancestor_id):
313
        s = StringIO()
314
        bundle_serializer.write_bundle(repository, revision_id,
315
                                       ancestor_id, s, '0.9')
316
        return s.getvalue()
317
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
318
    def get_merge_request(self, repository):
319
        """Provide data for performing a merge
320
321
        Returns suggested base, suggested target, and patch verification status
322
        """
323
        return None, self.revision_id, 'inapplicable'
324
2520.4.76 by Aaron Bentley
Move base64-encoding into merge directives
325
2520.4.73 by Aaron Bentley
Implement new merge directive format
326
class MergeDirective2(_BaseMergeDirective):
327
2520.4.136 by Aaron Bentley
Fix format strings
328
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.19)'
2520.4.73 by Aaron Bentley
Implement new merge directive format
329
330
    def __init__(self, revision_id, testament_sha1, time, timezone,
331
                 target_branch, patch=None, source_branch=None, message=None,
2520.4.105 by Aaron Bentley
Implement patch verification
332
                 bundle=None, base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
333
        if source_branch is None and bundle is None:
334
            raise errors.NoMergeSource()
335
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
336
            timezone, target_branch, patch, source_branch, message)
337
        self.bundle = bundle
2520.4.105 by Aaron Bentley
Implement patch verification
338
        self.base_revision_id = base_revision_id
2520.4.73 by Aaron Bentley
Implement new merge directive format
339
340
    def _patch_type(self):
341
        if self.bundle is not None:
342
            return 'bundle'
343
        elif self.patch is not None:
344
            return 'diff'
345
        else:
346
            return None
347
348
    patch_type = property(_patch_type)
349
350
    def clear_payload(self):
351
        self.patch = None
352
        self.bundle = None
353
2520.4.80 by Aaron Bentley
Improve merge directive tests
354
    def get_raw_bundle(self):
355
        if self.bundle is None:
356
            return None
357
        else:
358
            return self.bundle.decode('base-64')
359
2520.4.73 by Aaron Bentley
Implement new merge directive format
360
    @classmethod
361
    def _from_lines(klass, line_iter):
362
        stanza = rio.read_patch_stanza(line_iter)
363
        patch = None
364
        bundle = None
365
        try:
366
            start = line_iter.next()
367
        except StopIteration:
368
            pass
369
        else:
370
            if start.startswith('# Begin patch'):
371
                patch_lines = []
372
                for line in line_iter:
373
                    if line.startswith('# Begin bundle'):
374
                        start = line
375
                        break
376
                    patch_lines.append(line)
377
                else:
378
                    start = None
379
                patch = ''.join(patch_lines)
380
            if start is not None:
381
                if start.startswith('# Begin bundle'):
382
                    bundle = ''.join(line_iter)
383
                else:
384
                    raise errors.IllegalMergeDirectivePayload(start)
385
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
386
        kwargs = {}
387
        for key in ('revision_id', 'testament_sha1', 'target_branch',
2520.4.105 by Aaron Bentley
Implement patch verification
388
                    'source_branch', 'message', 'base_revision_id'):
2520.4.73 by Aaron Bentley
Implement new merge directive format
389
            try:
390
                kwargs[key] = stanza.get(key)
391
            except KeyError:
392
                pass
393
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
2520.4.105 by Aaron Bentley
Implement patch verification
394
        kwargs['base_revision_id'] =\
395
            kwargs['base_revision_id'].encode('utf-8')
2520.4.73 by Aaron Bentley
Implement new merge directive format
396
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
397
                     **kwargs)
398
399
    def to_lines(self):
2520.4.105 by Aaron Bentley
Implement patch verification
400
        lines = self._to_lines(base_revision=True)
2520.4.73 by Aaron Bentley
Implement new merge directive format
401
        if self.patch is not None:
402
            lines.append('# Begin patch\n')
403
            lines.extend(self.patch.splitlines(True))
404
        if self.bundle is not None:
405
            lines.append('# Begin bundle\n')
406
            lines.extend(self.bundle.splitlines(True))
407
        return lines
408
409
    @classmethod
410
    def from_objects(klass, repository, revision_id, time, timezone,
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
411
                 target_branch, include_patch=True, include_bundle=True,
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
412
                 local_target_branch=None, public_branch=None, message=None,
413
                 base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
414
        """Generate a merge directive from various objects
415
416
        :param repository: The repository containing the revision
417
        :param revision_id: The revision to merge
418
        :param time: The POSIX timestamp of the date the request was issued.
419
        :param timezone: The timezone of the request
420
        :param target_branch: The url of the branch to merge into
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
421
        :param include_patch: If true, include a preview patch
422
        :param include_bundle: If true, include a bundle
2520.4.73 by Aaron Bentley
Implement new merge directive format
423
        :param local_target_branch: a local copy of the target branch
424
        :param public_branch: location of a public branch containing the target
425
            revision.
426
        :param message: Message to use when committing the merge
427
        :return: The merge directive
428
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
429
        The public branch is always used if supplied.  If no bundle is
430
        included, the public branch must be supplied, and will be verified.
2520.4.73 by Aaron Bentley
Implement new merge directive format
431
432
        If the message is not supplied, the message from revision_id will be
433
        used for the commit.
434
        """
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
435
        locked = []
436
        try:
437
            repository.lock_write()
438
            locked.append(repository)
439
            t_revision_id = revision_id
440
            if revision_id == 'null:':
441
                t_revision_id = None
442
            t = testament.StrictTestament3.from_revision(repository,
443
                t_revision_id)
444
            submit_branch = _mod_branch.Branch.open(target_branch)
445
            submit_branch.lock_read()
446
            locked.append(submit_branch)
447
            if submit_branch.get_public_branch() is not None:
448
                target_branch = submit_branch.get_public_branch()
2520.4.105 by Aaron Bentley
Implement patch verification
449
            submit_revision_id = submit_branch.last_revision()
450
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
451
            graph = repository.get_graph(submit_branch.repository)
452
            ancestor_id = graph.find_unique_lca(revision_id,
453
                                                submit_revision_id)
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
454
            if base_revision_id is None:
455
                base_revision_id = ancestor_id
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
456
            if (include_patch, include_bundle) != (False, False):
457
                repository.fetch(submit_branch.repository, submit_revision_id)
458
            if include_patch:
459
                patch = klass._generate_diff(repository, revision_id,
460
                                             base_revision_id)
461
            else:
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
462
                patch = None
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
463
464
            if include_bundle:
465
                bundle = klass._generate_bundle(repository, revision_id,
466
                    ancestor_id).encode('base-64')
467
            else:
2520.4.73 by Aaron Bentley
Implement new merge directive format
468
                bundle = None
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
469
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
470
            if public_branch is not None and not include_bundle:
471
                public_branch_obj = _mod_branch.Branch.open(public_branch)
472
                public_branch_obj.lock_read()
473
                locked.append(public_branch_obj)
474
                if not public_branch_obj.repository.has_revision(
475
                    revision_id):
476
                    raise errors.PublicBranchOutOfDate(public_branch,
477
                                                       revision_id)
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
478
        finally:
479
            for entry in reversed(locked):
480
                entry.unlock()
2520.4.73 by Aaron Bentley
Implement new merge directive format
481
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
482
            patch, public_branch, message, bundle, base_revision_id)
2520.4.105 by Aaron Bentley
Implement patch verification
483
484
    def _verify_patch(self, repository):
485
        calculated_patch = self._generate_diff(repository, self.revision_id,
486
                                               self.base_revision_id)
487
        # Convert line-endings to UNIX
488
        stored_patch = re.sub('\r\n?', '\n', self.patch)
489
        # Strip trailing whitespace
490
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
491
        stored_patch = re.sub(' *\n', '\n', stored_patch)
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
492
        return (calculated_patch == stored_patch)
493
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
494
    def get_merge_request(self, repository):
495
        """Provide data for performing a merge
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
496
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
497
        Returns suggested base, suggested target, and patch verification status
498
        """
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
499
        verified = self._maybe_verify(repository)
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
500
        return self.base_revision_id, self.revision_id, verified
2520.4.105 by Aaron Bentley
Implement patch verification
501
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
502
    def _maybe_verify(self, repository):
503
        if self.patch is not None:
504
            if self._verify_patch(repository):
505
                return 'verified'
506
            else:
507
                return 'failed'
508
        else:
509
            return 'inapplicable'
510
2520.4.73 by Aaron Bentley
Implement new merge directive format
511
512
class MergeDirectiveFormatRegistry(registry.Registry):
513
514
    def register(self, directive):
515
        registry.Registry.register(self, directive._format_string, directive)
516
517
518
_format_registry = MergeDirectiveFormatRegistry()
519
_format_registry.register(MergeDirective)
520
_format_registry.register(MergeDirective2)