/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: John Arbash Meinel
  • Date: 2007-05-31 20:29:04 UTC
  • mto: This revision was merged to the branch mainline in revision 2499.
  • Revision ID: john@arbash-meinel.com-20070531202904-34h7ygudo7qq9ha1
Update the code so that symlinks aren't cached at incorrect times
and fix the tests so that they don't assume files and symlinks
get cached even when the timestamp doesn't declare them 'safe'.

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:
 
112
                patch_type = 'diff'
 
113
            else:
 
114
                patch_type = 'bundle'
 
115
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
 
116
        kwargs = {}
 
117
        for key in ('revision_id', 'testament_sha1', 'target_branch',
 
118
                    'source_branch', 'message'):
 
119
            try:
 
120
                kwargs[key] = stanza.get(key)
 
121
            except KeyError:
 
122
                pass
 
123
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
 
124
        return MergeDirective(time=time, timezone=timezone,
 
125
                              patch_type=patch_type, patch=patch, **kwargs)
 
126
 
 
127
    def to_lines(self):
 
128
        """Serialize as a list of lines
 
129
 
 
130
        :return: a list of lines
 
131
        """
 
132
        time_str = timestamp.format_patch_date(self.time, self.timezone)
 
133
        stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str,
 
134
                            target_branch=self.target_branch,
 
135
                            testament_sha1=self.testament_sha1)
 
136
        for key in ('source_branch', 'message'):
 
137
            if self.__dict__[key] is not None:
 
138
                stanza.add(key, self.__dict__[key])
 
139
        lines = ['# ' + self._format_string + '\n']
 
140
        lines.extend(rio.to_patch_lines(stanza))
 
141
        lines.append('# \n')
 
142
        if self.patch is not None:
 
143
            lines.extend(self.patch.splitlines(True))
 
144
        return lines
 
145
 
 
146
    def to_signed(self, branch):
 
147
        """Serialize as a signed string.
 
148
 
 
149
        :param branch: The source branch, to get the signing strategy
 
150
        :return: a string
 
151
        """
 
152
        my_gpg = gpg.GPGStrategy(branch.get_config())
 
153
        return my_gpg.sign(''.join(self.to_lines()))
 
154
 
 
155
    def to_email(self, mail_to, branch, sign=False):
 
156
        """Serialize as an email message.
 
157
 
 
158
        :param mail_to: The address to mail the message to
 
159
        :param branch: The source branch, to get the signing strategy and
 
160
            source email address
 
161
        :param sign: If True, gpg-sign the email
 
162
        :return: an email message
 
163
        """
 
164
        mail_from = branch.get_config().username()
 
165
        message = Message.Message()
 
166
        message['To'] = mail_to
 
167
        message['From'] = mail_from
 
168
        if self.message is not None:
 
169
            message['Subject'] = self.message
 
170
        else:
 
171
            revision = branch.repository.get_revision(self.revision_id)
 
172
            message['Subject'] = revision.message
 
173
        if sign:
 
174
            body = self.to_signed(branch)
 
175
        else:
 
176
            body = ''.join(self.to_lines())
 
177
        message.set_payload(body)
 
178
        return message
 
179
 
 
180
    @classmethod
 
181
    def from_objects(klass, repository, revision_id, time, timezone,
 
182
                 target_branch, patch_type='bundle',
 
183
                 local_target_branch=None, public_branch=None, message=None):
 
184
        """Generate a merge directive from various objects
 
185
 
 
186
        :param repository: The repository containing the revision
 
187
        :param revision_id: The revision to merge
 
188
        :param time: The POSIX timestamp of the date the request was issued.
 
189
        :param timezone: The timezone of the request
 
190
        :param target_branch: The url of the branch to merge into
 
191
        :param patch_type: 'bundle', 'diff' or None, depending on the type of
 
192
            patch desired.
 
193
        :param local_target_branch: a local copy of the target branch
 
194
        :param public_branch: location of a public branch containing the target
 
195
            revision.
 
196
        :param message: Message to use when committing the merge
 
197
        :return: The merge directive
 
198
 
 
199
        The public branch is always used if supplied.  If the patch_type is
 
200
        not 'bundle', the public branch must be supplied, and will be verified.
 
201
 
 
202
        If the message is not supplied, the message from revision_id will be
 
203
        used for the commit.
 
204
        """
 
205
        t = testament.StrictTestament3.from_revision(repository, revision_id)
 
206
        submit_branch = _mod_branch.Branch.open(target_branch)
 
207
        if submit_branch.get_public_branch() is not None:
 
208
            target_branch = submit_branch.get_public_branch()
 
209
        if patch_type is None:
 
210
            patch = None
 
211
        else:
 
212
            submit_revision_id = submit_branch.last_revision()
 
213
            repository.fetch(submit_branch.repository, submit_revision_id)
 
214
            ancestor_id = _mod_revision.common_ancestor(revision_id,
 
215
                                                        submit_revision_id,
 
216
                                                        repository)
 
217
            type_handler = {'bundle': klass._generate_bundle,
 
218
                            'diff': klass._generate_diff,
 
219
                            None: lambda x, y, z: None }
 
220
            patch = type_handler[patch_type](repository, revision_id,
 
221
                                             ancestor_id)
 
222
            if patch_type == 'bundle':
 
223
                s = StringIO()
 
224
                bundle_serializer.write_bundle(repository, revision_id,
 
225
                                               ancestor_id, s)
 
226
                patch = s.getvalue()
 
227
            elif patch_type == 'diff':
 
228
                patch = klass._generate_diff(repository, revision_id,
 
229
                                             ancestor_id)
 
230
 
 
231
            if public_branch is not None and patch_type != 'bundle':
 
232
                public_branch_obj = _mod_branch.Branch.open(public_branch)
 
233
                if not public_branch_obj.repository.has_revision(revision_id):
 
234
                    raise errors.PublicBranchOutOfDate(public_branch,
 
235
                                                       revision_id)
 
236
 
 
237
        return MergeDirective(revision_id, t.as_sha1(), time, timezone,
 
238
                              target_branch, patch, patch_type, public_branch,
 
239
                              message)
 
240
 
 
241
    @staticmethod
 
242
    def _generate_diff(repository, revision_id, ancestor_id):
 
243
        tree_1 = repository.revision_tree(ancestor_id)
 
244
        tree_2 = repository.revision_tree(revision_id)
 
245
        s = StringIO()
 
246
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
 
247
        return s.getvalue()
 
248
 
 
249
    @staticmethod
 
250
    def _generate_bundle(repository, revision_id, ancestor_id):
 
251
        s = StringIO()
 
252
        bundle_serializer.write_bundle(repository, revision_id,
 
253
                                       ancestor_id, s)
 
254
        return s.getvalue()
 
255
 
 
256
    def install_revisions(self, target_repo):
 
257
        """Install revisions and return the target revision"""
 
258
        if not target_repo.has_revision(self.revision_id):
 
259
            if self.patch_type == 'bundle':
 
260
                info = bundle_serializer.read_bundle(StringIO(self.patch))
 
261
                # We don't use the bundle's target revision, because
 
262
                # MergeDirective.revision_id is authoritative.
 
263
                info.install_revisions(target_repo)
 
264
            else:
 
265
                source_branch = _mod_branch.Branch.open(self.source_branch)
 
266
                target_repo.fetch(source_branch.repository, self.revision_id)
 
267
        return self.revision_id