/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.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
18
from StringIO import StringIO
19
20
from bzrlib import (
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
21
    branch as _mod_branch,
22
    diff,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
23
    errors,
1551.12.16 by Aaron Bentley
Enable signing merge directives
24
    gpg,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
25
    revision as _mod_revision,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
26
    rio,
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
27
    testament,
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
28
    timestamp,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
29
    )
1551.14.4 by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables'
30
from bzrlib.bundle import (
31
    serializer as bundle_serializer,
32
    )
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
33
from bzrlib.email_message import EmailMessage
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
34
35
36
class MergeDirective(object):
37
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
38
    """A request to perform a merge into a branch.
39
40
    Designed to be serialized and mailed.  It provides all the information
41
    needed to perform a merge automatically, by providing at minimum a revision
42
    bundle or the location of a branch.
43
44
    The serialization format is robust against certain common forms of
45
    deterioration caused by mailing.
46
47
    The format is also designed to be patch-compatible.  If the directive
48
    includes a diff or revision bundle, it should be possible to apply it
49
    directly using the standard patch program.
50
    """
51
1551.12.45 by Aaron Bentley
Change format marker to not experimental
52
    _format_string = 'Bazaar merge directive format 1'
1551.12.12 by Aaron Bentley
Add format header
53
1551.12.4 by Aaron Bentley
Add failing test
54
    def __init__(self, revision_id, testament_sha1, time, timezone,
1551.12.13 by Aaron Bentley
Rename fields
55
                 target_branch, patch=None, patch_type=None,
1551.12.26 by Aaron Bentley
Get email working, with optional message
56
                 source_branch=None, message=None):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
57
        """Constructor.
58
59
        :param revision_id: The revision to merge
60
        :param testament_sha1: The sha1 of the testament of the revision to
61
            merge.
62
        :param time: The current POSIX timestamp time
63
        :param timezone: The timezone offset
64
        :param target_branch: The branch to apply the merge to
65
        :param patch: The text of a diff or bundle
66
        :param patch_type: None, "diff" or "bundle", depending on the contents
67
            of patch
68
        :param source_branch: A public location to merge the revision from
69
        :param message: The message to use when committing this merge
70
        """
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
71
        assert patch_type in (None, 'diff', 'bundle')
1551.12.13 by Aaron Bentley
Rename fields
72
        if patch_type != 'bundle' and source_branch is None:
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
73
            raise errors.NoMergeSource()
74
        if patch_type is not None and patch is None:
75
            raise errors.PatchMissing(patch_type)
1551.12.4 by Aaron Bentley
Add failing test
76
        self.revision_id = revision_id
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
77
        self.testament_sha1 = testament_sha1
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
78
        self.time = time
79
        self.timezone = timezone
1551.12.13 by Aaron Bentley
Rename fields
80
        self.target_branch = target_branch
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
81
        self.patch = patch
82
        self.patch_type = patch_type
1551.12.13 by Aaron Bentley
Rename fields
83
        self.source_branch = source_branch
1551.12.26 by Aaron Bentley
Get email working, with optional message
84
        self.message = message
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
85
1551.12.12 by Aaron Bentley
Add format header
86
    @classmethod
87
    def from_lines(klass, lines):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
88
        """Deserialize a MergeRequest from an iterable of lines
89
90
        :param lines: An iterable of lines
91
        :return: a MergeRequest
92
        """
1551.12.51 by Aaron Bentley
Allow leading junk before merge directive header
93
        line_iter = iter(lines)
94
        for line in line_iter:
95
            if line.startswith('# ' + klass._format_string):
96
                break
97
        else:
1551.12.59 by Aaron Bentley
Correctly handle empty merge directive texts
98
            if len(lines) > 0:
99
                raise errors.NotAMergeDirective(lines[0])
100
            else:
101
                raise errors.NotAMergeDirective('')
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
102
        stanza = rio.read_patch_stanza(line_iter)
103
        patch_lines = list(line_iter)
104
        if len(patch_lines) == 0:
