/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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.26 by Aaron Bentley
Get email working, with optional message
18
from email import Message
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
19
from StringIO import StringIO
2520.4.105 by Aaron Bentley
Implement patch verification
20
import re
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
21
22
from bzrlib import (
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
23
    branch as _mod_branch,
24
    diff,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
25
    errors,
1551.12.16 by Aaron Bentley
Enable signing merge directives
26
    gpg,
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,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
32
    )
1551.14.4 by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables'
33
from bzrlib.bundle import (
34
    serializer as bundle_serializer,
35
    )
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
        message = Message.Message()
174
        message['To'] = mail_to
175
        message['From'] = mail_from
176
        if self.message is not None:
177
            message['Subject'] = self.message
178
        else:
179
            revision = branch.repository.get_revision(self.revision_id)
180
            message['Subject'] = revision.message
181
        if sign:
182
            body = self.to_signed(branch)
183
        else:
184
            body = ''.join(self.to_lines())
185
        message.set_payload(body)
186
        return message
187
188
    def install_revisions(self, target_repo):
189
        """Install revisions and return the target revision"""
190
        if not target_repo.has_revision(self.revision_id):
191
            if self.patch_type == 'bundle':
192
                info = bundle_serializer.read_bundle(
193
                    StringIO(self.get_raw_bundle()))
194
                # We don't use the bundle's target revision, because
195
                # MergeDirective.revision_id is authoritative.
196
                info.install_revisions(target_repo)
197
            else:
198
                source_branch = _mod_branch.Branch.open(self.source_branch)
199
                target_repo.fetch(source_branch.repository, self.revision_id)
200
        return self.revision_id
201
2520.4.73 by Aaron Bentley
Implement new merge directive format
202
203
class MergeDirective(_BaseMergeDirective):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
204
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
205
    """A request to perform a merge into a branch.
206
207
    Designed to be serialized and mailed.  It provides all the information
208
    needed to perform a merge automatically, by providing at minimum a revision
209
    bundle or the location of a branch.
210
211
    The serialization format is robust against certain common forms of
212
    deterioration caused by mailing.
213
214
    The format is also designed to be patch-compatible.  If the directive
215
    includes a diff or revision bundle, it should be possible to apply it
216
    directly using the standard patch program.
217
    """
218
1551.12.45 by Aaron Bentley
Change format marker to not experimental
219
    _format_string = 'Bazaar merge directive format 1'
1551.12.12 by Aaron Bentley
Add format header
220
1551.12.4 by Aaron Bentley
Add failing test
221
    def __init__(self, revision_id, testament_sha1, time, timezone,
1551.12.13 by Aaron Bentley
Rename fields
222
                 target_branch, patch=None, patch_type=None,
2520.4.73 by Aaron Bentley
Implement new merge directive format
223
                 source_branch=None, message=None, bundle=None):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
224
        """Constructor.
225
226
        :param revision_id: The revision to merge
227
        :param testament_sha1: The sha1 of the testament of the revision to
228
            merge.
229
        :param time: The current POSIX timestamp time
230
        :param timezone: The timezone offset
231
        :param target_branch: The branch to apply the merge to
232
        :param patch: The text of a diff or bundle
233
        :param patch_type: None, "diff" or "bundle", depending on the contents
234
            of patch
235
        :param source_branch: A public location to merge the revision from
236
        :param message: The message to use when committing this merge
237
        """
2520.4.73 by Aaron Bentley
Implement new merge directive format
238
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
239
            timezone, target_branch, patch, source_branch, message)
240
        assert patch_type in (None, 'diff', 'bundle'), patch_type
1551.12.13 by Aaron Bentley
Rename fields
241
        if patch_type != 'bundle' and source_branch is None:
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
242
            raise errors.NoMergeSource()
243
        if patch_type is not None and patch is None:
244
            raise errors.PatchMissing(patch_type)
245
        self.patch_type = patch_type
2520.4.73 by Aaron Bentley
Implement new merge directive format
246
247
    def clear_payload(self):
