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