/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1551.12.36 by Aaron Bentley
Fix failing tests
1
# Copyright (C) 2007 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
1551.12.26 by Aaron Bentley
Get email working, with optional message
18
from email import Message
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
19
from StringIO import StringIO
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,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
26
    revision as _mod_revision,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
27
    rio,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
28
    testament,
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
29
    timestamp,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
30
    )
31
from bzrlib.bundle import serializer as bundle_serializer
32
33
34
class MergeDirective(object):
35
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
36
    """A request to perform a merge into a branch.
37
38
    Designed to be serialized and mailed.  It provides all the information
39
    needed to perform a merge automatically, by providing at minimum a revision
40
    bundle or the location of a branch.
41
42
    The serialization format is robust against certain common forms of
43
    deterioration caused by mailing.
44
45
    The format is also designed to be patch-compatible.  If the directive
46
    includes a diff or revision bundle, it should be possible to apply it
47
    directly using the standard patch program.
48
    """
49
1551.12.45 by Aaron Bentley
Change format marker to not experimental
50
    _format_string = 'Bazaar merge directive format 1'
1551.12.12 by Aaron Bentley
Add format header
51
1551.12.4 by Aaron Bentley
Add failing test
52
    def __init__(self, revision_id, testament_sha1, time, timezone,
1551.12.13 by Aaron Bentley
Rename fields
53
                 target_branch, patch=None, patch_type=None,
1551.12.26 by Aaron Bentley
Get email working, with optional message
54
                 source_branch=None, message=None):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
55
        """Constructor.
56
57
        :param revision_id: The revision to merge
58
        :param testament_sha1: The sha1 of the testament of the revision to
59
            merge.
60
        :param time: The current POSIX timestamp time
61
        :param timezone: The timezone offset
62
        :param target_branch: The branch to apply the merge to
63
        :param patch: The text of a diff or bundle
64
        :param patch_type: None, "diff" or "bundle", depending on the contents
65
            of patch
66
        :param source_branch: A public location to merge the revision from
67
        :param message: The message to use when committing this merge
68
        """
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
69
        assert patch_type in (None, 'diff', 'bundle')
1551.12.13 by Aaron Bentley
Rename fields
70
        if patch_type != 'bundle' and source_branch is None:
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
71
            raise errors.NoMergeSource()
72
        if patch_type is not None and patch is None:
73
            raise errors.PatchMissing(patch_type)
1551.12.4 by Aaron Bentley
Add failing test
74
        self.revision_id = revision_id
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
75
        self.testament_sha1 = testament_sha1
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
76
        self.time = time
77
        self.timezone = timezone
1551.12.13 by Aaron Bentley
Rename fields
78
        self.target_branch = target_branch
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
79
        self.patch = patch
80
        self.patch_type = patch_type
1551.12.13 by Aaron Bentley
Rename fields
81
        self.source_branch = source_branch
1551.12.26 by Aaron Bentley
Get email working, with optional message
82
        self.message = message
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
83
1551.12.12 by Aaron Bentley
Add format header
84
    @classmethod
85
    def from_lines(klass, lines):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
86
        """Deserialize a MergeRequest from an iterable of lines
87
88
        :param lines: An iterable of lines
89
        :return: a MergeRequest
90
        """
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
91
        line_iter = iter(lines)
92
        for line in line_iter:
93
            if line.startswith('# ' + klass._format_string):
94
                break
95
        else:
1551.12.59 by Aaron Bentley
Correctly handle empty merge directive texts
96
            if len(lines) > 0:
97
                raise errors.NotAMergeDirective(lines[0])
98
            else:
99
                raise errors.NotAMergeDirective('')
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
100
        stanza = rio.read_patch_stanza(line_iter)
101
        patch_lines = list(line_iter)
102
        if len(patch_lines) == 0:
103
            patch = None
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
104
            patch_type = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
105
        else:
106
            patch = ''.join(patch_lines)
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
107
            try:
108
                bundle_serializer.read_bundle(StringIO(patch))
109
            except errors.NotABundle:
110
                patch_type = 'diff'
111
            else:
112
                patch_type = 'bundle'
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
113
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
114
        kwargs = {}
1551.12.13 by Aaron Bentley
Rename fields
115
        for key in ('revision_id', 'testament_sha1', 'target_branch',
1551.12.26 by Aaron Bentley
Get email working, with optional message
116
                    'source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
117
            try:
118
                kwargs[key] = stanza.get(key)
119
            except KeyError:
120
                pass
1551.12.54 by Aaron Bentley
Decoded revision ids are utf-8
121
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
122
        return MergeDirective(time=time, timezone=timezone,
123
                              patch_type=patch_type, patch=patch, **kwargs)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
124
125
    def to_lines(self):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
126
        """Serialize as a list of lines
127
128
        :return: a list of lines
129
        """
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
130
        time_str = timestamp.format_patch_date(self.time, self.timezone)
131
        stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str,
1551.12.13 by Aaron Bentley
Rename fields
132
                            target_branch=self.target_branch,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
133
                            testament_sha1=self.testament_sha1)