248
        self.patch = None
249
        self.patch_type = None
250
2520.4.80 by Aaron Bentley
Improve merge directive tests
251
    def get_raw_bundle(self):
252
        return self.bundle
253
2520.4.73 by Aaron Bentley
Implement new merge directive format
254
    def _bundle(self):
255
        if self.patch_type == 'bundle':
256
            return self.patch
257
        else:
258
            return None
259
260
    bundle = property(_bundle)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
261
1551.12.12 by Aaron Bentley
Add format header
262
    @classmethod
263
    def from_lines(klass, lines):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
264
        """Deserialize a MergeRequest from an iterable of lines
265
266
        :param lines: An iterable of lines
267
        :return: a MergeRequest
268
        """
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
269
        line_iter = iter(lines)
270
        for line in line_iter:
2520.4.73 by Aaron Bentley
Implement new merge directive format
271
            if line.startswith('# Bazaar merge directive format '):
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
272
                break
273
        else:
1551.12.59 by Aaron Bentley
Correctly handle empty merge directive texts
274
            if len(lines) > 0:
275
                raise errors.NotAMergeDirective(lines[0])
276
            else:
277
                raise errors.NotAMergeDirective('')
2520.4.73 by Aaron Bentley
Implement new merge directive format
278
        return _format_registry.get(line[2:].rstrip())._from_lines(line_iter)
279
280
    @classmethod
281
    def _from_lines(klass, line_iter):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
282
        stanza = rio.read_patch_stanza(line_iter)
283
        patch_lines = list(line_iter)
284
        if len(patch_lines) == 0:
285
            patch = None
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
286
            patch_type = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
287
        else:
288
            patch = ''.join(patch_lines)
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
289
            try:
290
                bundle_serializer.read_bundle(StringIO(patch))
1551.15.29 by Aaron Bentley
Make merge directives robust against broken bundles
291
            except (errors.NotABundle, errors.BundleNotSupported,
292
                    errors.BadBundle):
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
293
                patch_type = 'diff'
294
            else:
295
                patch_type = 'bundle'
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
296
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
297
        kwargs = {}
1551.12.13 by Aaron Bentley
Rename fields
298
        for key in ('revision_id', 'testament_sha1', 'target_branch',
1551.12.26 by Aaron Bentley
Get email working, with optional message
299
                    'source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
300
            try:
301
                kwargs[key] = stanza.get(key)
302
            except KeyError:
303
                pass
1551.12.54 by Aaron Bentley
Decoded revision ids are utf-8
304
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
305
        return MergeDirective(time=time, timezone=timezone,
306
                              patch_type=patch_type, patch=patch, **kwargs)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
307
308
    def to_lines(self):
2520.4.73 by Aaron Bentley
Implement new merge directive format
309
        lines = self._to_lines()
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
310
        if self.patch is not None:
311
            lines.extend(self.patch.splitlines(True))
312
        return lines
313
2520.4.73 by Aaron Bentley
Implement new merge directive format
314
    @staticmethod
315
    def _generate_bundle(repository, revision_id, ancestor_id):
316
        s = StringIO()
317
        bundle_serializer.write_bundle(repository, revision_id,
318
                                       ancestor_id, s, '0.9')
319
        return s.getvalue()
320
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
321
    def get_merge_request(self, repository):
322
        """Provide data for performing a merge
323
324
        Returns suggested base, suggested target, and patch verification status
325
        """
326
        return None, self.revision_id, 'inapplicable'
327
2520.4.76 by Aaron Bentley
Move base64-encoding into merge directives
328
2520.4.73 by Aaron Bentley
Implement new merge directive format
329
class MergeDirective2(_BaseMergeDirective):
330
2520.4.136 by Aaron Bentley
Fix format strings
331
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.19)'
2520.4.73 by Aaron Bentley
Implement new merge directive format
332
333
    def __init__(self, revision_id, testament_sha1, time, timezone,
334
                 target_branch, patch=None, source_branch=None, message=None,
2520.4.105 by Aaron Bentley
Implement patch verification
335
                 bundle=None, base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
336
        if source_branch is None and bundle is None:
337
            raise errors.NoMergeSource()
338
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
339
            timezone, target_branch, patch, source_branch, message)
