~bzr-pqm/bzr/bzr.dev

1551.12.36 by Aaron Bentley
Fix failing tests
1
# Copyright (C) 2007 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1551.12.36 by Aaron Bentley
Fix failing tests
16
17
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
18
from StringIO import StringIO
2520.4.105 by Aaron Bentley
Implement patch verification
19
import re
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
20
21
from bzrlib import (
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
22
    branch as _mod_branch,
23
    diff,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
24
    errors,
1551.12.16 by Aaron Bentley
Enable signing merge directives
25
    gpg,
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
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
139
    def get_disk_name(self, branch):
140
        """Generate a suitable basename for storing this directive on disk
141
142
        :param branch: The Branch this merge directive was generated fro
143
        :return: A string
144
        """
145
        revno, revision_id = branch.last_revision_info()
146
        if self.revision_id == revision_id:
3251.2.2 by Aaron Bentley
Fix bug in last revno handling
147
            revno = [revno]
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
148
        else:
149
            revno = branch.get_revision_id_to_revno_map().get(self.revision_id,
150
                ['merge'])
3449.4.1 by Lukáš Lalinský
Sanitize branch nick before using it as an attachment filename in ``bzr send``
151
        nick = re.sub('(\W+)', '-', branch.nick).strip('-')
152
        return '%s-%s' % (nick, '.'.join(str(n) for n in revno))
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
153
2520.4.73 by Aaron Bentley
Implement new merge directive format
154
    @staticmethod
155
    def _generate_diff(repository, revision_id, ancestor_id):
156
        tree_1 = repository.revision_tree(ancestor_id)
157
        tree_2 = repository.revision_tree(revision_id)
158
        s = StringIO()
159
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
160
        return s.getvalue()
161
162
    @staticmethod
163
    def _generate_bundle(repository, revision_id, ancestor_id):
164
        s = StringIO()
165
        bundle_serializer.write_bundle(repository, revision_id,
166
                                       ancestor_id, s)
167
        return s.getvalue()
168
2520.4.80 by Aaron Bentley
Improve merge directive tests
169
    def to_signed(self, branch):
170
        """Serialize as a signed string.
171
172
        :param branch: The source branch, to get the signing strategy
173
        :return: a string
174
        """
175
        my_gpg = gpg.GPGStrategy(branch.get_config())
176
        return my_gpg.sign(''.join(self.to_lines()))
177
178
    def to_email(self, mail_to, branch, sign=False):
179
        """Serialize as an email message.
180
181
        :param mail_to: The address to mail the message to
182
        :param branch: The source branch, to get the signing strategy and
183
            source email address
184
        :param sign: If True, gpg-sign the email
185
        :return: an email message
186
        """
187
        mail_from = branch.get_config().username()
188
        if self.message is not None:
2625.6.2 by Adeodato Simó
Merge bzr.dev, resolving conflicts and updating test_merge_directive.py.
189
            subject = self.message
2520.4.80 by Aaron Bentley
Improve merge directive tests
190
        else:
191
            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.
192
            subject = revision.message
2520.4.80 by Aaron Bentley
Improve merge directive tests
193
        if sign:
194
            body = self.to_signed(branch)
195
        else:
196
            body = ''.join(self.to_lines())
2625.6.2 by Adeodato Simó
Merge bzr.dev, resolving conflicts and updating test_merge_directive.py.
197
        message = EmailMessage(mail_from, mail_to, subject, body)
2520.4.80 by Aaron Bentley
Improve merge directive tests
198
        return message
199
200
    def install_revisions(self, target_repo):
201
        """Install revisions and return the target revision"""
202
        if not target_repo.has_revision(self.revision_id):
203
            if self.patch_type == 'bundle':
204
                info = bundle_serializer.read_bundle(
205
                    StringIO(self.get_raw_bundle()))
206
                # We don't use the bundle's target revision, because
207
                # MergeDirective.revision_id is authoritative.
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
208
                try:
209
                    info.install_revisions(target_repo, stream_input=False)
210
                except errors.RevisionNotPresent:
211
                    # At least one dependency isn't present.  Try installing
212
                    # missing revisions from the submit branch
3535.8.1 by James Westby
Handle something that isn't a branch being specified in target_branch.
213
                    try:
214
                        submit_branch = \
215
                            _mod_branch.Branch.open(self.target_branch)
216
                    except errors.NotBranchError:
217
                        raise errors.TargetNotBranch(self.target_branch)
1551.19.20 by Aaron Bentley
Updates from review
218
                    missing_revisions = []
219
                    bundle_revisions = set(r.revision_id for r in
220
                                           info.real_revisions)
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
221
                    for revision in info.real_revisions:
222
                        for parent_id in revision.parent_ids:
1551.19.20 by Aaron Bentley
Updates from review
223
                            if (parent_id not in bundle_revisions and
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
224
                                not target_repo.has_revision(parent_id)):
225
                                missing_revisions.append(parent_id)
1551.19.20 by Aaron Bentley
Updates from review
226
                    # reverse missing revisions to try to get heads first
227
                    unique_missing = []
228
                    unique_missing_set = set()
229
                    for revision in reversed(missing_revisions):
230
                        if revision in unique_missing_set:
231
                            continue
232
                        unique_missing.append(revision)
233
                        unique_missing_set.add(revision)
234
                    for missing_revision in unique_missing:
1551.19.19 by Aaron Bentley
Merge directives can now fetch prerequisites from the target branch
235
                        target_repo.fetch(submit_branch.repository,
236
                                          missing_revision)
237
                    info.install_revisions(target_repo, stream_input=False)
2520.4.80 by Aaron Bentley
Improve merge directive tests
238
            else:
239
                source_branch = _mod_branch.Branch.open(self.source_branch)
240
                target_repo.fetch(source_branch.repository, self.revision_id)
241
        return self.revision_id
242
2520.4.73 by Aaron Bentley
Implement new merge directive format
243
244
class MergeDirective(_BaseMergeDirective):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
245
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
246
    """A request to perform a merge into a branch.
247
248
    Designed to be serialized and mailed.  It provides all the information
249
    needed to perform a merge automatically, by providing at minimum a revision
250
    bundle or the location of a branch.
251
252
    The serialization format is robust against certain common forms of
253
    deterioration caused by mailing.
254
255
    The format is also designed to be patch-compatible.  If the directive
256
    includes a diff or revision bundle, it should be possible to apply it
257
    directly using the standard patch program.
258
    """
259
1551.12.45 by Aaron Bentley
Change format marker to not experimental
260
    _format_string = 'Bazaar merge directive format 1'
1551.12.12 by Aaron Bentley
Add format header
261
1551.12.4 by Aaron Bentley
Add failing test
262
    def __init__(self, revision_id, testament_sha1, time, timezone,
1551.12.13 by Aaron Bentley
Rename fields
263
                 target_branch, patch=None, patch_type=None,
2520.4.73 by Aaron Bentley
Implement new merge directive format
264
                 source_branch=None, message=None, bundle=None):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
265
        """Constructor.
266
267
        :param revision_id: The revision to merge
268
        :param testament_sha1: The sha1 of the testament of the revision to
269
            merge.
270
        :param time: The current POSIX timestamp time
271
        :param timezone: The timezone offset
272
        :param target_branch: The branch to apply the merge to
273
        :param patch: The text of a diff or bundle
274
        :param patch_type: None, "diff" or "bundle", depending on the contents
275
            of patch
276
        :param source_branch: A public location to merge the revision from
277
        :param message: The message to use when committing this merge
278
        """
2520.4.73 by Aaron Bentley
Implement new merge directive format
279
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
280
            timezone, target_branch, patch, source_branch, message)
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
281
        if patch_type not in (None, 'diff', 'bundle'):