1551.12.26 by Aaron Bentley
Get email working, with optional message
134
        for key in ('source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
135
            if self.__dict__[key] is not None:
136
                stanza.add(key, self.__dict__[key])
1551.12.12 by Aaron Bentley
Add format header
137
        lines = ['# ' + self._format_string + '\n']
138
        lines.extend(rio.to_patch_lines(stanza))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
139
        lines.append('# \n')
140
        if self.patch is not None:
141
            lines.extend(self.patch.splitlines(True))
142
        return lines
143
1551.12.16 by Aaron Bentley
Enable signing merge directives
144
    def to_signed(self, branch):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
145
        """Serialize as a signed string.
146
147
        :param branch: The source branch, to get the signing strategy
148
        :return: a string
149
        """
1551.12.16 by Aaron Bentley
Enable signing merge directives
150
        my_gpg = gpg.GPGStrategy(branch.get_config())
151
        return my_gpg.sign(''.join(self.to_lines()))
152
1551.12.26 by Aaron Bentley
Get email working, with optional message
153
    def to_email(self, mail_to, branch, sign=False):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
154
        """Serialize as an email message.
155
156
        :param mail_to: The address to mail the message to
157
        :param branch: The source branch, to get the signing strategy and
158
            source email address
159
        :param sign: If True, gpg-sign the email
160
        :return: an email message
161
        """
1551.12.26 by Aaron Bentley
Get email working, with optional message
162
        mail_from = branch.get_config().username()
163
        message = Message.Message()
164
        message['To'] = mail_to
165
        message['From'] = mail_from
166
        if self.message is not None:
167
            message['Subject'] = self.message
168
        else:
169
            revision = branch.repository.get_revision(self.revision_id)
170
            message['Subject'] = revision.message
171
        if sign:
172
            body = self.to_signed(branch)
173
        else:
174
            body = ''.join(self.to_lines())
175
        message.set_payload(body)
176
        return message
177
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
178
    @classmethod
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
179
    def from_objects(klass, repository, revision_id, time, timezone,
1551.12.13 by Aaron Bentley
Rename fields
180
                 target_branch, patch_type='bundle',
1551.12.27 by Aaron Bentley
support custom message everywhere
181
                 local_target_branch=None, public_branch=None, message=None):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
182
        """Generate a merge directive from various objects
183
184
        :param repository: The repository containing the revision
185
        :param revision_id: The revision to merge
186
        :param time: The POSIX timestamp of the date the request was issued.
187
        :param timezone: The timezone of the request
188
        :param target_branch: The url of the branch to merge into
189
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
190
            patch desired.
191
        :param local_target_branch: a local copy of the target branch
192
        :param public_branch: location of a public branch containing the target
193
            revision.
194
        :param message: Message to use when committing the merge
195
        :return: The merge directive
196
197
        The public branch is always used if supplied.  If the patch_type is
198
        not 'bundle', the public branch must be supplied, and will be verified.
199
200
        If the message is not supplied, the message from revision_id will be
201
        used for the commit.
202
        """
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
203
        t = testament.StrictTestament3.from_revision(repository, revision_id)
1551.12.50 by Aaron Bentley
Use public location of submit branch if possible
204
        submit_branch = _mod_branch.Branch.open(target_branch)
205
        if submit_branch.get_public_branch() is not None:
206
            target_branch = submit_branch.get_public_branch()
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
207
        if patch_type is None:
208
            patch = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
209
        else:
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
210
            submit_revision_id = submit_branch.last_revision()
211
            repository.fetch(submit_branch.repository, submit_revision_id)
212
            ancestor_id = _mod_revision.common_ancestor(revision_id,
213
                                                        submit_revision_id,
214
                                                        repository)
1551.12.39 by Aaron Bentley
Re-design patch handling to use a dict
215
            type_handler = {'bundle': klass._generate_bundle,
216
                            'diff': klass._generate_diff,
217
                            None: lambda x, y, z: None }
218
            patch = type_handler[patch_type](repository, revision_id,
219
                                             ancestor_id)
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
220
            if patch_type == 'bundle':
221
                s = StringIO()
222
                bundle_serializer.write_bundle(repository, revision_id,
223
                                               ancestor_id, s)
224
                patch = s.getvalue()
225
            elif patch_type == 'diff':
226
                patch = klass._generate_diff(repository, revision_id,
227
                                             ancestor_id)
1551.12.33 by Aaron Bentley
Take public_branch as a string, not object
228
1551.12.34 by Aaron Bentley
Check public branch only if not using a bundle
229
            if public_branch is not None and patch_type != 'bundle':
1551.12.33 by Aaron Bentley
Take public_branch as a string, not object
230
                public_branch_obj = _mod_branch.Branch.open(public_branch)
231
                if not public_branch_obj.repository.has_revision(revision_id):
232
                    raise errors.PublicBranchOutOfDate(public_branch,
233
                                                       revision_id)
234
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
235
        return MergeDirective(revision_id, t.as_sha1(), time, timezone,
1551.12.33 by Aaron Bentley
Take public_branch as a string, not object
236
                              target_branch, patch, patch_type, public_branch,
1551.12.27 by Aaron Bentley
support custom message everywhere
237
                              message)
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
238
239
    @staticmethod
240
    def _generate_diff(repository, revision_id, ancestor_id):
241
        tree_1 = repository.revision_tree(ancestor_id)
242
        tree_2 = repository.revision_tree(revision_id)
243
        s = StringIO()
1551.12.40 by Aaron Bentley
Do not show prefixes in diffs
244
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
245
        return s.getvalue()
1551.12.39 by Aaron Bentley
Re-design patch handling to use a dict
246
247
    @staticmethod
248
    def _generate_bundle(repository, revision_id, ancestor_id):
249
        s = StringIO()
250
        bundle_serializer.write_bundle(repository, revision_id,
251
                                       ancestor_id, s)
252
        return s.getvalue()