/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-07-04 23:38:26 UTC
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: aaron.bentley@utoronto.ca-20070704233826-jkp63376wi1n96mm
update docs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
from email import Message
18
19
from StringIO import StringIO
19
20
import re
20
21
 
23
24
    diff,
24
25
    errors,
25
26
    gpg,
26
 
    hooks,
27
27
    registry,
28
28
    revision as _mod_revision,
29
29
    rio,
30
30
    testament,
31
31
    timestamp,
32
 
    trace,
33
32
    )
34
33
from bzrlib.bundle import (
35
34
    serializer as bundle_serializer,
36
35
    )
37
 
from bzrlib.email_message import EmailMessage
38
 
 
39
 
 
40
 
class MergeRequestBodyParams(object):
41
 
    """Parameter object for the merge_request_body hook."""
42
 
 
43
 
    def __init__(self, body, orig_body, directive, to, basename, subject,
44
 
                 branch, tree=None):
45
 
        self.body = body
46
 
        self.orig_body = orig_body
47
 
        self.directive = directive
48
 
        self.branch = branch
49
 
        self.tree = tree
50
 
        self.to = to
51
 
        self.basename = basename
52
 
        self.subject = subject
53
 
 
54
 
 
55
 
class MergeDirectiveHooks(hooks.Hooks):
56
 
    """Hooks for MergeDirective classes."""
57
 
 
58
 
    def __init__(self):
59
 
        hooks.Hooks.__init__(self)
60
 
        self.create_hook(hooks.HookPoint('merge_request_body',
61
 
            "Called with a MergeRequestBodyParams when a body is needed for"
62
 
            " a merge request.  Callbacks must return a body.  If more"
63
 
            " than one callback is registered, the output of one callback is"
64
 
            " provided to the next.", (1, 15, 0), False))
65
 
 
66
 
 
67
 
class BaseMergeDirective(object):
68
 
    """A request to perform a merge into a branch.
69
 
 
70
 
    This is the base class that all merge directive implementations 
71
 
    should derive from.
72
 
 
73
 
    :cvar multiple_output_files: Whether or not this merge directive 
74
 
        stores a set of revisions in more than one file
75
 
    """
76
 
 
77
 
    hooks = MergeDirectiveHooks()
78
 
 
79
 
    multiple_output_files = False
 
36
 
 
37
 
 
38
class _BaseMergeDirective(object):
80
39
 
81
40
    def __init__(self, revision_id, testament_sha1, time, timezone,
82
41
                 target_branch, patch=None, source_branch=None, message=None,
102
61
        self.source_branch = source_branch
103
62
        self.message = message
104
63
 
105
 
    def to_lines(self):
106
 
        """Serialize as a list of lines
107
 
 
108
 
        :return: a list of lines
109
 
        """
110
 
        raise NotImplementedError(self.to_lines)
111
 
 
112
 
    def to_files(self):
113
 
        """Serialize as a set of files.
114
 
 
115
 
        :return: List of tuples with filename and contents as lines
116
 
        """
117
 
        raise NotImplementedError(self.to_files)
118
 
 
119
 
    def get_raw_bundle(self):
120
 
        """Return the bundle for this merge directive.
121
 
 
122
 
        :return: bundle text or None if there is no bundle
123
 
        """
124
 
        return None
125
 
 
126
64
    def _to_lines(self, base_revision=False):
127
65
        """Serialize as a list of lines
128
66
 
142
80
        lines.append('# \n')
143
81
        return lines
144
82
 
145
 
    def write_to_directory(self, path):
146
 
        """Write this merge directive to a series of files in a directory.
147
 
 
148
 
        :param path: Filesystem path to write to
149
 
        """
150
 
        raise NotImplementedError(self.write_to_directory)
151
 
 
152
83
    @classmethod
153
84
    def from_objects(klass, repository, revision_id, time, timezone,
154
85
                 target_branch, patch_type='bundle',
205
136
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
206
137
            patch, patch_type, public_branch, message)
207
138
 
208
 
    def get_disk_name(self, branch):
209
 
        """Generate a suitable basename for storing this directive on disk
210
 
 
211
 
        :param branch: The Branch this merge directive was generated fro
212
 
        :return: A string
213
 
        """
214
 
        revno, revision_id = branch.last_revision_info()
215
 
        if self.revision_id == revision_id:
216
 
            revno = [revno]
217
 
        else:
218
 
            revno = branch.get_revision_id_to_revno_map().get(self.revision_id,
219
 
                ['merge'])
220
 
        nick = re.sub('(\W+)', '-', branch.nick).strip('-')
221
 
        return '%s-%s' % (nick, '.'.join(str(n) for n in revno))
222
 
 
223
139
    @staticmethod
224
140
    def _generate_diff(repository, revision_id, ancestor_id):
225
141
        tree_1 = repository.revision_tree(ancestor_id)
254
170
        :return: an email message
255
171
        """
256
172
        mail_from = branch.get_config().username()
 
173
        message = Message.Message()
 
174
        message['To'] = mail_to
 
175
        message['From'] = mail_from
257
176
        if self.message is not None:
258
 
            subject = self.message
 
177
            message['Subject'] = self.message
259
178
        else:
260
179
            revision = branch.repository.get_revision(self.revision_id)
261
 
            subject = revision.message
 
180
            message['Subject'] = revision.message
262
181
        if sign:
263
182
            body = self.to_signed(branch)
264
183
        else:
265
184
            body = ''.join(self.to_lines())
266
 
        message = EmailMessage(mail_from, mail_to, subject, body)
 
185
        message.set_payload(body)
267
186
        return message
268
187
 
269
188
    def install_revisions(self, target_repo):
274
193
                    StringIO(self.get_raw_bundle()))