282
            raise ValueError(patch_type)
1551.12.13 by Aaron Bentley
Rename fields
283
        if patch_type != 'bundle' and source_branch is None:
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
284
            raise errors.NoMergeSource()
285
        if patch_type is not None and patch is None:
286
            raise errors.PatchMissing(patch_type)
287
        self.patch_type = patch_type
2520.4.73 by Aaron Bentley
Implement new merge directive format
288
289
    def clear_payload(self):
290
        self.patch = None
291
        self.patch_type = None
292
2520.4.80 by Aaron Bentley
Improve merge directive tests
293
    def get_raw_bundle(self):
294
        return self.bundle
295
2520.4.73 by Aaron Bentley
Implement new merge directive format
296
    def _bundle(self):
297
        if self.patch_type == 'bundle':
298
            return self.patch
299
        else:
300
            return None
301
302
    bundle = property(_bundle)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
303
1551.12.12 by Aaron Bentley
Add format header
304
    @classmethod
305
    def from_lines(klass, lines):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
306
        """Deserialize a MergeRequest from an iterable of lines
307
308
        :param lines: An iterable of lines
309
        :return: a MergeRequest
310
        """
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
311
        line_iter = iter(lines)
312
        for line in line_iter:
2520.4.73 by Aaron Bentley
Implement new merge directive format
313
            if line.startswith('# Bazaar merge directive format '):
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
314
                break
