~bzr-pqm/bzr/bzr.dev

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