/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: Aaron Bentley
  • Date: 2007-06-21 23:43:17 UTC
  • mto: (2520.5.2 bzr.mpbundle)
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: abentley@panoramicfeedback.com-20070621234317-5w3h8h36oe90sups
Implement new merge directive format

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
    diff,
24
24
    errors,
25
25
    gpg,
 
26
    registry,
26
27
    revision as _mod_revision,
27
28
    rio,
28
29
    testament,
33
34
    )
34
35
 
35
36
 
36
 
class MergeDirective(object):
 
37
class _BaseMergeDirective(object):
 
38
 
 
39
    def __init__(self, revision_id, testament_sha1, time, timezone,
 
40
                 target_branch, patch=None, source_branch=None, message=None,
 
41
                 bundle=None):
 
42
        """Constructor.
 
43
 
 
44
        :param revision_id: The revision to merge
 
45
        :param testament_sha1: The sha1 of the testament of the revision to
 
46
            merge.
 
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
 
53
        """
 
54
        self.revision_id = revision_id
 
55
        self.testament_sha1 = testament_sha1
 
56
        self.time = time
 
57
        self.timezone = timezone
 
58
        self.target_branch = target_branch
 
59
        self.patch = patch
 
60
        self.source_branch = source_branch
 
61
        self.message = message
 
62
 
 
63
    def _to_lines(self):
 
64
        """Serialize as a list of lines
 
65
 
 
66
        :return: a list of lines
 
67
        """
 
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))
 
77
        lines.append('# \n')
 
78
        return lines
 
79
 
 
80
    @classmethod
 
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
 
85
 
 
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
 
92
            patch desired.
 
93
        :param local_target_branch: a local copy of the target branch
 
94
        :param public_branch: location of a public branch containing the target
 
95
            revision.
 
96
        :param message: Message to use when committing the merge
 
97
        :return: The merge directive
 
98
 
 
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.
 
101
 
 
102
        If the message is not supplied, the message from revision_id will be
 
103
        used for the commit.
 
104
        """
 
105
        t_revision_id = revision_id
 
106
        if revision_id == 'null:':
 
107
            t_revision_id = None
 
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:
 
113
            patch = None
 
114
        else:
 
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,
 
120
                                                submit_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,
 
125
                                             ancestor_id)
 
126
 
 
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,
 
131
                                                       revision_id)
 
132
 
 
133
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
 
134
            patch, patch_type, public_branch, message)
 
135
 
 
136
    @staticmethod
 
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)
 
140
        s = StringIO()
 
141
        diff.show_diff_trees(tree_1, tree_2, s, old_label='', new_label='')
 
142
        return s.getvalue()
 
143
 
 
144
    @staticmethod
 
145
    def _generate_bundle(repository, revision_id, ancestor_id):
 
146
        s = StringIO()
 
147
        bundle_serializer.write_bundle(repository, revision_id,
 
148
                                       ancestor_id, s)
 
149
        return s.getvalue()
 
150
 
 
151
 
 
152
class MergeDirective(_BaseMergeDirective):
37
153
 
38
154
    """A request to perform a merge into a branch.
39
155
 
53
169
 
54
170
    def __init__(self, revision_id, testament_sha1, time, timezone,
55
171
                 target_branch, patch=None, patch_type=None,
56
 
                 source_branch=None, message=None):
 
172
                 source_branch=None, message=None, bundle=None):
57
173
        """Constructor.
58
174
 
59
175
        :param revision_id: The revision to merge
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
70
186
        """
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
78
 
        self.time = time
79
 
        self.timezone = timezone
80
 
        self.target_branch = target_branch
81
 
        self.patch = patch
82
194
        self.patch_type = patch_type
83
 
        self.source_branch = source_branch
84
 
        self.message = message
 
195
 
 
196
    def clear_payload(self):
 
197
        self.patch = None
 
198
        self.patch_type = None
 
199
 
 
200
    def _bundle(self):
 
201
        if self.patch_type == 'bundle':
 
202
            return self.patch
 
203
        else:
 
204
            return None
 
205
 
 
206
    bundle = property(_bundle)
85
207
 
86
208
    @classmethod
87
209
    def from_lines(klass, lines):
