/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: Robert Collins
  • Date: 2007-03-25 08:59:56 UTC
  • mto: (2376.3.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2401.
  • Revision ID: robertc@robertcollins.net-20070325085956-my8jv7cifqzyltyz
New SmartServer hooks facility. There are two initial hooks documented
in bzrlib.transport.smart.SmartServerHooks. The two initial hooks allow
plugins to execute code upon server startup and shutdown.
(Robert Collins).

SmartServer in standalone mode will now close its listening socket
when it stops, rather than waiting for garbage collection. This primarily
fixes test suite hangs when a test tries to connect to a shutdown server.
It may also help improve behaviour when dealing with a server running
on a specific port (rather than dynamically assigned ports).
(Robert Collins)

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