275
194
                # We don't use the bundle's target revision, because
276
195
                # MergeDirective.revision_id is authoritative.
277
 
                try:
278
 
                    info.install_revisions(target_repo, stream_input=False)
279
 
                except errors.RevisionNotPresent:
280
 
                    # At least one dependency isn't present.  Try installing
281
 
                    # missing revisions from the submit branch
282
 
                    try:
283
 
                        submit_branch = \
284
 
                            _mod_branch.Branch.open(self.target_branch)
285
 
                    except errors.NotBranchError:
286
 
                        raise errors.TargetNotBranch(self.target_branch)
287
 
                    missing_revisions = []
288
 
                    bundle_revisions = set(r.revision_id for r in
289
 
                                           info.real_revisions)
290
 
                    for revision in info.real_revisions:
291
 
                        for parent_id in revision.parent_ids:
292
 
                            if (parent_id not in bundle_revisions and
293
 
                                not target_repo.has_revision(parent_id)):
294
 
                                missing_revisions.append(parent_id)
295
 
                    # reverse missing revisions to try to get heads first
296
 
                    unique_missing = []
297
 
                    unique_missing_set = set()
298
 
                    for revision in reversed(missing_revisions):
299
 
                        if revision in unique_missing_set:
300
 
                            continue
301
 
                        unique_missing.append(revision)
302
 
                        unique_missing_set.add(revision)
303
 
                    for missing_revision in unique_missing:
304
 
                        target_repo.fetch(submit_branch.repository,
305
 
                                          missing_revision)
306
 
                    info.install_revisions(target_repo, stream_input=False)
 
196
                info.install_revisions(target_repo)
307
197
            else:
308
198
                source_branch = _mod_branch.Branch.open(self.source_branch)
309
199
                target_repo.fetch(source_branch.repository, self.revision_id)
310
200
        return self.revision_id
311
201
 
312
 
    def compose_merge_request(self, mail_client, to, body, branch, tree=None):
313
 
        """Compose a request to merge this directive.
314
 
 
315
 
        :param mail_client: The mail client to use for composing this request.
316
 
        :param to: The address to compose the request to.
317
 
        :param branch: The Branch that was used to produce this directive.
318
 
        :param tree: The Tree (if any) for the Branch used to produce this
319
 
            directive.
320
 
        """
321
 
        basename = self.get_disk_name(branch)
322
 
        subject = '[MERGE] '
323
 
        if self.message is not None:
324
 
            subject += self.message
325
 
        else:
326
 
            revision = branch.repository.get_revision(self.revision_id)
327
 
            subject += revision.get_summary()
328
 
        if getattr(mail_client, 'supports_body', False):
329
 
            orig_body = body
330
 
            for hook in self.hooks['merge_request_body']:
331
 
                params = MergeRequestBodyParams(body, orig_body, self,
332
 
                                                to, basename, subject, branch,
333
 
                                                tree)
334
 
                body = hook(params)
335
 
        elif len(self.hooks['merge_request_body']) > 0:
336
 
            trace.warning('Cannot run merge_request_body hooks because mail'
337
 
                          ' client %s does not support message bodies.',
338
 
                        mail_client.__class__.__name__)
339
 
        mail_client.compose_merge_request(to, subject,
340
 
                                          ''.join(self.to_lines()),
341
 
                                          basename, body)
342
 
 
343
 
 
344
 
class MergeDirective(BaseMergeDirective):
 
