/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge_directive.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-24 15:16:40 UTC
  • mto: (2547.2.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2550.
  • Revision ID: v.ladeuil+lp@free.fr-20070624151640-5tg62jb5g9tqvybd
Fix 121889 by working around urllib2 bug.

* tests/HTTPTestUtil.py:
(DigestAuthRequestHandler.send_header_auth_reqed): python-2.4.1
fail to decode the header without the quotes. The RFC do not
require them, python >= 2.4.4 handles them gracefully. Not a big
deal.

* tests/test_http.py:
(TestAuth.setUp): Add a comment in hope I will not running around
shouting: "Who takes my traces ? Gimme my traces !" when running
the only tests who capture their own traces without showing them.

* transport/http/_urllib2_wrappers.py (BasicAuthHandler,
DigestAuthHandler): Not directly related to the bug, bug good to
fix anyway, the digest auth should be preferred to the basic
one. To do so, the digest handler should be tried before the basic
one.

* builtins.py:
(cmd_selftest.run): Fix typo. Note to reviewers: No, it's not
related to the bug. No there are no tests for that. No I don't
intend to write some :) But I'll understand if you veto that
because you want to take care of it :D

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
18
from email import Message
 
19
from StringIO import StringIO
 
20
 
 
21
from bzrlib import (
 
22
    branch as _mod_branch,
 
23
    diff,
 
24
    errors,
 
25
    gpg,
 
26
    revision as _mod_revision,
 
27
    rio,
 
28
    testament,
 
29
    timestamp,
 
30
    )
 
31
from bzrlib.bundle import (
 
32
    serializer as bundle_serializer,
 
33
    )
 
34
 
 
35
 
 
36
class MergeDirective(object):
 
37
 
 
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
 
 
52
    _format_string = 'Bazaar merge directive format 1'
 
53
 
 
54
    def __init__(self, revision_id, testament_sha1, time, timezone,
 
55
                 target_branch, patch=None, patch_type=None,
 
56
                 source_branch=None, message=None):
 
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
        """
 
71
        assert patch_type in (None, 'diff', 'bundle')
 
72
        if patch_type != 'bundle' and source_branch is None:
 
73
            raise errors.NoMergeSource()
 
74
        if patch_type is not None and patch is None:
 
75
            raise errors.PatchMissing(patch_type)
 
76
        self.revision_id = revision_id
 
77
        self.testament_sha1 = testament_sha1
 
78
        self.time = time
 
79
        self.timezone = timezone
 
80
        self.target_branch = target_branch
 
81
        self.patch = patch
 
82
        self.patch_type = patch_type
 
83
        self.source_branch = source_branch
 
84
        self.message = message
 
85
 
 
86
    @classmethod
 
87
    def from_lines(klass, lines):
 
88
        """Deserialize a MergeRequest from an iterable of lines
 
89
 
 
90
        :param lines: An iterable of lines
 
91
        :return: a MergeRequest
 
92
        """
 
93
        line_iter = iter(lines)
 
94
        for line in line_iter:
 
95
            if line.startswith('# ' + klass._format_string):
 
96
                break
 
97
        else:
 
98
            if len(lines) > 0:
 
99
                raise errors.NotAMergeDirective(lines[0])
 
100
            else:
 
101
                raise errors.NotAMergeDirective('')
 
102
        stanza = rio.read_patch_stanza(line_iter)
 
103
        patch_lines = list(line_iter)
 
104
        if len(patch_lines) == 0:
 
105
            patch = None
 
106
            patch_type = None
 
107
        else:
 
108
            patch = ''.join(patch_lines)
 
109
            try:
 
110
                bundle_serializer.read_bundle(StringIO(patch))
 
111
            except (errors.NotABundle, errors.BundleNotSupported,
 
112
                    errors.BadBundle):
 
113
                patch_type = 'diff'
 
114
            else:
 
115
                patch_type = 'bundle'
 
116
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
 
117
        kwargs = {}
 
118
        for key in ('revision_id', 'testament_sha1', 'target_branch',
 
119
                    'source_branch', 'message'):
 
120
            try:
 
121
                kwargs[key] = stanza.get(key)
 
122
            except KeyError:
 
123
                pass
 
124
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
 
125
        return MergeDirective(time=time, timezone=timezone,
 
126
                              patch_type=patch_type, patch=patch, **kwargs)
 
127
 
 
128
    def to_lines(self):
 
129
        """Serialize as a list of lines
 
130
 
 
131
        :return: a list of lines
 
132
        """
 
133
        time_str = timestamp.format_patch_date(self.time, self.timezone)
 
134
        stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str,
 
135
                            target_branch=self.target_branch,
 
136
                            testament_sha1=self.testament_sha1)
 
137
        for key in ('source_branch', 'message'):
 
138
            if self.__dict__[key] is not None:
 
139
                stanza.add(key, self.__dict__[key])
 
140
        lines = ['# ' + self._format_string + '\n']
 
141
        lines.extend(rio.to_patch_lines(stanza))
 
142
        lines.append('# \n')
 
143
        if self.patch is not None:
 
144
            lines.extend(self.patch.splitlines(True))
 
145
        return lines
 
146
 
 
147
    def to_signed(self, branch):
 
148
        """Serialize as a signed string.
 
149
 
 
150
        :param branch: The source branch, to get the signing strategy
 
