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 |
)
|
1551.14.4
by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables' |
31 |
from bzrlib.bundle import ( |
32 |
serializer as bundle_serializer, |
|
33 |
)
|
|
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)) |
|
111 |
except errors.NotABundle: |
|
112 |
patch_type = 'diff' |
|
113 |
else: |
|
114 |
patch_type = 'bundle' |
|
1551.12.30
by Aaron Bentley
Use patch-style dates for timestamps in merge directives |
115 |
time, timezone = timestamp.parse_patch_date(stanza.get('timestamp')) |
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
116 |
kwargs = {} |
1551.12.13
by Aaron Bentley
Rename fields |
117 |
for key in ('revision_id', 'testament_sha1', 'target_branch', |
1551.12.26
by Aaron Bentley
Get email working, with optional message |
118 |
'source_branch', 'message'): |
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
119 |
try: |
120 |
kwargs[key] = stanza.get(key) |
|
121 |
except KeyError: |
|
122 |
pass
|
|
1551.12.54
by Aaron Bentley
Decoded revision ids are utf-8 |
123 |
kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8') |
1551.12.3
by Aaron Bentley
Add timestamps to merge directives |
124 |
return MergeDirective(time=time, timezone=timezone, |
125 |
patch_type=patch_type, patch=patch, **kwargs) |
|
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
126 |
|
127 |
def to_lines(self): |
|
1551.12.38
by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions |
128 |
"""Serialize as a list of lines
|
129 |
||
130 |
:return: a list of lines
|
|
131 |
"""
|
|
1551.12.30
by Aaron Bentley
Use patch-style dates for timestamps in merge directives |
132 |
time_str = timestamp.format_patch_date(self.time, self.timezone) |
133 |
stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str, |
|
1551.12.13
by Aaron Bentley
Rename fields |
134 |
target_branch=self.target_branch, |
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
135 |
testament_sha1=self.testament_sha1) |
1551.12.26
by Aaron Bentley
Get email working, with optional message |
136 |
for key in ('source_branch', 'message'): |
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
137 |
if self.__dict__[key] is not None: |
138 |
stanza.add(key, self.__dict__[key]) |
|
1551.12.12
by Aaron Bentley
Add format header |
139 |
lines = ['# ' + self._format_string + '\n'] |
140 |
lines.extend(rio.to_patch_lines(stanza)) |
|
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
141 |
lines.append('# \n') |
142 |
if self.patch is not None: |
|
143 |
lines.extend(self.patch.splitlines(True)) |
|
144 |
return lines |
|
145 |
||
1551.12.16
by Aaron Bentley
Enable signing merge directives |
146 |
def to_signed(self, branch): |
1551.12.38
by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions |
147 |
"""Serialize as a signed string.
|
148 |
||
149 |
:param branch: The source branch, to get the signing strategy
|
|
150 |
:return: a string
|
|
151 |
"""
|
|
1551.12.16
by Aaron Bentley
Enable signing merge directives |
152 |
my_gpg = gpg.GPGStrategy(branch.get_config()) |
153 |
return my_gpg.sign(''.join(self.to_lines())) |
|
154 |
||
1551.12.26
by Aaron Bentley
Get email working, with optional message |
155 |
def to_email(self, mail_to, branch, sign=False): |
1551.12.38
by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions |
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 |
"""
|
|
1551.12.26
by Aaron Bentley
Get email working, with optional message |
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 |
||
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
180 |
@classmethod
|
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
181 |
def from_objects(klass, repository, revision_id, time, timezone, |
1551.12.13
by Aaron Bentley
Rename fields |
182 |
target_branch, patch_type='bundle', |
1551.12.27
by Aaron Bentley
support custom message everywhere |
183 |
local_target_branch=None, public_branch=None, message=None): |
1551.12.38
by Aaron Bentley
Add docs for MergeDirective and RIO-patch functions |
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 |
"""
|
|
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
205 |
t = testament.StrictTestament3.from_revision(repository, revision_id) |
1551.12.50
by Aaron Bentley
Use public location of submit branch if possible |
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() |
|
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
209 |
if patch_type is None: |
210 |
patch = None |
|
1551.12.2
by Aaron Bentley
Got directives round-tripping, with bundles and everything |
211 |
else: |
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
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) |
|
1551.12.39
by Aaron Bentley
Re-design patch handling to use a dict |
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) |
|
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
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) |
|
1551.12.33
by Aaron Bentley
Take public_branch as a string, not object |
230 |
|
1551.12.34
by Aaron Bentley
Check public branch only if not using a bundle |
231 |
if public_branch is not None and patch_type != 'bundle': |
1551.12.33
by Aaron Bentley
Take public_branch as a string, not object |
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 |
||
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
237 |
return MergeDirective(revision_id, t.as_sha1(), time, timezone, |
1551.12.33
by Aaron Bentley
Take public_branch as a string, not object |
238 |
target_branch, patch, patch_type, public_branch, |
1551.12.27
by Aaron Bentley
support custom message everywhere |
239 |
message) |
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
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() |
|
1551.12.40
by Aaron Bentley
Do not show prefixes in diffs |
246 |
diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='') |
1551.12.5
by Aaron Bentley
Get MergeDirective.from_objects working |
247 |
return s.getvalue() |
1551.12.39
by Aaron Bentley
Re-design patch handling to use a dict |
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() |
|
1551.14.4
by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables' |
255 |
|
1551.14.9
by Aaron Bentley
rename get_target_revision to install_revisions |
256 |
def install_revisions(self, target_repo): |
257 |
"""Install revisions and return the target revision"""
|
|
1551.14.4
by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables' |
258 |
if not target_repo.has_revision(self.revision_id): |
259 |
if self.patch_type == 'bundle': |
|
1551.14.9
by Aaron Bentley
rename get_target_revision to install_revisions |
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) |
|
1551.14.4
by Aaron Bentley
Change bundle reader and merge directive to both be 'mergeables' |
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 |