92
214
        """
93
215
        line_iter = iter(lines)
94
216
        for line in line_iter:
95
 
            if line.startswith('# ' + klass._format_string):
 
217
            if line.startswith('# Bazaar merge directive format '):
96
218
                break
97
219
        else:
98
220
            if len(lines) > 0:
99
221
                raise errors.NotAMergeDirective(lines[0])
100
222
            else:
101
223
                raise errors.NotAMergeDirective('')
 
224
        return _format_registry.get(line[2:].rstrip())._from_lines(line_iter)
 
225
 
 
226
    @classmethod
 
227
    def _from_lines(klass, line_iter):
102
228
        stanza = rio.read_patch_stanza(line_iter)
103
229
        patch_lines = list(line_iter)
104
230
        if len(patch_lines) == 0:
126
252
                              patch_type=patch_type, patch=patch, **kwargs)
127
253
 
128
254
    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')
 
255
        lines = self._to_lines()
143
256
        if self.patch is not None:
144
257
            lines.extend(self.patch.splitlines(True))
145
258
        return lines
178
291
        message.set_payload(body)
179
292
        return message
180
293
 
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
294
 
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
 
307
 
 
308
    @staticmethod
 
309
    def _generate_bundle(repository, revision_id, ancestor_id):
 
310
        s = StringIO()
 
311
        bundle_serializer.write_bundle(repository, revision_id,
 
312
                                       ancestor_id, s, '0.9')
 
313
        return s.getvalue()
 
314
 
 
315
class MergeDirective2(_BaseMergeDirective):
 
316
 
 
317
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.18)'
 
318
 
 
319
    def __init__(self, revision_id, testament_sha1, time, timezone,
 
320
                 target_branch, patch=None, source_branch=None, message=None,
 
321
                 bundle=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)
 
326
        self.bundle = bundle
 
327
 
 
328
    def _patch_type(self):
 
329
        if self.bundle is not None:
 
330
            return 'bundle'
 
331
        elif self.patch is not None:
 
332
            return 'diff'
 
333
        else:
 
334
            return None
 
335
 
 
336
    patch_type = property(_patch_type)
 
337
 
 
338
    def clear_payload(self):
 
339
        self.patch = None
 
340
        self.bundle = None
 
341
 
 
342
    @classmethod
 
343
    def _from_lines(klass, line_iter):
 
344
        stanza = rio.read_patch_stanza(line_iter)
 
345
        patch = None
 
346
        bundle = None
 
347
        try:
 
348
            start = line_iter.next()
 
349
        except StopIteration:
 
350
            pass
 
351
        else:
 
352
            if start.startswith('# Begin patch'):
 
353
                patch_lines = []
 
354
                for line in line_iter:
 
355
                    if line.startswith('# Begin bundle'):
 
356
                        start = line
 
357
                        break
 
358
                    patch_lines.append(line)
 
359
                else:
 
360
                    start = None
 
361
                patch = ''.join(patch_lines)
 
362
            if start is not None:
 
363
                if start.startswith('# Begin bundle'):
 
364
                    bundle = ''.join(line_iter)
 
365
                else:
 
366
                    raise errors.IllegalMergeDirectivePayload(start)
 
367
        time, timezone = timestamp.parse_patch_date(stanza.get('timestamp'))
 
368
        kwargs = {}
 
369
        for key in ('revision_id', 'testament_sha1', 'target_branch',
 
370
                    'source_branch', 'message'):
 
371
            try:
 
372
                kwargs[key] = stanza.get(key)
 
373
            except KeyError:
 
374
                pass
 
375
        kwargs['revision_id'] = kwargs['revision_id'].encode('utf-8')
 
376
        return klass(time=time, timezone=timezone, patch=patch, bundle=bundle,
 
377
                     **kwargs)
 
378
 
 
379
    def to_lines(self):
 
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))
 
387
        return lines
 
388
 
 
389
    @classmethod
 
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
 
394
 
 
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
 
401
            patch desired.
 
402
        :param local_target_branch: a local copy of the target branch
 
403
        :param public_branch: location of a public branch containing the target
 
404
            revision.
 
405
        :param message: Message to use when committing the merge
 
406
        :return: The merge directive
 
407
 
 
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.
 
410
 
 
411
        If the message is not supplied, the message from revision_id will be
 
412
        used for the commit.
 
413
        """
 
414
        t_revision_id = revision_id
 
415
        if revision_id == 'null:':
 
416
            t_revision_id = None
 
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:
 
422
            patch = None
 
423
            bundle = None
 
424
        else:
 
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,
 
430
                                                submit_revision_id)
 
431
            if patch_type in ('bundle', 'diff'):
 
432
                patch = klass._generate_diff(repository, revision_id,
 
433
                                             ancestor_id)
 
434
            if patch_type == 'bundle':
 
435
                bundle = klass._generate_bundle(repository, revision_id,
 
436
                                               ancestor_id)
 
437
            else:
 
438
                bundle = None
 
439
 
 
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,
 
444
                                                       revision_id)
 
445
 
 
446
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
 
447
            patch, public_branch, message, bundle)
 
448
 
 
449
class MergeDirectiveFormatRegistry(registry.Registry):
 
450
 
 
451
    def register(self, directive):
 
452
        registry.Registry.register(self, directive._format_string, directive)
 
453
 
 
454
 
 
455
_format_registry = MergeDirectiveFormatRegistry()
 
456
_format_registry.register(MergeDirective)
 
457
_format_registry.register(MergeDirective2)