~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
47
47
from progress import DummyProgress, ProgressPhase
48
48
from bzrlib.revision import (NULL_REVISION, ensure_null)
49
49
from bzrlib.textfile import check_text_lines
50
 
from bzrlib.trace import mutter, warning, note
51
 
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
 
50
from bzrlib.trace import mutter, warning, note, is_quiet
 
51
from bzrlib.transform import (TransformPreview, TreeTransform,
 
52
                              resolve_conflicts, cook_conflicts,
52
53
                              conflict_pass, FinalPaths, create_by_entry,
53
54
                              unique_add, ROOT_PARENT)
54
55
from bzrlib.versionedfile import PlanWeaveMerge
115
116
 
116
117
    def _get_base_is_other_ancestor(self):
117
118
        if self._base_is_other_ancestor is None:
118
 
            self.base_is_other_ancestor = self.revision_graph.is_ancestor(
 
119
            if self.other_basis is None:
 
120
                return True
 
121
            self._base_is_other_ancestor = self.revision_graph.is_ancestor(
119
122
                self.base_rev_id, self.other_basis)
120
123
        return self._base_is_other_ancestor
121
124
 
161
164
        return merger, verified
162
165
 
163
166
    @staticmethod
164
 
    def from_revision_ids(pb, this, other, base=None, other_branch=None,
 
167
    def from_revision_ids(pb, tree, other, base=None, other_branch=None,
165
168
                          base_branch=None, revision_graph=None):
166
169
        """Return a Merger for revision-ids.
167
170
 
170
173
        :param base: The revision-id to use as BASE.  If not specified, will
171
174
            be auto-selected.
172
175
        :param other_branch: A branch containing the other revision-id.  If
173
 
            not supplied, this.branch is used.
 
176
            not supplied, tree.branch is used.
174
177
        :param base_branch: A branch containing the base revision-id.  If
175
 
            not supplied, other_branch or this.branch will be used.
 
178
            not supplied, other_branch or tree.branch will be used.
 
179
        :param revision_graph: If you have a revision_graph precomputed, pass
 
180
            it in, otherwise it will be created for you.
176
181
        :param pb: A progress indicator
177
182
        """
178
 
        merger = Merger(this.branch, this_tree=this, pb=pb,
 
183
        merger = Merger(tree.branch, this_tree=tree, pb=pb,
179
184
                        revision_graph=revision_graph)
180
185
        if other_branch is None:
181
 
            other_branch = this.branch
 
186
            other_branch = tree.branch
182
187
        merger.set_other_revision(other, other_branch)
183
188
        if base is None:
184
189
            merger.find_base()
376
381
                    base_branch.get_rev_id(base_revision[1]))
377
382
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
378
383
 
379
 
    def do_merge(self):
 
384
    def make_merger(self):
380
385
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
381
386
                  'other_tree': self.other_tree,
382
387
                  'interesting_ids': self.interesting_ids,
383
388
                  'interesting_files': self.interesting_files,
384
 
                  'pp': self.pp}
 
389
                  'pp': self.pp,
 
390
                  'do_merge': False}
385
391
        if self.merge_type.requires_base:
386
392
            kwargs['base_tree'] = self.base_tree
387
393
        if self.merge_type.supports_reprocess:
393
399
            kwargs['show_base'] = self.show_base
394
400
        elif self.show_base:
395
401
            raise BzrError("Showing base is not supported for this"
396
 
                                  " merge type. %s" % self.merge_type)
 
402
                           " merge type. %s" % self.merge_type)
397
403
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
398
404
            and not self.base_is_other_ancestor):
399
405
            raise errors.CannotReverseCherrypick()
400
 
        if self.merge_type.history_based:
 
406
        if self.merge_type.supports_cherrypick:
401
407
            kwargs['cherrypick'] = (not self.base_is_ancestor or
402
408
                                    not self.base_is_other_ancestor)
 
409
        return self.merge_type(pb=self._pb,
 
410
                               change_reporter=self.change_reporter,
 
411
                               **kwargs)
 
412
 
 
413
    def do_merge(self):
403
414
        self.this_tree.lock_tree_write()
404
415
        if self.base_tree is not None:
405
416
            self.base_tree.lock_read()
406
417
        if self.other_tree is not None:
407
418
            self.other_tree.lock_read()
408
419
        try:
409
 
            merge = self.merge_type(pb=self._pb,
410
 
                                    change_reporter=self.change_reporter,
411
 
                                    **kwargs)
 
420
            merge = self.make_merger()
 
421
            merge.do_merge()
412
422
            if self.recurse == 'down':