202
 
 
203
class MergeDirective(_BaseMergeDirective):
345
204
 
346
205
    """A request to perform a merge into a branch.
347
206
 
376
235
        :param source_branch: A public location to merge the revision from
377
236
        :param message: The message to use when committing this merge
378
237
        """
379
 
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
 
238
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
380
239
            timezone, target_branch, patch, source_branch, message)
381
 
        if patch_type not in (None, 'diff', 'bundle'):
382
 
            raise ValueError(patch_type)
 
240
        assert patch_type in (None, 'diff', 'bundle'), patch_type
383
241
        if patch_type != 'bundle' and source_branch is None:
384
242
            raise errors.NoMergeSource()
385
243
        if patch_type is not None and patch is None:
409
267
        :return: a MergeRequest
410
268
        """
411
269
        line_iter = iter(lines)
412
 
        firstline = ""
413
270
        for line in line_iter:
414
271
            if line.startswith('# Bazaar merge directive format '):
415
 
                return _format_registry.get(line[2:].rstrip())._from_lines(
416
 
                    line_iter)
417
 
            firstline = firstline or line.strip()
418
 
        raise errors.NotAMergeDirective(firstline)
 
272
                break
 
273
        else:
 
274
            if len(lines) > 0:
 
275
                raise errors.NotAMergeDirective(lines[0])
 
276
            else:
 
277
                raise errors.NotAMergeDirective('')
 
278
        return _format_registry.get(line[2:].rstrip())._from_lines(line_iter)
419
279
 
420
280
    @classmethod
421
281
    def _from_lines(klass, line_iter):
466
326
        return None, self.revision_id, 'inapplicable'
467
327
 
468
328
 
469
 
class MergeDirective2(BaseMergeDirective):
 
329
class MergeDirective2(_BaseMergeDirective):
470
330
 
471
 
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.90)'
 
331
    _format_string = 'Bazaar merge directive format 2 (Bazaar 0.18)'
472
332
 
473
333
    def __init__(self, revision_id, testament_sha1, time, timezone,
474
334
                 target_branch, patch=None, source_branch=None, message=None,
475
335
                 bundle=None, base_revision_id=None):
476
336
        if source_branch is None and bundle is None:
477
337
            raise errors.NoMergeSource()
478
 
        BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
 
338
        _BaseMergeDirective.__init__(self, revision_id, testament_sha1, time,
479
339
            timezone, target_branch, patch, source_branch, message)
480
340
        self.bundle = bundle
481
341
        self.base_revision_id = base_revision_id
618
478
                    revision_id):
619
479
                    raise errors.PublicBranchOutOfDate(public_branch,
620
480
                                                       revision_id)
621
 
            testament_sha1 = t.as_sha1()
622
481
        finally:
623
482
            for entry in reversed(locked):
624
483
                entry.unlock()
625
 
        return klass(revision_id, testament_sha1, time, timezone,
626
 
            target_branch, patch, public_branch, message, bundle,
627
 
            base_revision_id)
 
484
        return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
 
485
            patch, public_branch, message, bundle, base_revision_id)
628
486
 
629
487
    def _verify_patch(self, repository):
630
488
        calculated_patch = self._generate_diff(repository, self.revision_id,
631
489
                                               self.base_revision_id)
632
490
        # Convert line-endings to UNIX
633
491
        stored_patch = re.sub('\r\n?', '\n', self.patch)
634
 
        calculated_patch = re.sub('\r\n?', '\n', calculated_patch)
635
492
        # Strip trailing whitespace
636
493
        calculated_patch = re.sub(' *\n', '\n', calculated_patch)
637
494
        stored_patch = re.sub(' *\n', '\n', stored_patch)
657
514
 
658
515
class MergeDirectiveFormatRegistry(registry.Registry):
659
516
 
660
 
    def register(self, directive, format_string=None):
661
 
        if format_string is None:
662
 
            format_string = directive._format_string
663
 
        registry.Registry.register(self, format_string, directive)
 
517
    def register(self, directive):
 
518
        registry.Registry.register(self, directive._format_string, directive)
664
519
 
665
520
 
666
521
_format_registry = MergeDirectiveFormatRegistry()
667
522
_format_registry.register(MergeDirective)
668
523
_format_registry.register(MergeDirective2)
669
 
# 0.19 never existed.  It got renamed to 0.90.  But by that point, there were
670
 
# already merge directives in the wild that used 0.19. Registering with the old
671
 
# format string to retain compatibility with those merge directives.
672
 
_format_registry.register(MergeDirective2,
673
 
                          'Bazaar merge directive format 2 (Bazaar 0.19)')