315
        else:
1551.12.59 by Aaron Bentley
Correctly handle empty merge directive texts
316
            if len(lines) > 0:
317
                raise errors.NotAMergeDirective(lines[0])
318
            else:
319
                raise errors.NotAMergeDirective('')
2520.4.73 by Aaron Bentley
Implement new merge directive format
320
        return _format_registry.get(line[2:].rstrip())._from_lines(line_iter)
321
322
    @classmethod
323
    def _from_lines(klass, line_iter):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
324
        stanza = rio.read_patch_stanza(line_iter)
325
        patch_lines = list(line_iter)
326
        if len(patch_lines) == 0:
327
            patch = None
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
328
            patch_type = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
329
        else:
330
            patch = ''.join(patch_lines)
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
331
            try:
332
                bundle_serializer.read_bundle(StringIO(patch))
1551.15.29 by Aaron Bentley
Make merge directives robust against broken bundles
333
            except (errors.NotABundle, errors.BundleNotSupported,
334
                    errors.BadBundle):
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
335
                patch_type = 'diff'
336
            else:
337
                patch_type = 'bundle'
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
338
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
339
        kwargs = {}
1551.12.13 by Aaron Bentley
Rename fields
340
        for key in ('revision_id', 'testament_sha1', 'target_branch',
1551.12.26 by Aaron Bentley
Get email working, with optional message
341
                    'source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
342
            try:
343
                kwargs[key] = stanza.get(key)
344
            except KeyError:
345
                pass
1551.12.54 by Aaron Bentley
Decoded revision ids are utf-8
346
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
347
        return MergeDirective(time=time, timezone=timezone,
348
                              patch_type=patch_type, patch=patch, **kwargs)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
349
350
    def to_lines(self):
2520.4.73 by Aaron Bentley
Implement new merge directive format
351
        lines = self._to_lines()
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
352
        if self.patch is not None:
353
            lines.extend(self.patch.splitlines(True))
354
        return lines
355
2520.4.73 by Aaron Bentley
Implement new merge directive format
356
    @staticmethod
357
    def _generate_bundle(repository, revision_id, ancestor_id):
358
        s = StringIO()
359
        bundle_serializer.write_bundle(repository, revision_id,
360
                                       ancestor_id, s, '0.9')
361
        return s.getvalue()
362
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
363
    def get_merge_request(self, repository):
364
        """Provide data for performing a merge
365
366
        Returns suggested base, suggested target, and patch verification status
367
        """
368
        return None, self.revision_id, 'inapplicable'
369
2520.4.76 by Aaron Bentley
Move base64-encoding into merge directives
370
2520.4.73 by Aaron Bentley
Implement new merge directive format
371
class MergeDirective2(_BaseMergeDirective):
372
2687.2.2 by Martin Pool
Fix up other references to 0.19
373
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
2520.4.73 by Aaron Bentley
Implement new merge directive format
374
375
    def __init__(self, revision_id, testament_sha1, time, timezone,
376
                 target_branch, patch=None, source_branch=None, message=None,
2520.4.105 by Aaron Bentley
Implement patch verification
377
                 bundle=None, base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
378
        if source_branch is None and bundle is None:
379
            raise errors.NoMergeSource()
380
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
381
            timezone, target_branch, patch, source_branch, message)
382
        self.bundle = bundle
2520.4.105 by Aaron Bentley
Implement patch verification
383
        self.base_revision_id = base_revision_id
2520.4.73 by Aaron Bentley
Implement new merge directive format
384
385
    def _patch_type(self):
386
        if self.bundle is not None:
387
            return 'bundle'
388
        elif self.patch is not None:
389
            return 'diff'
390
        else:
391
            return None
392
393
    patch_type = property(_patch_type)
394
395
    def clear_payload(self):
396
        self.patch = None
397
        self.bundle = None
398
2520.4.80 by Aaron Bentley
Improve merge directive tests
399
    def get_raw_bundle(self):
400
        if self.bundle is None:
401
            return None
402
        else:
403
            return self.bundle.decode('base-64')
404
2520.4.73 by Aaron Bentley
Implement new merge directive format
405
    @classmethod
406
    def _from_lines(klass, line_iter):
407
        stanza = rio.read_patch_stanza(line_iter)
408
        patch = None
409
        bundle = None
410
        try:
411
            start = line_iter.next()
412
        except StopIteration:
413
            pass
414
        else:
415
            if start.startswith('# Begin patch'):
416
                patch_lines = []
417
                for line in line_iter:
418
                    if line.startswith('# Begin bundle'):
419
                        start = line
420
                        break
421
                    patch_lines.append(line)
422
                else:
423
                    start = None
424
                patch = ''.join(patch_lines)
425
            if start is not None:
426
                if start.startswith('# Begin bundle'):
427
                    bundle = ''.join(line_iter)
428
                else:
429
                    raise errors.IllegalMergeDirectivePayload(start)
430
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
431
        kwargs = {}
432
        for key in ('revision_id', 'testament_sha1', 'target_branch',
2520.4.105 by Aaron Bentley
Implement patch verification
433
                    'source_branch', 'message', 'base_revision_id'):
2520.4.73 by Aaron Bentley
Implement new merge directive format
434
            try:
435
                kwargs[key] = stanza.get(key)
436
            except KeyError:
437
                pass
438
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
2520.4.105 by Aaron Bentley
Implement patch verification
439
        kwargs['base_revision_id'] =\
440
            kwargs['base_revision_id'].encode('utf-8')
2520.4.73 by Aaron Bentley
Implement new merge directive format
441
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
442
                     **kwargs)