151
        :return: a string
 
152
        """
 
153
        my_gpg = gpg.GPGStrategy(branch.get_config())
 
154
        return my_gpg.sign(''.join(self.to_lines()))
 
155
 
 
156
    def to_email(self, mail_to, branch, sign=False):
 
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
        """
 
165
        mail_from = branch.get_config().username()
 
166
        message = Message.Message()
 
167
        message['To'] = mail_to
 
168
        message['From'] = mail_from
 
169
        if self.message is not None:
 
170
            message['Subject'] = self.message
 
171
        else:
 
172
            revision = branch.repository.get_revision(self.revision_id)
 
173
            message['Subject'] = revision.message
 
174
        if sign:
 
175
            body = self.to_signed(branch)
 
176
        else:
 
177
            body = ''.join(self.to_lines())
 
178
        message.set_payload(body)
 
179
        return message
 
180
 
 
181
    @classmethod
 
182
    def from_objects(klass, repository, revision_id, time, timezone,
 
183
                 target_branch, patch_type='bundle',
 
184
                 local_target_branch=None, public_branch=None, message=None):
 
185
        """Generate a merge directive from various objects
 
186
 
 
187
        :param repository: The repository containing the revision
 
188
        :param revision_id: The revision to merge
 
189
        :param time: The POSIX timestamp of the date the request was issued.
 
190
        :param timezone: The timezone of the request
 
191
        :param target_branch: The url of the branch to merge into
 
192
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
 
193
            patch desired.
 
194
        :param local_target_branch: a local copy of the target branch
 
195
        :param public_branch: location of a public branch containing the target
 
196
            revision.
 
197
        :param message: Message to use when committing the merge
 
198
        :return: The merge directive
 
199
 
 
200
        The public branch is always used if supplied.  If the patch_type is
 
201
        not 'bundle', the public branch must be supplied, and will be verified.
 
202
 
 
203
        If the message is not supplied, the message from revision_id will be
 
204
        used for the commit.
 
205
        """
 
206
        t_revision_id = revision_id
 
207
        if revision_id == 'null:':
 
208
            t_revision_id = None
 
209
        t = testament.StrictTestament3.from_revision(repository, t_revision_id)
 
210
        submit_branch = _mod_branch.Branch.open(target_branch)
 
211
        if submit_branch.get_public_branch() is not None:
 
212
            target_branch = submit_branch.get_public_branch()
 
213
        if patch_type is None:
 
214
            patch = None
 
215
        else:
 
216
            submit_revision_id = submit_branch.last_revision()
 
217
            submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
 
218
            repository.fetch(submit_branch.repository, submit_revision_id)
 
219
            graph = repository.get_graph()
 
220
            ancestor_id = graph.find_unique_lca(revision_id,
 
221
                                                submit_revision_id)
 
222
            type_handler = {'bundle': klass._generate_bundle,
 
223
                            'diff': klass._generate_diff,
 
224
                            None: lambda x, y, z: None }
 
225
            patch = type_handler[patch_type](repository, revision_id,
 
226
                                             ancestor_id)
 
227
            if patch_type == 'bundle':
 
228
                s = StringIO()
 
229
                bundle_serializer.write_bundle(repository, revision_id,
 
230
                                               ancestor_id, s)
 
231
                patch = s.getvalue()
 
232
            elif patch_type == 'diff':
 
233
                patch = klass._generate_diff(repository, revision_id,
 
234
                                             ancestor_id)
 
235
 
 
236
            if public_branch is not None and patch_type != 'bundle':
 
237
                public_branch_obj = _mod_branch.Branch.open(public_branch)
 
238
                if not public_branch_obj.repository.has_revision(revision_id):
 
239
                    raise errors.PublicBranchOutOfDate(public_branch,
 
240
                                                       revision_id)
 
241
 
 
242
        return MergeDirective(revision_id, t.as_sha1(), time, timezone,
 
243
                              target_branch, patch, patch_type, public_branch,
 
244
                              message)
 
245
 
 
246
    @staticmethod
 
247
    def _generate_diff(repository, revision_id, ancestor_id):
 
248
        tree_1 = repository.revision_tree(ancestor_id)
 
249
        tree_2 = repository.revision_tree(revision_id)
 
250
        s = StringIO()
 
251
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
 
252
        return s.getvalue()
 
253
 
 
254
    @staticmethod
 
255
    def _generate_bundle(repository, revision_id, ancestor_id):
 
256
        s = StringIO()
 
257
        bundle_serializer.write_bundle(repository, revision_id,
 
258
                                       ancestor_id, s)
 
259
        return s.getvalue()
 
260
 
 
261
    def install_revisions(self, target_repo):
 
262
        """Install revisions and return the target revision"""
 
263
        if not target_repo.has_revision(self.revision_id):
 
264
            if self.patch_type == 'bundle':
 
265
                info = bundle_serializer.read_bundle(StringIO(self.patch))
 
266
                # We don't use the bundle's target revision, because
 
267
                # MergeDirective.revision_id is authoritative.
 
268
                info.install_revisions(target_repo)
 
269
            else:
 
270
                source_branch = _mod_branch.Branch.open(self.source_branch)
 
271
                target_repo.fetch(source_branch.repository, self.revision_id)
 
272
        return self.revision_id