36
class MergeDirective(object):
37
class _BaseMergeDirective(object):
39
def __init__(self, revision_id, testament_sha1, time, timezone,
40
target_branch, patch=None, source_branch=None, message=None,
44
:param revision_id: The revision to merge
45
:param testament_sha1: The sha1 of the testament of the revision to
47
:param time: The current POSIX timestamp time
48
:param timezone: The timezone offset
49
:param target_branch: The branch to apply the merge to
50
:param patch: The text of a diff or bundle
51
:param source_branch: A public location to merge the revision from
52
:param message: The message to use when committing this merge
54
self.revision_id = revision_id
55
self.testament_sha1 = testament_sha1
57
self.timezone = timezone
58
self.target_branch = target_branch
60
self.source_branch = source_branch
61
self.message = message
64
"""Serialize as a list of lines
66
:return: a list of lines
68
time_str = timestamp.format_patch_date(self.time, self.timezone)
69
stanza = rio.Stanza(revision_id=self.revision_id, timestamp=time_str,
70
target_branch=self.target_branch,
71
testament_sha1=self.testament_sha1)
72
for key in ('source_branch', 'message'):
73
if self.__dict__[key] is not None:
74
stanza.add(key, self.__dict__[key])
75
lines = ['# ' + self._format_string + '\n']
76
lines.extend(rio.to_patch_lines(stanza))
81
def from_objects(klass, repository, revision_id, time, timezone,
82
target_branch, patch_type='bundle',
83
local_target_branch=None, public_branch=None, message=None):
84
"""Generate a merge directive from various objects
86
:param repository: The repository containing the revision
87
:param revision_id: The revision to merge
88
:param time: The POSIX timestamp of the date the request was issued.
89
:param timezone: The timezone of the request
90
:param target_branch: The url of the branch to merge into
91
:param patch_type: 'bundle', 'diff' or None, depending on the type of
93
:param local_target_branch: a local copy of the target branch
94
:param public_branch: location of a public branch containing the target
96
:param message: Message to use when committing the merge
97
:return: The merge directive
99
The public branch is always used if supplied. If the patch_type is
100
not 'bundle', the public branch must be supplied, and will be verified.
102
If the message is not supplied, the message from revision_id will be
105
t_revision_id = revision_id
106
if revision_id == 'null:':
108
t = testament.StrictTestament3.from_revision(repository, t_revision_id)
109
submit_branch = _mod_branch.Branch.open(target_branch)
110
if submit_branch.get_public_branch() is not None:
111
target_branch = submit_branch.get_public_branch()
112
if patch_type is None:
115
submit_revision_id = submit_branch.last_revision()
116
submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
117
repository.fetch(submit_branch.repository, submit_revision_id)
118
graph = repository.get_graph()
119
ancestor_id = graph.find_unique_lca(revision_id,
121
type_handler = {'bundle': klass._generate_bundle,
122
'diff': klass._generate_diff,
123
None: lambda x, y, z: None }
124
patch = type_handler[patch_type](repository, revision_id,
127
if public_branch is not None and patch_type != 'bundle':
128
public_branch_obj = _mod_branch.Branch.open(public_branch)
129
if not public_branch_obj.repository.has_revision(revision_id):
130
raise errors.PublicBranchOutOfDate(public_branch,
133
return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
134
patch, patch_type, public_branch, message)
137
def _generate_diff(repository, revision_id, ancestor_id):
138
tree_1 = repository.revision_tree(ancestor_id)
139
tree_2 = repository.revision_tree(revision_id)
141
diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
145
def _generate_bundle(repository, revision_id, ancestor_id):
147
bundle_serializer.write_bundle(repository, revision_id,
152
class MergeDirective(_BaseMergeDirective):
38
154
"""A request to perform a merge into a branch.
68
184
:param source_branch: A public location to merge the revision from
69
185
:param message: The message to use when committing this merge
71
assert patch_type in (None, 'diff', 'bundle')
187
_BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
188
timezone, target_branch, patch, source_branch, message)
189
assert patch_type in (None, 'diff', 'bundle'), patch_type
72
190
if patch_type != 'bundle' and source_branch is None:
73
191
raise errors.NoMergeSource()
74
192
if patch_type is not None and patch is None:
75
193
raise errors.PatchMissing(patch_type)
76
self.revision_id = revision_id
77
self.testament_sha1 = testament_sha1
79
self.timezone = timezone
80
self.target_branch = target_branch
82
194
self.patch_type = patch_type
83
self.source_branch = source_branch
84
self.message = message
196
def clear_payload(self):
198
self.patch_type = None
201
if self.patch_type == 'bundle':
206
bundle = property(_bundle)
87
209
def from_lines(klass, lines):
178
291
message.set_payload(body)
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
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
194
:param local_target_branch: a local copy of the target branch
195
:param public_branch: location of a public branch containing the target
197
:param message: Message to use when committing the merge
198
:return: The merge directive
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.
203
If the message is not supplied, the message from revision_id will be
206
t_revision_id = revision_id
207
if revision_id == 'null:':
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:
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,
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,
227
if patch_type == 'bundle':
229
bundle_serializer.write_bundle(repository, revision_id,
232
elif patch_type == 'diff':
233
patch = klass._generate_diff(repository, revision_id,
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,
242
return MergeDirective(revision_id, t.as_sha1(), time, timezone,
243
target_branch, patch, patch_type, public_branch,
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)
251
diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
255
def _generate_bundle(repository, revision_id, ancestor_id):
257
bundle_serializer.write_bundle(repository, revision_id,
261
295
def install_revisions(self, target_repo):
262
296
"""Install revisions and return the target revision"""
270
304
source_branch = _mod_branch.Branch.open(self.source_branch)
271
305
target_repo.fetch(source_branch.repository, self.revision_id)
272
306
return self.revision_id
309
def _generate_bundle(repository, revision_id, ancestor_id):
311
bundle_serializer.write_bundle(repository, revision_id,
312
ancestor_id, s, '0.9')
315
class MergeDirective2(_BaseMergeDirective):
317
_format_string = 'Bazaar merge directive format 2 (Bazaar 0.18)'
319
def __init__(self, revision_id, testament_sha1, time, timezone,
320
target_branch, patch=None, source_branch=None, message=None,
322
if source_branch is None and bundle is None:
323
raise errors.NoMergeSource()
324
_BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
325
timezone, target_branch, patch, source_branch, message)
328
def _patch_type(self):
329
if self.bundle is not None:
331
elif self.patch is not None:
336
patch_type = property(_patch_type)
338
def clear_payload(self):
343
def _from_lines(klass, line_iter):
344
stanza = rio.read_patch_stanza(line_iter)
348
start = line_iter.next()
349
except StopIteration:
352
if start.startswith('# Begin patch'):
354
for line in line_iter:
355
if line.startswith('# Begin bundle'):
358
patch_lines.append(line)
361
patch = ''.join(patch_lines)
362
if start is not None:
363
if start.startswith('# Begin bundle'):
364
bundle = ''.join(line_iter)
366
raise errors.IllegalMergeDirectivePayload(start)
367
time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
369
for key in ('revision_id', 'testament_sha1', 'target_branch',
370
'source_branch', 'message'):
372
kwargs[key] = stanza.get(key)
375
kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
376
return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
380
lines = self._to_lines()
381
if self.patch is not None:
382
lines.append('# Begin patch\n')
383
lines.extend(self.patch.splitlines(True))
384
if self.bundle is not None:
385
lines.append('# Begin bundle\n')
386
lines.extend(self.bundle.splitlines(True))
390
def from_objects(klass, repository, revision_id, time, timezone,
391
target_branch, patch_type='bundle',
392
local_target_branch=None, public_branch=None, message=None):
393
"""Generate a merge directive from various objects
395
:param repository: The repository containing the revision
396
:param revision_id: The revision to merge
397
:param time: The POSIX timestamp of the date the request was issued.
398
:param timezone: The timezone of the request
399
:param target_branch: The url of the branch to merge into
400
:param patch_type: 'bundle', 'diff' or None, depending on the type of
402
:param local_target_branch: a local copy of the target branch
403
:param public_branch: location of a public branch containing the target
405
:param message: Message to use when committing the merge
406
:return: The merge directive
408
The public branch is always used if supplied. If the patch_type is
409
not 'bundle', the public branch must be supplied, and will be verified.
411
If the message is not supplied, the message from revision_id will be
414
t_revision_id = revision_id
415
if revision_id == 'null:':
417
t = testament.StrictTestament3.from_revision(repository, t_revision_id)
418
submit_branch = _mod_branch.Branch.open(target_branch)
419
if submit_branch.get_public_branch() is not None:
420
target_branch = submit_branch.get_public_branch()
421
if patch_type is None:
425
submit_revision_id = submit_branch.last_revision()
426
submit_revision_id = _mod_revision.ensure_null(submit_revision_id)
427
repository.fetch(submit_branch.repository, submit_revision_id)
428
graph = repository.get_graph()
429
ancestor_id = graph.find_unique_lca(revision_id,
431
if patch_type in ('bundle', 'diff'):
432
patch = klass._generate_diff(repository, revision_id,
434
if patch_type == 'bundle':
435
bundle = klass._generate_bundle(repository, revision_id,
440
if public_branch is not None and patch_type != 'bundle':
441
public_branch_obj = _mod_branch.Branch.open(public_branch)
442
if not public_branch_obj.repository.has_revision(revision_id):
443
raise errors.PublicBranchOutOfDate(public_branch,
446
return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
447
patch, public_branch, message, bundle)
449
class MergeDirectiveFormatRegistry(registry.Registry):
451
def register(self, directive):
452
registry.Registry.register(self, directive._format_string, directive)
455
_format_registry = MergeDirectiveFormatRegistry()
456
_format_registry.register(MergeDirective)
457
_format_registry.register(MergeDirective2)