443
444
    def to_lines(self):
2520.4.105 by Aaron Bentley
Implement patch verification
445
        lines = self._to_lines(base_revision=True)
2520.4.73 by Aaron Bentley
Implement new merge directive format
446
        if self.patch is not None:
447
            lines.append('# Begin patch\n')
448
            lines.extend(self.patch.splitlines(True))
449
        if self.bundle is not None:
450
            lines.append('# Begin bundle\n')
451
            lines.extend(self.bundle.splitlines(True))
452
        return lines
453
454
    @classmethod
455
    def from_objects(klass, repository, revision_id, time, timezone,
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
456
                 target_branch, include_patch=True, include_bundle=True,
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
457
                 local_target_branch=None, public_branch=None, message=None,
458
                 base_revision_id=None):
2520.4.73 by Aaron Bentley
Implement new merge directive format
459
        """Generate a merge directive from various objects
460
461
        :param repository: The repository containing the revision
462
        :param revision_id: The revision to merge
463
        :param time: The POSIX timestamp of the date the request was issued.
464
        :param timezone: The timezone of the request
465
        :param target_branch: The url of the branch to merge into
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
466
        :param include_patch: If true, include a preview patch
467
        :param include_bundle: If true, include a bundle
2520.4.73 by Aaron Bentley
Implement new merge directive format
468
        :param local_target_branch: a local copy of the target branch
469
        :param public_branch: location of a public branch containing the target
470
            revision.
471
        :param message: Message to use when committing the merge
472
        :return: The merge directive
473
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
474
        The public branch is always used if supplied.  If no bundle is
475
        included, the public branch must be supplied, and will be verified.
2520.4.73 by Aaron Bentley
Implement new merge directive format
476
477
        If the message is not supplied, the message from revision_id will be
478
        used for the commit.
479
        """
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
480
        locked = []
481
        try:
482
            repository.lock_write()
483
            locked.append(repository)
484
            t_revision_id = revision_id
485
            if revision_id == 'null:':
486
                t_revision_id = None
487
            t = testament.StrictTestament3.from_revision(repository,
488
                t_revision_id)
489
            submit_branch = _mod_branch.Branch.open(target_branch)
490
            submit_branch.lock_read()
491
            locked.append(submit_branch)
492
            if submit_branch.get_public_branch() is not None:
493
                target_branch = submit_branch.get_public_branch()
2520.4.105 by Aaron Bentley
Implement patch verification
494
            submit_revision_id = submit_branch.last_revision()
495
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
496
            graph = repository.get_graph(submit_branch.repository)