340
        self.bundle = bundle
2520.4.105 by Aaron Bentley
Implement patch verification
341
        self.base_revision_id = base_revision_id
2520.4.73 by Aaron Bentley
Implement new merge directive format
342
343
    def _patch_type(self):
344
        if self.bundle is not None:
345
            return 'bundle'
346
        elif self.patch is not None:
347
            return 'diff'
348
        else:
349
            return None
350
351
    patch_type = property(_patch_type)
352
353
    def clear_payload(self):
354
        self.patch = None
355
        self.bundle = None
356
2520.4.80 by Aaron Bentley
Improve merge directive tests
357
    def get_raw_bundle(self):
358
        if self.bundle is None:
359
            return None
360
        else:
361
            return self.bundle.decode('base-64')
362
2520.4.73 by Aaron Bentley
Implement new merge directive format
363
    @classmethod
364
    def _from_lines(klass, line_iter):
365
        stanza = rio.read_patch_stanza(line_iter)
366
        patch = None
367
        bundle = None
368
        try:
369
            start = line_iter.next()
370
        except StopIteration:
371
            pass
372
        else:
373
            if start.startswith('# Begin patch'):
374
                patch_lines = []
375
                for line in line_iter:
376
                    if line.startswith('# Begin bundle'):
377
                        start = line
378
                        break
379
                    patch_lines.append(line)
380
                else:
381
                    start = None
382
                patch = ''.join(patch_lines)
383
            if start is not None:
384
                if start.startswith('# Begin bundle'):
385
                    bundle = ''.join(line_iter)
386
                else:
387
                    raise errors.IllegalMergeDirectivePayload(start)
388
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
389
        kwargs = {}
390
        for key in ('revision_id', 'testament_sha1', 'target_branch',
2520.4.105 by Aaron Bentley
Implement patch verification
391
                    'source_branch', 'message', 'base_revision_id'):
2520.4.73 by Aaron Bentley
Implement new merge directive format
392
            try:
393
                kwargs[key] = stanza.get(key)
394
            except KeyError:
395
                pass
396
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
2520.4.105 by Aaron Bentley
Implement patch verification
397
        kwargs['base_revision_id'] =\
398
            kwargs['base_revision_id'].encode('utf-8')
2520.4.73 by Aaron Bentley
Implement new merge directive format
399
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
400
                     **kwargs)
401
402
    def to_lines(self):
2520.4.105 by Aaron Bentley
Implement patch verification
403
        lines = self._to_lines(base_revision=True)
2520.4.73 by Aaron Bentley
Implement new merge directive format
404
        if self.patch is not None:
405
            lines.append('# Begin patch\n')
406
            lines.extend(self.patch.splitlines(True))
407
        if self.bundle is not None:
408
            lines.append('# Begin bundle\n')
409
            lines.extend(self.bundle.splitlines(True))
410
        return lines
411
412
    @classmethod