413
423
                for path, file_id in self.this_tree.iter_references():
414
424
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
424
434
                    base_revision = self.base_tree.get_reference_revision(file_id)
425
435
                    sub_merge.base_tree = \
426
436
                        sub_tree.branch.repository.revision_tree(base_revision)
 
437
                    sub_merge.base_rev_id = base_revision
427
438
                    sub_merge.do_merge()
428
439
 
429
440
        finally:
433
444
                self.base_tree.unlock()
434
445
            self.this_tree.unlock()
435
446
        if len(merge.cooked_conflicts) == 0:
436
 
            if not self.ignore_zero:
 
447
            if not self.ignore_zero and not is_quiet():
437
448
                note("All changes applied successfully.")
438
449
        else:
439
450
            note("%d conflicts encountered." % len(merge.cooked_conflicts))
447
458
    supports_reprocess = True
448
459
    supports_show_base = True
449
460
    history_based = False
 
461
    supports_cherrypick = True
450
462
    supports_reverse_cherrypick = True
451
463
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
452
464
 
453
465
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
454
466
                 interesting_ids=None, reprocess=False, show_base=False,
455
467
                 pb=DummyProgress(), pp=None, change_reporter=None,
456
 
                 interesting_files=None):
 
468
                 interesting_files=None, do_merge=True,
 
469
                 cherrypick=False):
457
470
        """Initialize the merger object and perform the merge.
458
471
 
459
472
        :param working_tree: The working tree to apply the merge to
482
495
        self.interesting_ids = interesting_ids
483
496
        self.interesting_files = interesting_files
484
497
        self.this_tree = working_tree
485
 
        self.this_tree.lock_tree_write()
486
498
        self.base_tree = base_tree
487
 
        self.base_tree.lock_read()
488
499
        self.other_tree = other_tree
489
 
        self.other_tree.lock_read()
490
500
        self._raw_conflicts = []
491
501
        self.cooked_conflicts = []
492
502
        self.reprocess = reprocess
494
504
        self.pb = pb
495
505
        self.pp = pp
496
506
        self.change_reporter = change_reporter
 
507
        self.cherrypick = cherrypick
497
508
        if self.pp is None:
498
509
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
 
510
        if do_merge:
 
511
            self.do_merge()
499
512
 
500
 
        self.tt = TreeTransform(working_tree, self.pb)
 
513
    def do_merge(self):
 
514
        self.this_tree.lock_tree_write()
 
515
        self.base_tree.lock_read()
 
516
        self.other_tree.lock_read()
 
517
        self.tt = TreeTransform(self.this_tree, self.pb)
501
518
        try:
502
519
            self.pp.next_phase()
503
 
            entries = self._entries3()
504
 
            child_pb = ui.ui_factory.nested_progress_bar()
505
 
            try:
506
 
                for num, (file_id, changed, parents3, names3,
507
 
                          executable3) in enumerate(entries):
508
 
                    child_pb.update('Preparing file merge', num, len(entries))
509
 
                    self._merge_names(file_id, parents3, names3)
510
 
                    if changed:
511
 
                        file_status = self.merge_contents(file_id)
512
 
                    else:
513
 
                        file_status = 'unmodified'
514
 
                    self._merge_executable(file_id,
515
 
                        executable3, file_status)
516
 
            finally:
517
 
                child_pb.finished()
518
 
            self.fix_root()
519
 
            self.pp.next_phase()
520
 
            child_pb = ui.ui_factory.nested_progress_bar()
521
 
            try:
522
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
523
 
                    lambda t, c: conflict_pass(t, c, self.other_tree))
524
 
            finally:
525
 
                child_pb.finished()
526
 
            if change_reporter is not None:
527
 
                from bzrlib import delta
528
 
                delta.report_changes(self.tt._iter_changes(), change_reporter)
529
 
            self.cook_conflicts(fs_conflicts)
530
 
            for conflict in self.cooked_conflicts:
531
 
                warning(conflict)
 
520
            self._compute_transform()
532
521
            self.pp.next_phase()
533
522
            results = self.tt.apply(no_conflicts=True)
534
523
            self.write_modified(results)
535
524
            try:
536
 
                working_tree.add_conflicts(self.cooked_conflicts)
 
525
                self.this_tree.add_conflicts(self.cooked_conflicts)
537
526
            except UnsupportedOperation:
538
527
                pass
539
528
        finally:
543
532
            self.this_tree.unlock()
544
533
            self.pb.clear()
545
534
 
 
535
    def make_preview_transform(self):
 
536
        self.base_tree.lock_read()
 
537
        self.other_tree.lock_read()
 
538
        self.tt = TransformPreview(self.this_tree)
 
539
        try:
 
540
            self.pp.next_phase()
 
541
            self._compute_transform()
 
542
            self.pp.next_phase()
 
543
        finally:
 
544
            self.other_tree.unlock()
 
545
            self.base_tree.unlock()
 
546
            self.pb.clear()
 
547
        return self.tt
 
548
 
 
549
    def _compute_transform(self):
 
550
        entries = self._entries3()
 
551
        child_pb = ui.ui_factory.nested_progress_bar()
 
552
        try:
 
553
            for num, (file_id, changed, parents3, names3,
 
554
                      executable3) in enumerate(entries):
 
555
                child_pb.update('Preparing file merge', num, len(entries))
 
556
                self._merge_names(file_id, parents3, names3)
 
557
                if changed:
 
558
                    file_status = self.merge_contents(file_id)
 
559
                else:
 
560
                    file_status = 'unmodified'
 
561
                self._merge_executable(file_id,
 
562
                    executable3, file_status)
 
563
        finally:
 
564
            child_pb.finished()
 
565
        self.fix_root()
 
566
        self.pp.next_phase()
 
567
        child_pb = ui.ui_factory.nested_progress_bar()
 
568
        try:
 
569
            fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
570
                lambda t, c: conflict_pass(t, c, self.other_tree))
 
571
        finally:
 
572
            child_pb.finished()
 
573
        if self.change_reporter is not None:
 
574
            from bzrlib import delta
 
575
            delta.report_changes(
 
576
                self.tt.iter_changes(), self.change_reporter)
 
577
        self.cook_conflicts(fs_conflicts)
 
578
        for conflict in self.cooked_conflicts:
 
579
            warning(conflict)
 
580
 
546
581
    def _entries3(self):
547
582
        """Gather data about files modified between three trees.