105
            patch = None
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
106
            patch_type = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
107
        else:
108
            patch = ''.join(patch_lines)
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
109
            try:
110
                bundle_serializer.read_bundle(StringIO(patch))
1551.15.29 by Aaron Bentley
Make merge directives robust against broken bundles
111
            except (errors.NotABundle, errors.BundleNotSupported,
112
                    errors.BadBundle):
1551.12.53 by Aaron Bentley
Fix deserialization of merge directives with no patch
113
                patch_type = 'diff'
114
            else:
115
                patch_type = 'bundle'
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
116
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
117
        kwargs = {}
1551.12.13 by Aaron Bentley
Rename fields
118
        for key in ('revision_id', 'testament_sha1', 'target_branch',
1551.12.26 by Aaron Bentley
Get email working, with optional message
119
                    'source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
120
            try:
121
                kwargs[key] = stanza.get(key)
122
            except KeyError:
123
                pass
1551.12.54 by Aaron Bentley
Decoded revision ids are utf-8
124
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
1551.12.3 by Aaron Bentley
Add timestamps to merge directives
125
        return MergeDirective(time=time, timezone=timezone,
126
                              patch_type=patch_type, patch=patch, **kwargs)
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
127
128
    def to_lines(self):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
129
        """Serialize as a list of lines
130
131
        :return: a list of lines
132
        """
1551.12.30 by Aaron Bentley
Use patch-style dates for timestamps in merge directives
133
        time_str = timestamp.format_patch_date(self.time, self.timezone)
134
        stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str,
1551.12.13 by Aaron Bentley
Rename fields
135
                            target_branch=self.target_branch,
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
136
                            testament_sha1=self.testament_sha1)
1551.12.26 by Aaron Bentley
Get email working, with optional message
137
        for key in ('source_branch', 'message'):
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
138
            if self.__dict__[key] is not None:
139
                stanza.add(key, self.__dict__[key])
1551.12.12 by Aaron Bentley
Add format header
140
        lines = ['# ' + self._format_string + '\n']
141
        lines.extend(rio.to_patch_lines(stanza))
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
142
        lines.append('# \n')
143
        if self.patch is not None:
144
            lines.extend(self.patch.splitlines(True))
145
        return lines
146
1551.12.16 by Aaron Bentley
Enable signing merge directives
147
    def to_signed(self, branch):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
148
        """Serialize as a signed string.
149
150
        :param branch: The source branch, to get the signing strategy
151
        :return: a string
152
        """
1551.12.16 by Aaron Bentley
Enable signing merge directives
153
        my_gpg = gpg.GPGStrategy(branch.get_config())
154
        return my_gpg.sign(''.join(self.to_lines()))
155
1551.12.26 by Aaron Bentley
Get email working, with optional message
156
    def to_email(self, mail_to, branch, sign=False):
1551.12.38 by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions
157
        """Serialize as an email message.
158
159
        :param mail_to: The address to mail the message to
160
        :param branch: The source branch, to get the signing strategy and
161
            source email address
162
        :param sign: If True, gpg-sign the email
163
        :return: an email message
164
        """
1551.12.26 by Aaron Bentley
Get email working, with optional message
165
        mail_from = branch.get_config().username()
166
        if self.message is not None:
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
167
            subject = self.message
1551.12.26 by Aaron Bentley
Get email working, with optional message
168
        else:
169
            revision = branch.repository.get_revision(self.revision_id)
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
170
            subject = revision.message
1551.12.26 by Aaron Bentley
Get email working, with optional message
171
        if sign:
172
            body = self.to_signed(branch)
173
        else:
174
            body = ''.join(self.to_lines())
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
175
        message = EmailMessage(mail_from, mail_to, subject, body)