413
    def from_objects(klass, repository, revision_id, time, timezone,
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
414
                 target_branch, include_patch=True, include_bundle=True,
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
415
                 local_target_branch=None, public_branch=None, message=None,
416
                 base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
417
        """Generate a merge directive from various objects
418
419
        :param repository: The repository containing the revision
420
        :param revision_id: The revision to merge
421
        :param time: The POSIX timestamp of the date the request was issued.
422
        :param timezone: The timezone of the request
423
        :param target_branch: The url of the branch to merge into
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
424
        :param include_patch: If true, include a preview patch
425
        :param include_bundle: If true, include a bundle
2520.4.73 by Aaron Bentley
Implement new merge directive format
426
        :param local_target_branch: a local copy of the target branch
427
        :param public_branch: location of a public branch containing the target
428
            revision.
429
        :param message: Message to use when committing the merge
430
        :return: The merge directive
431
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
432
        The public branch is always used if supplied.  If no bundle is
433
        included, the public branch must be supplied, and will be verified.
2520.4.73 by Aaron Bentley
Implement new merge directive format
434
435
        If the message is not supplied, the message from revision_id will be
436
        used for the commit.
437
        """
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
438
        locked = []
439
        try:
440
            repository.lock_write()
441
            locked.append(repository)
442
            t_revision_id = revision_id
443
            if revision_id == 'null:':
444
                t_revision_id = None
445
            t = testament.StrictTestament3.from_revision(repository,
446
                t_revision_id)
447
            submit_branch = _mod_branch.Branch.open(target_branch)
448
            submit_branch.lock_read()
449
            locked.append(submit_branch)
450
            if submit_branch.get_public_branch() is not None:
451
                target_branch = submit_branch.get_public_branch()
2520.4.105 by Aaron Bentley
Implement patch verification
452
            submit_revision_id = submit_branch.last_revision()
453
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
454
            graph = repository.get_graph(submit_branch.repository)
455
            ancestor_id = graph.find_unique_lca(revision_id,
456
                                                submit_revision_id)
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
457
            if base_revision_id is None:
458
                base_revision_id = ancestor_id
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
459
            if (include_patch, include_bundle) != (False, False):
460
                repository.fetch(submit_branch.repository, submit_revision_id)
461
            if include_patch:
462
                patch = klass._generate_diff(repository, revision_id,
463
                                             base_revision_id)
464
            else:
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
465
                patch = None
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
466
467
            if include_bundle:
468
                bundle = klass._generate_bundle(repository, revision_id,
469
                    ancestor_id).encode('base-64')
470
            else:
2520.4.73 by Aaron Bentley
Implement new merge directive format
471
                bundle = None
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
472
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
473
            if public_branch is not None and not include_bundle:
474
                public_branch_obj = _mod_branch.Branch.open(public_branch)
475
                public_branch_obj.lock_read()
476
                locked.append(public_branch_obj)
477
                if not public_branch_obj.repository.has_revision(
478
                    revision_id):
479
                    raise errors.PublicBranchOutOfDate(public_branch,
480
                                                       revision_id)
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
481
        finally:
482
            for entry in reversed(locked):
483
                entry.unlock()
2520.4.73 by Aaron Bentley
Implement new merge directive format
484
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
485
            patch, public_branch, message, bundle, base_revision_id)
2520.4.105 by Aaron Bentley
Implement patch verification
486
487
    def _verify_patch(self, repository):
488
        calculated_patch = self._generate_diff(repository, self.revision_id,
489
                                               self.base_revision_id)
490
        # Convert line-endings to UNIX
491
        stored_patch = re.sub('\r\n?', '\n', self.patch)
492
        # Strip trailing whitespace
493
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
494
        stored_patch = re.sub(' *\n', '\n', stored_patch)
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
495
        return (calculated_patch == stored_patch)
496
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
497
    def get_merge_request(self, repository):
498
        """Provide data for performing a merge
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
499
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
500
        Returns suggested base, suggested target, and patch verification status
501
        """
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
502
        verified = self._maybe_verify(repository)
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
503
        return self.base_revision_id, self.revision_id, verified
2520.4.105 by Aaron Bentley
Implement patch verification
504
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
505
    def _maybe_verify(self, repository):
506
        if self.patch is not None:
507
            if self._verify_patch(repository):
508
                return 'verified'
509
            else:
510
                return 'failed'
511
        else:
512
            return 'inapplicable'
513
2520.4.73 by Aaron Bentley
Implement new merge directive format
514
515
class MergeDirectiveFormatRegistry(registry.Registry):
516
517
    def register(self, directive):
518
        registry.Registry.register(self, directive._format_string, directive)
519
520
521
_format_registry = MergeDirectiveFormatRegistry()
522
_format_registry.register(MergeDirective)
523
_format_registry.register(MergeDirective2)