497
            ancestor_id = graph.find_unique_lca(revision_id,
498
                                                submit_revision_id)
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
499
            if base_revision_id is None:
500
                base_revision_id = ancestor_id
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
501
            if (include_patch, include_bundle) != (False, False):
502
                repository.fetch(submit_branch.repository, submit_revision_id)
503
            if include_patch:
504
                patch = klass._generate_diff(repository, revision_id,
505
                                             base_revision_id)
506
            else:
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
507
                patch = None
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
508
509
            if include_bundle:
510
                bundle = klass._generate_bundle(repository, revision_id,
511
                    ancestor_id).encode('base-64')
512
            else:
2520.4.73 by Aaron Bentley
Implement new merge directive format
513
                bundle = None
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
514
2520.5.4 by Aaron Bentley
Replace 'bundle-revisions' with 'submit' command
515
            if public_branch is not None and not include_bundle:
516
                public_branch_obj = _mod_branch.Branch.open(public_branch)
517
                public_branch_obj.lock_read()
518
                locked.append(public_branch_obj)
519
                if not public_branch_obj.repository.has_revision(
520
                    revision_id):
521
                    raise errors.PublicBranchOutOfDate(public_branch,
522
                                                       revision_id)
2520.4.86 by Aaron Bentley
Improve locking in _BaseMergeDirective.from_object
523
        finally:
524
            for entry in reversed(locked):
525
                entry.unlock()
2520.4.73 by Aaron Bentley
Implement new merge directive format
526
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
2520.4.112 by Aaron Bentley
Make cherry-pick merge directives possible
527
            patch, public_branch, message, bundle, base_revision_id)
2520.4.105 by Aaron Bentley
Implement patch verification
528
529
    def _verify_patch(self, repository):
530
        calculated_patch = self._generate_diff(repository, self.revision_id,
531
                                               self.base_revision_id)
532
        # Convert line-endings to UNIX
533
        stored_patch = re.sub('\r\n?', '\n', self.patch)
2520.7.2 by Aaron Bentley
Restore patch verification for CR, CRLF files
534
        calculated_patch = re.sub('\r\n?', '\n', calculated_patch)
2520.4.105 by Aaron Bentley
Implement patch verification
535
        # Strip trailing whitespace
536
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
537
        stored_patch = re.sub(' *\n', '\n', stored_patch)
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
538
        return (calculated_patch == stored_patch)
539
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
540
    def get_merge_request(self, repository):
541
        """Provide data for performing a merge
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
542
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
543
        Returns suggested base, suggested target, and patch verification status
544
        """
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
545
        verified = self._maybe_verify(repository)
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
546
        return self.base_revision_id, self.revision_id, verified
2520.4.105 by Aaron Bentley
Implement patch verification
547
2520.4.108 by Aaron Bentley
Start work on using merge base from directives
548
    def _maybe_verify(self, repository):
549
        if self.patch is not None:
550
            if self._verify_patch(repository):
551
                return 'verified'
552
            else:
553
                return 'failed'
554
        else:
555
            return 'inapplicable'
556
2520.4.73 by Aaron Bentley
Implement new merge directive format
557
558
class MergeDirectiveFormatRegistry(registry.Registry):
559
2694.1.1 by Aaron Bentley
Restore support for Merge directive 2 / 0.19
560
    def register(self, directive, format_string=None):
561
        if format_string is None:
2694.1.3 by Aaron Bentley
Fix whitespace
562
            format_string = directive._format_string
2694.1.1 by Aaron Bentley
Restore support for Merge directive 2 / 0.19
563
        registry.Registry.register(self, format_string, directive)
2520.4.73 by Aaron Bentley
Implement new merge directive format
564
565
566
_format_registry = MergeDirectiveFormatRegistry()
567
_format_registry.register(MergeDirective)
568
_format_registry.register(MergeDirective2)
4297.1.1 by Vincent Ladeuil
Trivial cleanups.
569
# 0.19 never existed.  It got renamed to 0.90.  But by that point, there were
570
# already merge directives in the wild that used 0.19. Registering with the old
571
# format string to retain compatibility with those merge directives.
2694.1.1 by Aaron Bentley
Restore support for Merge directive 2 / 0.19
572
_format_registry.register(MergeDirective2,
573
                          'Bazaar merge directive format 2 (Bazaar 0.19)')