1551.12.26 by Aaron Bentley
Get email working, with optional message
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
        """
2490.2.28 by Aaron Bentley
Fix handling of null revision
203
        t_revision_id = revision_id
204
        if revision_id == 'null:':
205
            t_revision_id = None
206
        t = testament.StrictTestament3.from_revision(repository, t_revision_id)
1551.12.50 by Aaron Bentley
Use public location of submit branch if possible
207
        submit_branch = _mod_branch.Branch.open(target_branch)
208
        if submit_branch.get_public_branch() is not None:
209
            target_branch = submit_branch.get_public_branch()
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
210
        if patch_type is None:
211
            patch = None
1551.12.2 by Aaron Bentley
Got directives round-tripping, with bundles and everything
212
        else:
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
213
            submit_revision_id = submit_branch.last_revision()
2490.2.28 by Aaron Bentley
Fix handling of null revision
214
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
215
            repository.fetch(submit_branch.repository, submit_revision_id)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
216
            graph = repository.get_graph()
217
            ancestor_id = graph.find_unique_lca(revision_id,
218
                                                submit_revision_id)
1551.12.39 by Aaron Bentley
Re-design patch handling to use a dict
219
            type_handler = {'bundle': klass._generate_bundle,
220
                            'diff': klass._generate_diff,
221
                            None: lambda x, y, z: None }
222
            patch = type_handler[patch_type](repository, revision_id,
223
                                             ancestor_id)
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
224
            if patch_type == 'bundle':
225
                s = StringIO()
226
                bundle_serializer.write_bundle(repository, revision_id,
227
                                               ancestor_id, s)
228
                patch = s.getvalue()
229
            elif patch_type == 'diff':
230
                patch = klass._generate_diff(repository, revision_id,
231
                                             ancestor_id)
1551.12.33 by Aaron Bentley
Take public_branch as a string, not object
232
1551.12.34 by Aaron Bentley
Check public branch only if not using a bundle
233
            if public_branch is not None and patch_type != 'bundle':
1551.12.33 by Aaron Bentley
Take public_branch as a string, not object
234
                public_branch_obj = _mod_branch.Branch.open(public_branch)
235
                if not public_branch_obj.repository.has_revision(revision_id):
236
                    raise errors.PublicBranchOutOfDate(public_branch,
237
                                                       revision_id)
238
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
239
        return MergeDirective(revision_id, t.as_sha1(), time, timezone,
1551.12.33 by Aaron Bentley
Take public_branch as a string, not object
240
                              target_branch, patch, patch_type, public_branch,
1551.12.27 by Aaron Bentley
support custom message everywhere
241
                              message)
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
242
243
    @staticmethod
244
    def _generate_diff(repository, revision_id, ancestor_id):
245
        tree_1 = repository.revision_tree(ancestor_id)
246
        tree_2 = repository.revision_tree(revision_id)
247
        s = StringIO()
1551.12.40 by Aaron Bentley
Do not show prefixes in diffs
248
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
1551.12.5 by Aaron Bentley
Get MergeDirective.from_objects working
249
        return s.getvalue()
1551.12.39 by Aaron Bentley
Re-design patch handling to use a dict
250
251
    @staticmethod
252
    def _generate_bundle(repository, revision_id, ancestor_id):
253
        s = StringIO()
254
        bundle_serializer.write_bundle(repository, revision_id,
255
                                       ancestor_id, s)
256
        return s.getvalue()
1551.14.4 by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables'
257
1551.14.9 by Aaron Bentley
rename get_target_revision to install_revisions
258
    def install_revisions(self, target_repo):
259
        """Install revisions and return the target revision"""
1551.14.4 by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables'
260
        if not target_repo.has_revision(self.revision_id):
261
            if self.patch_type == 'bundle':
1551.14.9 by Aaron Bentley
rename get_target_revision to install_revisions
262
                info = bundle_serializer.read_bundle(StringIO(self.patch))
263
                # We don't use the bundle's target revision, because
264
                # MergeDirective.revision_id is authoritative.
265
                info.install_revisions(target_repo)
1551.14.4 by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables'
266
            else:
267
                source_branch = _mod_branch.Branch.open(self.source_branch)
268
                target_repo.fetch(source_branch.repository, self.revision_id)
269
        return self.revision_id