548
583
 
553
588
        executable3 is a tuple of execute-bit values for base, other and this.
554
589
        """
555
590
        result = []
556
 
        iterator = self.other_tree._iter_changes(self.base_tree,
 
591
        iterator = self.other_tree.iter_changes(self.base_tree,
557
592
                include_unchanged=True, specific_files=self.interesting_files,
558
593
                extra_trees=[self.this_tree])
559
594
        for (file_id, paths, changed, versioned, parents, names, kind,
838
873
            base_lines = []
839
874
        other_lines = self.get_lines(self.other_tree, file_id)
840
875
        this_lines = self.get_lines(self.this_tree, file_id)
841
 
        m3 = Merge3(base_lines, this_lines, other_lines)
 
876
        m3 = Merge3(base_lines, this_lines, other_lines,
 
877
                    is_cherrypick=self.cherrypick)
842
878
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
843
879
        if self.show_base is True:
844
880
            base_marker = '|' * 7
1013
1049
    supports_reverse_cherrypick = False
1014
1050
    history_based = True
1015
1051
 
1016
 
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
1017
 
                 interesting_ids=None, pb=DummyProgress(), pp=None,
1018
 
                 reprocess=False, change_reporter=None,
1019
 
                 interesting_files=None, cherrypick=False):
1020
 
        self.cherrypick = cherrypick
1021
 
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
1022
 
                                          base_tree, other_tree, 
1023
 
                                          interesting_ids=interesting_ids, 
1024
 
                                          pb=pb, pp=pp, reprocess=reprocess,
1025
 
                                          change_reporter=change_reporter)
1026
 
 
1027
1052
    def _merged_lines(self, file_id):
1028
1053
        """Generate the merged lines.
1029
1054
        There is no distinction between lines that are meant to contain <<<<<<<
1166
1191
    merger.reprocess = reprocess
1167
1192
    merger.other_rev_id = other_rev_id
1168
1193
    merger.other_basis = other_rev_id
 
1194
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
 
1195
    if get_revision_id is None:
 
1196
        get_revision_id = base_tree.last_revision
 
1197
    merger.set_base_revision(get_revision_id(), this_branch)
1169
1198
    return merger.do_merge()
1170
1199
 
1171
1200
def get_merge_type_registry():
1261
1290
            for b_index in range(last_j, j):
1262
1291
                if b_index in new_b:
1263
1292
                    if b_index in killed_a:
1264
 
                        yield 'conflicted-b', self.lines_b[a_index]
 
1293
                        yield 'conflicted-b', self.lines_b[b_index]
1265
1294
                    else:
1266
1295
                        yield 'new-b', self.lines_b[b_index]
1267
1296
                else: