~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-05 10:27:33 UTC
  • mto: (5008.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5009.
  • Revision ID: v.ladeuil+lp@free.fr-20100205102733-8wpjnqz6g4nvrbfu
All Conflict action method names start with 'action_' to avoid potential namespace collisions

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
 
import warnings
20
 
 
21
 
from bzrlib.lazy_import import lazy_import
22
 
lazy_import(globals(), """
 
17
 
23
18
from bzrlib import (
24
19
    branch as _mod_branch,
25
 
    cleanup,
26
20
    conflicts as _mod_conflicts,
27
21
    debug,
28
 
    generate_ids,
 
22
    decorators,
 
23
    errors,
29
24
    graph as _mod_graph,
 
25
    hooks,
30
26
    merge3,
31
27
    osutils,
32
28
    patiencediff,
 
29
    progress,
33
30
    revision as _mod_revision,
34
31
    textfile,
35
32
    trace,
37
34
    tree as _mod_tree,
38
35
    tsort,
39
36
    ui,
40
 
    versionedfile,
41
 
    workingtree,
42
 
    )
43
 
from bzrlib.i18n import gettext
44
 
""")
45
 
from bzrlib import (
46
 
    decorators,
47
 
    errors,
48
 
    hooks,
49
 
    registry,
 
37
    versionedfile
50
38
    )
51
39
from bzrlib.symbol_versioning import (
52
40
    deprecated_in,
57
45
 
58
46
def transform_tree(from_tree, to_tree, interesting_ids=None):
59
47
    from_tree.lock_tree_write()
60
 
    operation = cleanup.OperationWithCleanups(merge_inner)
61
 
    operation.add_cleanup(from_tree.unlock)
62
 
    operation.run_simple(from_tree.branch, to_tree, from_tree,
63
 
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
 
48
    try:
 
49
        merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
 
50
                    interesting_ids=interesting_ids, this_tree=from_tree)
 
51
    finally:
 
52
        from_tree.unlock()
64
53
 
65
54
 
66
55
class MergeHooks(hooks.Hooks):
67
56
 
68
57
    def __init__(self):
69
 
        hooks.Hooks.__init__(self, "bzrlib.merge", "Merger.hooks")
70
 
        self.add_hook('merge_file_content',
 
58
        hooks.Hooks.__init__(self)
 
59
        self.create_hook(hooks.HookPoint('merge_file_content',
71
60
            "Called with a bzrlib.merge.Merger object to create a per file "
72
61
            "merge object when starting a merge. "
73
62
            "Should return either None or a subclass of "
77
66
            "side has deleted the file and the other has changed it). "
78
67
            "See the AbstractPerFileMerger API docs for details on how it is "
79
68
            "used by merge.",
80
 
            (2, 1))
81
 
        self.add_hook('pre_merge',
82
 
            'Called before a merge. '
83
 
            'Receives a Merger object as the single argument.',
84
 
            (2, 5))
85
 
        self.add_hook('post_merge',
86
 
            'Called after a merge. '
87
 
            'Receives a Merger object as the single argument. '
88
 
            'The return value is ignored.',
89
 
            (2, 5))
 
69
            (2, 1), None))
90
70
 
91
71
 
92
72
class AbstractPerFileMerger(object):
104
84
    def merge_contents(self, merge_params):
105
85
        """Attempt to merge the contents of a single file.
106
86
        
107
 
        :param merge_params: A bzrlib.merge.MergeFileHookParams
108
 
        :return: A tuple of (status, chunks), where status is one of
 
87
        :param merge_params: A bzrlib.merge.MergeHookParams
 
88
        :return : A tuple of (status, chunks), where status is one of
109
89
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
110
90
            is 'success' or 'conflicted', then chunks should be an iterable of
111
91
            strings for the new file contents.
113
93
        return ('not applicable', None)
114
94
 
115
95
 
116
 
class PerFileMerger(AbstractPerFileMerger):
117
 
    """Merge individual files when self.file_matches returns True.
118
 
 
119
 
    This class is intended to be subclassed.  The file_matches and
120
 
    merge_matching methods should be overridden with concrete implementations.
121
 
    """
122
 
 
123
 
    def file_matches(self, params):
124
 
        """Return True if merge_matching should be called on this file.
125
 
 
126
 
        Only called with merges of plain files with no clear winner.
127
 
 
128
 
        Subclasses must override this.
129
 
        """
130
 
        raise NotImplementedError(self.file_matches)
131
 
 
132
 
    def get_filename(self, params, tree):
133
 
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
134
 
        self.merger.this_tree) and a MergeFileHookParams.
135
 
        """
136
 
        return osutils.basename(tree.id2path(params.file_id))
137
 
 
138
 
    def get_filepath(self, params, tree):
139
 
        """Calculate the path to the file in a tree.
140
 
 
141
 
        :param params: A MergeFileHookParams describing the file to merge
142
 
        :param tree: a Tree, e.g. self.merger.this_tree.
143
 
        """
144
 
        return tree.id2path(params.file_id)
145
 
 
146
 
    def merge_contents(self, params):
147
 
        """Merge the contents of a single file."""
148
 
        # Check whether this custom merge logic should be used.
149
 
        if (
150
 
            # OTHER is a straight winner, rely on default merge.
151
 
            params.winner == 'other' or
152
 
            # THIS and OTHER aren't both files.
153
 
            not params.is_file_merge() or
154
 
            # The filename doesn't match
155
 
            not self.file_matches(params)):
156
 
            return 'not_applicable', None
157
 
        return self.merge_matching(params)
158
 
 
159
 
    def merge_matching(self, params):
160
 
        """Merge the contents of a single file that has matched the criteria
161
 
        in PerFileMerger.merge_contents (is a conflict, is a file,
162
 
        self.file_matches is True).
163
 
 
164
 
        Subclasses must override this.
165
 
        """
166
 
        raise NotImplementedError(self.merge_matching)
167
 
 
168
 
 
169
 
class ConfigurableFileMerger(PerFileMerger):
 
96
class ConfigurableFileMerger(AbstractPerFileMerger):
170
97
    """Merge individual files when configured via a .conf file.
171
98
 
172
99
    This is a base class for concrete custom file merging logic. Concrete
173
100
    classes should implement ``merge_text``.
174
101
 
175
 
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
176
 
    
177
102
    :ivar affected_files: The configured file paths to merge.
178
 
 
179
103
    :cvar name_prefix: The prefix to use when looking up configuration
180
 
        details. <name_prefix>_merge_files describes the files targeted by the
181
 
        hook for example.
182
 
        
 
104
        details.
183
105
    :cvar default_files: The default file paths to merge when no configuration
184
106
        is present.
185
107
    """
195
117
        if self.name_prefix is None:
196
118
            raise ValueError("name_prefix must be set.")
197
119
 
198
 
    def file_matches(self, params):
199
 
        """Check whether the file should call the merge hook.
200
 
 
201
 
        <name_prefix>_merge_files configuration variable is a list of files
202
 
        that should use the hook.
203
 
        """
 
120
    def filename_matches_config(self, params):
204
121
        affected_files = self.affected_files
205
122
        if affected_files is None:
206
 
            config = self.merger.this_branch.get_config()
 
123
            config = self.merger.this_tree.branch.get_config()
207
124
            # Until bzr provides a better policy for caching the config, we
208
125
            # just add the part we're interested in to the params to avoid
209
126
            # reading the config files repeatedly (bazaar.conf, location.conf,
215
132
                affected_files = self.default_files
216
133
            self.affected_files = affected_files
217
134
        if affected_files:
218
 
            filepath = self.get_filepath(params, self.merger.this_tree)
219
 
            if filepath in affected_files:
 
135
            filename = self.merger.this_tree.id2path(params.file_id)
 
136
            if filename in affected_files:
220
137
                return True
221
138
        return False
222
139
 
223
 
    def merge_matching(self, params):
224
 
        return self.merge_text(params)
 
140
    def merge_contents(self, params):
 
141
        """Merge the contents of a single file."""
 
142
        # First, check whether this custom merge logic should be used.  We
 
143
        # expect most files should not be merged by this handler.
 
144
        if (
 
145
            # OTHER is a straight winner, rely on default merge.
 
146
            params.winner == 'other' or
 
147
            # THIS and OTHER aren't both files.
 
148
            not params.is_file_merge() or
 
149
            # The filename isn't listed in the 'NAME_merge_files' config
 
150
            # option.
 
151
            not self.filename_matches_config(params)):
 
152
            return 'not_applicable', None
 
153
        return self.merge_text(self, params)
225
154
 
226
155
    def merge_text(self, params):
227
156
        """Merge the byte contents of a single file.
233
162
        raise NotImplementedError(self.merge_text)
234
163
 
235
164
 
236
 
class MergeFileHookParams(object):
 
165
class MergeHookParams(object):
237
166
    """Object holding parameters passed to merge_file_content hooks.
238
167
 
239
168
    There are some fields hooks can access:
298
227
        self.interesting_files = None
299
228
        self.show_base = False
300
229
        self.reprocess = False
301
 
        if pb is not None:
302
 
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
 
230
        if pb is None:
 
231
            pb = progress.DummyProgress()
 
232
        self._pb = pb
303
233
        self.pp = None
304
234
        self.recurse = recurse
305
235
        self.change_reporter = change_reporter
441
371
        return self._cached_trees[revision_id]
442
372
 
443
373
    def _get_tree(self, treespec, possible_transports=None):
 
374
        from bzrlib import workingtree
444
375
        location, revno = treespec
445
376
        if revno is None:
446
377
            tree = workingtree.WorkingTree.open_containing(location)[0]
454
385
        revision_id = _mod_revision.ensure_null(revision_id)
455
386
        return branch, self.revision_tree(revision_id, branch)
456
387
 
 
388
    @deprecated_method(deprecated_in((2, 1, 0)))
 
389
    def ensure_revision_trees(self):
 
390
        if self.this_revision_tree is None:
 
391
            self.this_basis_tree = self.revision_tree(self.this_basis)
 
392
            if self.this_basis == self.this_rev_id:
 
393
                self.this_revision_tree = self.this_basis_tree
 
394
 
 
395
        if self.other_rev_id is None:
 
396
            other_basis_tree = self.revision_tree(self.other_basis)
 
397
            if other_basis_tree.has_changes(self.other_tree):
 
398
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
399
            other_rev_id = self.other_basis
 
400
            self.other_tree = other_basis_tree
 
401
 
 
402
    @deprecated_method(deprecated_in((2, 1, 0)))
 
403
    def file_revisions(self, file_id):
 
404
        self.ensure_revision_trees()
 
405
        def get_id(tree, file_id):
 
406
            revision_id = tree.inventory[file_id].revision
 
407
            return revision_id
 
408
        if self.this_rev_id is None:
 
409
            if self.this_basis_tree.get_file_sha1(file_id) != \
 
410
                self.this_tree.get_file_sha1(file_id):
 
411
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
412
 
 
413
        trees = (self.this_basis_tree, self.other_tree)
 
414
        return [get_id(tree, file_id) for tree in trees]
 
415
 
 
416
    @deprecated_method(deprecated_in((2, 1, 0)))
 
417
    def check_basis(self, check_clean, require_commits=True):
 
418
        if self.this_basis is None and require_commits is True:
 
419
            raise errors.BzrCommandError(
 
420
                "This branch has no commits."
 
421
                " (perhaps you would prefer 'bzr pull')")
 
422
        if check_clean:
 
423
            self.compare_basis()
 
424
            if self.this_basis != self.this_rev_id:
 
425
                raise errors.UncommittedChanges(self.this_tree)
 
426
 
 
427
    @deprecated_method(deprecated_in((2, 1, 0)))
 
428
    def compare_basis(self):
 
429
        try:
 
430
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
431
        except errors.NoSuchRevision:
 
432
            basis_tree = self.this_tree.basis_tree()
 
433
        if not self.this_tree.has_changes(basis_tree):
 
434
            self.this_rev_id = self.this_basis
 
435
 
457
436
    def set_interesting_files(self, file_list):
458
437
        self.interesting_files = file_list
459
438
 
466
445
    def _add_parent(self):
467
446
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
468
447
        new_parent_trees = []
469
 
        operation = cleanup.OperationWithCleanups(
470
 
            self.this_tree.set_parent_trees)
471
448
        for revision_id in new_parents:
472
449
            try:
473
450
                tree = self.revision_tree(revision_id)
475
452
                tree = None
476
453
            else:
477
454
                tree.lock_read()
478
 
                operation.add_cleanup(tree.unlock)
479
455
            new_parent_trees.append((revision_id, tree))
480
 
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
 
456
        try:
 
457
            self.this_tree.set_parent_trees(new_parent_trees,
 
458
                                            allow_leftmost_as_ghost=True)
 
459
        finally:
 
460
            for _revision_id, tree in new_parent_trees:
 
461
                if tree is not None:
 
462
                    tree.unlock()
481
463
 
482
464
    def set_other(self, other_revision, possible_transports=None):
483
465
        """Set the revision and tree to merge from.
504
486
                raise errors.NoCommits(self.other_branch)
505
487
        if self.other_rev_id is not None:
506
488
            self._cached_trees[self.other_rev_id] = self.other_tree
507
 
        self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
 
489
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
508
490
 
509
491
    def set_other_revision(self, revision_id, other_branch):
510
492
        """Set 'other' based on a branch and revision id
548
530
            elif len(lcas) == 1:
549
531
                self.base_rev_id = list(lcas)[0]
550
532
            else: # len(lcas) > 1
551
 
                self._is_criss_cross = True
552
533
                if len(lcas) > 2:
553
534
                    # find_unique_lca can only handle 2 nodes, so we have to
554
535
                    # start back at the beginning. It is a shame to traverse
559
540
                else:
560
541
                    self.base_rev_id = self.revision_graph.find_unique_lca(
561
542
                                            *lcas)
562
 
                sorted_lca_keys = self.revision_graph.find_merge_order(
563
 
                    revisions[0], lcas)
564
 
                if self.base_rev_id == _mod_revision.NULL_REVISION:
565
 
                    self.base_rev_id = sorted_lca_keys[0]
566
 
 
 
543
                self._is_criss_cross = True
567
544
            if self.base_rev_id == _mod_revision.NULL_REVISION:
568
545
                raise errors.UnrelatedBranches()
569
546
            if self._is_criss_cross:
570
547
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
571
548
                              ' help criss-cross.')
572
549
                trace.mutter('Criss-cross lcas: %r' % lcas)
573
 
                if self.base_rev_id in lcas:
574
 
                    trace.mutter('Unable to find unique lca. '
575
 
                                 'Fallback %r as best option.'
576
 
                                 % self.base_rev_id)
577
 
                interesting_revision_ids = set(lcas)
578
 
                interesting_revision_ids.add(self.base_rev_id)
 
550
                interesting_revision_ids = [self.base_rev_id]
 
551
                interesting_revision_ids.extend(lcas)
579
552
                interesting_trees = dict((t.get_revision_id(), t)
580
553
                    for t in self.this_branch.repository.revision_trees(
581
554
                        interesting_revision_ids))
582
555
                self._cached_trees.update(interesting_trees)
583
 
                if self.base_rev_id in lcas:
584
 
                    self.base_tree = interesting_trees[self.base_rev_id]
585
 
                else:
586
 
                    self.base_tree = interesting_trees.pop(self.base_rev_id)
 
556
                self.base_tree = interesting_trees.pop(self.base_rev_id)
 
557
                sorted_lca_keys = self.revision_graph.find_merge_order(
 
558
                    revisions[0], lcas)
587
559
                self._lca_trees = [interesting_trees[key]
588
560
                                   for key in sorted_lca_keys]
589
561
            else:
612
584
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
613
585
 
614
586
    def make_merger(self):
615
 
        kwargs = {'working_tree': self.this_tree, 'this_tree': self.this_tree,
 
587
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
616
588
                  'other_tree': self.other_tree,
617
589
                  'interesting_ids': self.interesting_ids,
618
590
                  'interesting_files': self.interesting_files,
619
 
                  'this_branch': self.this_branch,
620
 
                  'other_branch': self.other_branch,
 
591
                  'pp': self.pp, 'this_branch': self.this_branch,
621
592
                  'do_merge': False}
622
593
        if self.merge_type.requires_base:
623
594
            kwargs['base_tree'] = self.base_tree
641
612
        if self._is_criss_cross and getattr(self.merge_type,
642
613
                                            'supports_lca_trees', False):
643
614
            kwargs['lca_trees'] = self._lca_trees
644
 
        return self.merge_type(pb=None,
 
615
        return self.merge_type(pb=self._pb,
645
616
                               change_reporter=self.change_reporter,
646
617
                               **kwargs)
647
618
 
648
 
    def _do_merge_to(self):
649
 
        merge = self.make_merger()
 
619
    def _do_merge_to(self, merge):
650
620
        if self.other_branch is not None:
651
621
            self.other_branch.update_references(self.this_branch)
652
 
        for hook in Merger.hooks['pre_merge']:
653
 
            hook(merge)
654
622
        merge.do_merge()
655
 
        for hook in Merger.hooks['post_merge']:
656
 
            hook(merge)
657
623
        if self.recurse == 'down':
658
624
            for relpath, file_id in self.this_tree.iter_references():
659
625
                sub_tree = self.this_tree.get_nested_tree(file_id, relpath)
663
629
                    continue
664
630
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
665
631
                sub_merge.merge_type = self.merge_type
666
 
                other_branch = self.other_branch.reference_parent(file_id,
667
 
                                                                  relpath)
 
632
                other_branch = self.other_branch.reference_parent(file_id, relpath)
668
633
                sub_merge.set_other_revision(other_revision, other_branch)
669
634
                base_revision = self.base_tree.get_reference_revision(file_id)
670
635
                sub_merge.base_tree = \
671
636
                    sub_tree.branch.repository.revision_tree(base_revision)
672
637
                sub_merge.base_rev_id = base_revision
673
638
                sub_merge.do_merge()
674
 
        return merge
675
639
 
676
640
    def do_merge(self):
677
 
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
678
641
        self.this_tree.lock_tree_write()
679
 
        operation.add_cleanup(self.this_tree.unlock)
680
 
        if self.base_tree is not None:
681
 
            self.base_tree.lock_read()
682
 
            operation.add_cleanup(self.base_tree.unlock)
683
 
        if self.other_tree is not None:
684
 
            self.other_tree.lock_read()
685
 
            operation.add_cleanup(self.other_tree.unlock)
686
 
        merge = operation.run_simple()
 
642
        try:
 
643
            if self.base_tree is not None:
 
644
                self.base_tree.lock_read()
 
645
            try:
 
646
                if self.other_tree is not None:
 
647
                    self.other_tree.lock_read()
 
648
                try:
 
649
                    merge = self.make_merger()
 
650
                    self._do_merge_to(merge)
 
651
                finally:
 
652
                    if self.other_tree is not None:
 
653
                        self.other_tree.unlock()
 
654
            finally:
 
655
                if self.base_tree is not None:
 
656
                    self.base_tree.unlock()
 
657
        finally:
 
658
            self.this_tree.unlock()
687
659
        if len(merge.cooked_conflicts) == 0:
688
660
            if not self.ignore_zero and not trace.is_quiet():
689
 
                trace.note(gettext("All changes applied successfully."))
 
661
                trace.note("All changes applied successfully.")
690
662
        else:
691
 
            trace.note(gettext("%d conflicts encountered.")
 
663
            trace.note("%d conflicts encountered."
692
664
                       % len(merge.cooked_conflicts))
693
665
 
694
666
        return len(merge.cooked_conflicts)
724
696
 
725
697
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
726
698
                 interesting_ids=None, reprocess=False, show_base=False,
727
 
                 pb=None, pp=None, change_reporter=None,
 
699
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
728
700
                 interesting_files=None, do_merge=True,
729
 
                 cherrypick=False, lca_trees=None, this_branch=None,
730
 
                 other_branch=None):
 
701
                 cherrypick=False, lca_trees=None, this_branch=None):
731
702
        """Initialize the merger object and perform the merge.
732
703
 
733
704
        :param working_tree: The working tree to apply the merge to
734
705
        :param this_tree: The local tree in the merge operation
735
706
        :param base_tree: The common tree in the merge operation
736
707
        :param other_tree: The other tree to merge changes from
737
 
        :param this_branch: The branch associated with this_tree.  Defaults to
738
 
            this_tree.branch if not supplied.
739
 
        :param other_branch: The branch associated with other_tree, if any.
 
708
        :param this_branch: The branch associated with this_tree
740
709
        :param interesting_ids: The file_ids of files that should be
741
710
            participate in the merge.  May not be combined with
742
711
            interesting_files.
743
712
        :param: reprocess If True, perform conflict-reduction processing.
744
713
        :param show_base: If True, show the base revision in text conflicts.
745
714
            (incompatible with reprocess)
746
 
        :param pb: ignored
 
715
        :param pb: A Progress bar
747
716
        :param pp: A ProgressPhase object
748
717
        :param change_reporter: An object that should report changes made
749
718
        :param interesting_files: The tree-relative paths of files that should
760
729
        if interesting_files is not None and interesting_ids is not None:
761
730
            raise ValueError(
762
731
                'specify either interesting_ids or interesting_files')
763
 
        if this_branch is None:
764
 
            this_branch = this_tree.branch
765
732
        self.interesting_ids = interesting_ids
766
733
        self.interesting_files = interesting_files
767
 
        self.working_tree = working_tree
768
 
        self.this_tree = this_tree
 
734
        self.this_tree = working_tree
769
735
        self.base_tree = base_tree
770
736
        self.other_tree = other_tree
771
737
        self.this_branch = this_branch
772
 
        self.other_branch = other_branch
773
738
        self._raw_conflicts = []
774
739
        self.cooked_conflicts = []
775
740
        self.reprocess = reprocess
780
745
        # making sure we haven't missed any corner cases.
781
746
        # if lca_trees is None:
782
747
        #     self._lca_trees = [self.base_tree]
 
748
        self.pb = pb
 
749
        self.pp = pp
783
750
        self.change_reporter = change_reporter
784
751
        self.cherrypick = cherrypick
 
752
        if self.pp is None:
 
753
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
785
754
        if do_merge:
786
755
            self.do_merge()
787
 
        if pp is not None:
788
 
            warnings.warn("pp argument to Merge3Merger is deprecated")
789
 
        if pb is not None:
790
 
            warnings.warn("pb argument to Merge3Merger is deprecated")
791
756
 
792
757
    def do_merge(self):
793
 
        operation = cleanup.OperationWithCleanups(self._do_merge)
794
 
        self.working_tree.lock_tree_write()
795
 
        operation.add_cleanup(self.working_tree.unlock)
796
 
        self.this_tree.lock_read()
797
 
        operation.add_cleanup(self.this_tree.unlock)
 
758
        self.this_tree.lock_tree_write()
798
759
        self.base_tree.lock_read()
799
 
        operation.add_cleanup(self.base_tree.unlock)
800
760
        self.other_tree.lock_read()
801
 
        operation.add_cleanup(self.other_tree.unlock)
802
 
        operation.run()
803
 
 
804
 
    def _do_merge(self, operation):
805
 
        self.tt = transform.TreeTransform(self.working_tree, None)
806
 
        operation.add_cleanup(self.tt.finalize)
807
 
        self._compute_transform()
808
 
        results = self.tt.apply(no_conflicts=True)
809
 
        self.write_modified(results)
810
761
        try:
811
 
            self.working_tree.add_conflicts(self.cooked_conflicts)
812
 
        except errors.UnsupportedOperation:
813
 
            pass
 
762
            self.tt = transform.TreeTransform(self.this_tree, self.pb)
 
763
            try:
 
764
                self.pp.next_phase()
 
765
                self._compute_transform()
 
766
                self.pp.next_phase()
 
767
                results = self.tt.apply(no_conflicts=True)
 
768
                self.write_modified(results)
 
769
                try:
 
770
                    self.this_tree.add_conflicts(self.cooked_conflicts)
 
771
                except errors.UnsupportedOperation:
 
772
                    pass
 
773
            finally:
 
774
                self.tt.finalize()
 
775
        finally:
 
776
            self.other_tree.unlock()
 
777
            self.base_tree.unlock()
 
778
            self.this_tree.unlock()
 
779
            self.pb.clear()
814
780
 
815
781
    def make_preview_transform(self):
816
 
        operation = cleanup.OperationWithCleanups(self._make_preview_transform)
817
782
        self.base_tree.lock_read()
818
 
        operation.add_cleanup(self.base_tree.unlock)
819
783
        self.other_tree.lock_read()
820
 
        operation.add_cleanup(self.other_tree.unlock)
821
 
        return operation.run_simple()
822
 
 
823
 
    def _make_preview_transform(self):
824
 
        self.tt = transform.TransformPreview(self.working_tree)
825
 
        self._compute_transform()
 
784
        self.tt = transform.TransformPreview(self.this_tree)
 
785
        try:
 
786
            self.pp.next_phase()
 
787
            self._compute_transform()
 
788
            self.pp.next_phase()
 
789
        finally:
 
790
            self.other_tree.unlock()
 
791
            self.base_tree.unlock()
 
792
            self.pb.clear()
826
793
        return self.tt
827
794
 
828
795
    def _compute_transform(self):
832
799
        else:
833
800
            entries = self._entries_lca()
834
801
            resolver = self._lca_multi_way
835
 
        # Prepare merge hooks
836
 
        factories = Merger.hooks['merge_file_content']
837
 
        # One hook for each registered one plus our default merger
838
 
        hooks = [factory(self) for factory in factories] + [self]
839
 
        self.active_hooks = [hook for hook in hooks if hook is not None]
840
802
        child_pb = ui.ui_factory.nested_progress_bar()
841
803
        try:
 
804
            factories = Merger.hooks['merge_file_content']
 
805
            hooks = [factory(self) for factory in factories] + [self]
 
806
            self.active_hooks = [hook for hook in hooks if hook is not None]
842
807
            for num, (file_id, changed, parents3, names3,
843
808
                      executable3) in enumerate(entries):
844
 
                # Try merging each entry
845
 
                child_pb.update(gettext('Preparing file merge'),
846
 
                                num, len(entries))
 
809
                child_pb.update('Preparing file merge', num, len(entries))
847
810
                self._merge_names(file_id, parents3, names3, resolver=resolver)
848
811
                if changed:
849
812
                    file_status = self._do_merge_contents(file_id)
853
816
                    executable3, file_status, resolver=resolver)
854
817
        finally:
855
818
            child_pb.finished()
856
 
        self.tt.fixup_new_roots()
857
 
        self._finish_computing_transform()
858
 
 
859
 
    def _finish_computing_transform(self):
860
 
        """Finalize the transform and report the changes.
861
 
 
862
 
        This is the second half of _compute_transform.
863
 
        """
 
819
        self.fix_root()
 
820
        self.pp.next_phase()
864
821
        child_pb = ui.ui_factory.nested_progress_bar()
865
822
        try:
866
823
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
873
830
                self.tt.iter_changes(), self.change_reporter)
874
831
        self.cook_conflicts(fs_conflicts)
875
832
        for conflict in self.cooked_conflicts:
876
 
            trace.warning(unicode(conflict))
 
833
            trace.warning(conflict)
877
834
 
878
835
    def _entries3(self):
879
836
        """Gather data about files modified between three trees.
886
843
        """
887
844
        result = []
888
845
        iterator = self.other_tree.iter_changes(self.base_tree,
889
 
                specific_files=self.interesting_files,
 
846
                include_unchanged=True, specific_files=self.interesting_files,
890
847
                extra_trees=[self.this_tree])
891
848
        this_entries = dict((e.file_id, e) for p, e in
892
849
                            self.this_tree.iter_entries_by_dir(
918
875
        it then compares with THIS and BASE.
919
876
 
920
877
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
921
 
 
922
 
        :return: [(file_id, changed, parents, names, executable)], where:
923
 
 
924
 
            * file_id: Simple file_id of the entry
925
 
            * changed: Boolean, True if the kind or contents changed else False
926
 
            * parents: ((base, [parent_id, in, lcas]), parent_id_other,
927
 
                        parent_id_this)
928
 
            * names:   ((base, [name, in, lcas]), name_in_other, name_in_this)
929
 
            * executable: ((base, [exec, in, lcas]), exec_in_other,
930
 
                        exec_in_this)
 
878
        :return: [(file_id, changed, parents, names, executable)]
 
879
            file_id     Simple file_id of the entry
 
880
            changed     Boolean, True if the kind or contents changed
 
881
                        else False
 
882
            parents     ((base, [parent_id, in, lcas]), parent_id_other,
 
883
                         parent_id_this)
 
884
            names       ((base, [name, in, lcas]), name_in_other, name_in_this)
 
885
            executable  ((base, [exec, in, lcas]), exec_in_other, exec_in_this)
931
886
        """
932
887
        if self.interesting_files is not None:
933
888
            lookup_trees = [self.this_tree, self.base_tree]
940
895
        result = []
941
896
        walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees)
942
897
 
943
 
        base_inventory = self.base_tree.root_inventory
944
 
        this_inventory = self.this_tree.root_inventory
 
898
        base_inventory = self.base_tree.inventory
 
899
        this_inventory = self.this_tree.inventory
945
900
        for path, file_id, other_ie, lca_values in walker.iter_all():
946
901
            # Is this modified at all from any of the other trees?
947
902
            if other_ie is None:
975
930
                else:
976
931
                    lca_entries.append(lca_ie)
977
932
 
978
 
            if base_inventory.has_id(file_id):
 
933
            if file_id in base_inventory:
979
934
                base_ie = base_inventory[file_id]
980
935
            else:
981
936
                base_ie = _none_entry
982
937
 
983
 
            if this_inventory.has_id(file_id):
 
938
            if file_id in this_inventory:
984
939
                this_ie = this_inventory[file_id]
985
940
            else:
986
941
                this_ie = _none_entry
1066
1021
                        continue
1067
1022
                else:
1068
1023
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1024
                # XXX: We need to handle kind == 'symlink'
1069
1025
 
1070
1026
            # If we have gotten this far, that means something has changed
1071
1027
            result.append((file_id, content_changed,
1078
1034
                          ))
1079
1035
        return result
1080
1036
 
 
1037
 
 
1038
    def fix_root(self):
 
1039
        try:
 
1040
            self.tt.final_kind(self.tt.root)
 
1041
        except errors.NoSuchFile:
 
1042
            self.tt.cancel_deletion(self.tt.root)
 
1043
        if self.tt.final_file_id(self.tt.root) is None:
 
1044
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
 
1045
                                 self.tt.root)
 
1046
        other_root_file_id = self.other_tree.get_root_id()
 
1047
        if other_root_file_id is None:
 
1048
            return
 
1049
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
1050
        if other_root == self.tt.root:
 
1051
            return
 
1052
        try:
 
1053
            self.tt.final_kind(other_root)
 
1054
        except errors.NoSuchFile:
 
1055
            return
 
1056
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
 
1057
            # the other tree's root is a non-root in the current tree
 
1058
            return
 
1059
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
1060
        self.tt.cancel_creation(other_root)
 
1061
        self.tt.cancel_versioning(other_root)
 
1062
 
 
1063
    def reparent_children(self, ie, target):
 
1064
        for thing, child in ie.children.iteritems():
 
1065
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
1066
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
1067
 
1081
1068
    def write_modified(self, results):
1082
1069
        modified_hashes = {}
1083
1070
        for path in results.modified_paths:
1084
 
            file_id = self.working_tree.path2id(self.working_tree.relpath(path))
 
1071
            file_id = self.this_tree.path2id(self.this_tree.relpath(path))
1085
1072
            if file_id is None:
1086
1073
                continue
1087
 
            hash = self.working_tree.get_file_sha1(file_id)
 
1074
            hash = self.this_tree.get_file_sha1(file_id)
1088
1075
            if hash is None:
1089
1076
                continue
1090
1077
            modified_hashes[file_id] = hash
1091
 
        self.working_tree.set_merge_modified(modified_hashes)
 
1078
        self.this_tree.set_merge_modified(modified_hashes)
1092
1079
 
1093
1080
    @staticmethod
1094
1081
    def parent(entry, file_id):
1107
1094
    @staticmethod
1108
1095
    def contents_sha1(tree, file_id):
1109
1096
        """Determine the sha1 of the file contents (used as a key method)."""
1110
 
        if not tree.has_id(file_id):
 
1097
        if file_id not in tree:
1111
1098
            return None
1112
1099
        return tree.get_file_sha1(file_id)
1113
1100
 
1129
1116
 
1130
1117
    @staticmethod
1131
1118
    def _three_way(base, other, this):
 
1119
        #if base == other, either they all agree, or only THIS has changed.
1132
1120
        if base == other:
1133
 
            # if 'base == other', either they all agree, or only 'this' has
1134
 
            # changed.
1135
1121
            return 'this'
1136
1122
        elif this not in (base, other):
1137
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1138
1123
            return 'conflict'
 
1124
        # "Ambiguous clean merge" -- both sides have made the same change.
1139
1125
        elif this == other:
1140
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1141
1126
            return "this"
 
1127
        # this == base: only other has changed.
1142
1128
        else:
1143
 
            # this == base: only other has changed.
1144
1129
            return "other"
1145
1130
 
1146
1131
    @staticmethod
1190
1175
                # only has an lca value
1191
1176
                return 'other'
1192
1177
 
1193
 
        # At this point, the lcas disagree, and the tip disagree
 
1178
        # At this point, the lcas disagree, and the tips disagree
1194
1179
        return 'conflict'
1195
1180
 
 
1181
    @staticmethod
 
1182
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
 
1183
        """Do a three-way test on a scalar.
 
1184
        Return "this", "other" or "conflict", depending whether a value wins.
 
1185
        """
 
1186
        key_base = key(base_tree, file_id)
 
1187
        key_other = key(other_tree, file_id)
 
1188
        #if base == other, either they all agree, or only THIS has changed.
 
1189
        if key_base == key_other:
 
1190
            return "this"
 
1191
        key_this = key(this_tree, file_id)
 
1192
        # "Ambiguous clean merge"
 
1193
        if key_this == key_other:
 
1194
            return "this"
 
1195
        elif key_this == key_base:
 
1196
            return "other"
 
1197
        else:
 
1198
            return "conflict"
 
1199
 
1196
1200
    def merge_names(self, file_id):
1197
1201
        def get_entry(tree):
1198
 
            try:
1199
 
                return tree.root_inventory[file_id]
1200
 
            except errors.NoSuchId:
 
1202
            if tree.has_id(file_id):
 
1203
                return tree.inventory[file_id]
 
1204
            else:
1201
1205
                return None
1202
1206
        this_entry = get_entry(self.this_tree)
1203
1207
        other_entry = get_entry(self.other_tree)
1230
1234
                parent_id_winner = "other"
1231
1235
        if name_winner == "this" and parent_id_winner == "this":
1232
1236
            return
1233
 
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
1234
 
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1235
 
            # road if a ContentConflict needs to be created so we should not do
1236
 
            # that
1237
 
            trans_id = self.tt.trans_id_file_id(file_id)
1238
 
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1239
 
                                        this_parent, this_name,
1240
 
                                        other_parent, other_name))
1241
 
        if not self.other_tree.has_id(file_id):
 
1237
        if name_winner == "conflict":
 
1238
            trans_id = self.tt.trans_id_file_id(file_id)
 
1239
            self._raw_conflicts.append(('name conflict', trans_id,
 
1240
                                        this_name, other_name))
 
1241
        if parent_id_winner == "conflict":
 
1242
            trans_id = self.tt.trans_id_file_id(file_id)
 
1243
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1244
                                        this_parent, other_parent))
 
1245
        if other_name is None:
1242
1246
            # it doesn't matter whether the result was 'other' or
1243
 
            # 'conflict'-- if it has no file id, we leave it alone.
 
1247
            # 'conflict'-- if there's no 'other', we leave it alone.
1244
1248
            return
 
1249
        # if we get here, name_winner and parent_winner are set to safe values.
 
1250
        trans_id = self.tt.trans_id_file_id(file_id)
1245
1251
        parent_id = parents[self.winner_idx[parent_id_winner]]
1246
 
        name = names[self.winner_idx[name_winner]]
1247
 
        if parent_id is not None or name is not None:
1248
 
            # if we get here, name_winner and parent_winner are set to safe
1249
 
            # values.
1250
 
            if parent_id is None and name is not None:
1251
 
                # if parent_id is None and name is non-None, current file is
1252
 
                # the tree root.
1253
 
                if names[self.winner_idx[parent_id_winner]] != '':
1254
 
                    raise AssertionError(
1255
 
                        'File looks like a root, but named %s' %
1256
 
                        names[self.winner_idx[parent_id_winner]])
1257
 
                parent_trans_id = transform.ROOT_PARENT
1258
 
            else:
1259
 
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1260
 
            self.tt.adjust_path(name, parent_trans_id,
1261
 
                                self.tt.trans_id_file_id(file_id))
 
1252
        if parent_id is not None:
 
1253
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1254
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
1255
                                parent_trans_id, trans_id)
1262
1256
 
1263
1257
    def _do_merge_contents(self, file_id):
1264
1258
        """Performs a merge on file_id contents."""
1265
1259
        def contents_pair(tree):
1266
 
            if not tree.has_id(file_id):
 
1260
            if file_id not in tree:
1267
1261
                return (None, None)
1268
1262
            kind = tree.kind(file_id)
1269
1263
            if kind == "file":
1298
1292
        # We have a hypothetical conflict, but if we have files, then we
1299
1293
        # can try to merge the content
1300
1294
        trans_id = self.tt.trans_id_file_id(file_id)
1301
 
        params = MergeFileHookParams(self, file_id, trans_id, this_pair[0],
 
1295
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1302
1296
            other_pair[0], winner)
1303
1297
        hooks = self.active_hooks
1304
1298
        hook_status = 'not_applicable'
1307
1301
            if hook_status != 'not_applicable':
1308
1302
                # Don't try any more hooks, this one applies.
1309
1303
                break
1310
 
        # If the merge ends up replacing the content of the file, we get rid of
1311
 
        # it at the end of this method (this variable is used to track the
1312
 
        # exceptions to this rule).
1313
 
        keep_this = False
1314
1304
        result = "modified"
1315
1305
        if hook_status == 'not_applicable':
1316
 
            # No merge hook was able to resolve the situation. Two cases exist:
1317
 
            # a content conflict or a duplicate one.
 
1306
            # This is a contents conflict, because none of the available
 
1307
            # functions could merge it.
1318
1308
            result = None
1319
1309
            name = self.tt.final_name(trans_id)
1320
1310
            parent_id = self.tt.final_parent(trans_id)
1321
 
            duplicate = False
1322
 
            inhibit_content_conflict = False
1323
 
            if params.this_kind is None: # file_id is not in THIS
1324
 
                # Is the name used for a different file_id ?
1325
 
                dupe_path = self.other_tree.id2path(file_id)
1326
 
                this_id = self.this_tree.path2id(dupe_path)
1327
 
                if this_id is not None:
1328
 
                    # Two entries for the same path
1329
 
                    keep_this = True
1330
 
                    # versioning the merged file will trigger a duplicate
1331
 
                    # conflict
1332
 
                    self.tt.version_file(file_id, trans_id)
1333
 
                    transform.create_from_tree(
1334
 
                        self.tt, trans_id, self.other_tree, file_id,
1335
 
                        filter_tree_path=self._get_filter_tree_path(file_id))
1336
 
                    inhibit_content_conflict = True
1337
 
            elif params.other_kind is None: # file_id is not in OTHER
1338
 
                # Is the name used for a different file_id ?
1339
 
                dupe_path = self.this_tree.id2path(file_id)
1340
 
                other_id = self.other_tree.path2id(dupe_path)
1341
 
                if other_id is not None:
1342
 
                    # Two entries for the same path again, but here, the other
1343
 
                    # entry will also be merged.  We simply inhibit the
1344
 
                    # 'content' conflict creation because we know OTHER will
1345
 
                    # create (or has already created depending on ordering) an
1346
 
                    # entry at the same path. This will trigger a 'duplicate'
1347
 
                    # conflict later.
1348
 
                    keep_this = True
1349
 
                    inhibit_content_conflict = True
1350
 
            if not inhibit_content_conflict:
1351
 
                if params.this_kind is not None:
1352
 
                    self.tt.unversion_file(trans_id)
1353
 
                # This is a contents conflict, because none of the available
1354
 
                # functions could merge it.
1355
 
                file_group = self._dump_conflicts(name, parent_id, file_id,
1356
 
                                                  set_version=True)
1357
 
                self._raw_conflicts.append(('contents conflict', file_group))
 
1311
            if self.this_tree.has_id(file_id):
 
1312
                self.tt.unversion_file(trans_id)
 
1313
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1314
                                              set_version=True)
 
1315
            self._raw_conflicts.append(('contents conflict', file_group))
1358
1316
        elif hook_status == 'success':
1359
1317
            self.tt.create_file(lines, trans_id)
1360
1318
        elif hook_status == 'conflicted':
1376
1334
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
1377
1335
        if not self.this_tree.has_id(file_id) and result == "modified":
1378
1336
            self.tt.version_file(file_id, trans_id)
1379
 
        if not keep_this:
1380
 
            # The merge has been performed and produced a new content, so the
1381
 
            # old contents should not be retained.
 
1337
        # The merge has been performed, so the old contents should not be
 
1338
        # retained.
 
1339
        try:
1382
1340
            self.tt.delete_contents(trans_id)
 
1341
        except errors.NoSuchFile:
 
1342
            pass
1383
1343
        return result
1384
1344
 
1385
1345
    def _default_other_winner_merge(self, merge_hook_params):
1386
1346
        """Replace this contents with other."""
1387
1347
        file_id = merge_hook_params.file_id
1388
1348
        trans_id = merge_hook_params.trans_id
 
1349
        file_in_this = self.this_tree.has_id(file_id)
1389
1350
        if self.other_tree.has_id(file_id):
1390
1351
            # OTHER changed the file
1391
 
            transform.create_from_tree(
1392
 
                self.tt, trans_id, self.other_tree, file_id,
1393
 
                filter_tree_path=self._get_filter_tree_path(file_id))
 
1352
            wt = self.this_tree
 
1353
            if wt.supports_content_filtering():
 
1354
                # We get the path from the working tree if it exists.
 
1355
                # That fails though when OTHER is adding a file, so
 
1356
                # we fall back to the other tree to find the path if
 
1357
                # it doesn't exist locally.
 
1358
                try:
 
1359
                    filter_tree_path = wt.id2path(file_id)
 
1360
                except errors.NoSuchId:
 
1361
                    filter_tree_path = self.other_tree.id2path(file_id)
 
1362
            else:
 
1363
                # Skip the id2path lookup for older formats
 
1364
                filter_tree_path = None
 
1365
            transform.create_from_tree(self.tt, trans_id,
 
1366
                             self.other_tree, file_id,
 
1367
                             filter_tree_path=filter_tree_path)
1394
1368
            return 'done', None
1395
 
        elif self.this_tree.has_id(file_id):
 
1369
        elif file_in_this:
1396
1370
            # OTHER deleted the file
1397
1371
            return 'delete', None
1398
1372
        else:
1424
1398
    def get_lines(self, tree, file_id):
1425
1399
        """Return the lines in a file, or an empty list."""
1426
1400
        if tree.has_id(file_id):
1427
 
            return tree.get_file_lines(file_id)
 
1401
            return tree.get_file(file_id).readlines()
1428
1402
        else:
1429
1403
            return []
1430
1404
 
1472
1446
                                              other_lines)
1473
1447
            file_group.append(trans_id)
1474
1448
 
1475
 
 
1476
 
    def _get_filter_tree_path(self, file_id):
1477
 
        if self.this_tree.supports_content_filtering():
1478
 
            # We get the path from the working tree if it exists.
1479
 
            # That fails though when OTHER is adding a file, so
1480
 
            # we fall back to the other tree to find the path if
1481
 
            # it doesn't exist locally.
1482
 
            try:
1483
 
                return self.this_tree.id2path(file_id)
1484
 
            except errors.NoSuchId:
1485
 
                return self.other_tree.id2path(file_id)
1486
 
        # Skip the id2path lookup for older formats
1487
 
        return None
1488
 
 
1489
1449
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1490
1450
                        base_lines=None, other_lines=None, set_version=False,
1491
1451
                        no_base=False):
1557
1517
        if winner == 'this' and file_status != "modified":
1558
1518
            return
1559
1519
        trans_id = self.tt.trans_id_file_id(file_id)
1560
 
        if self.tt.final_kind(trans_id) != "file":
 
1520
        try:
 
1521
            if self.tt.final_kind(trans_id) != "file":
 
1522
                return
 
1523
        except errors.NoSuchFile:
1561
1524
            return
1562
1525
        if winner == "this":
1563
1526
            executability = this_executable
1574
1537
 
1575
1538
    def cook_conflicts(self, fs_conflicts):
1576
1539
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1577
 
        content_conflict_file_ids = set()
1578
 
        cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
 
1540
        name_conflicts = {}
 
1541
        self.cooked_conflicts.extend(transform.cook_conflicts(
 
1542
                fs_conflicts, self.tt))
1579
1543
        fp = transform.FinalPaths(self.tt)
1580
1544
        for conflict in self._raw_conflicts:
1581
1545
            conflict_type = conflict[0]
1582
 
            if conflict_type == 'path conflict':
1583
 
                (trans_id, file_id,
1584
 
                this_parent, this_name,
1585
 
                other_parent, other_name) = conflict[1:]
1586
 
                if this_parent is None or this_name is None:
1587
 
                    this_path = '<deleted>'
1588
 
                else:
1589
 
                    parent_path =  fp.get_path(
1590
 
                        self.tt.trans_id_file_id(this_parent))
1591
 
                    this_path = osutils.pathjoin(parent_path, this_name)
1592
 
                if other_parent is None or other_name is None:
1593
 
                    other_path = '<deleted>'
1594
 
                else:
1595
 
                    if other_parent == self.other_tree.get_root_id():
1596
 
                        # The tree transform doesn't know about the other root,
1597
 
                        # so we special case here to avoid a NoFinalPath
1598
 
                        # exception
1599
 
                        parent_path = ''
1600
 
                    else:
1601
 
                        parent_path =  fp.get_path(
1602
 
                            self.tt.trans_id_file_id(other_parent))
1603
 
                    other_path = osutils.pathjoin(parent_path, other_name)
1604
 
                c = _mod_conflicts.Conflict.factory(
1605
 
                    'path conflict', path=this_path,
1606
 
                    conflict_path=other_path,
1607
 
                    file_id=file_id)
1608
 
            elif conflict_type == 'contents conflict':
 
1546
            if conflict_type in ('name conflict', 'parent conflict'):
 
1547
                trans_id = conflict[1]
 
1548
                conflict_args = conflict[2:]
 
1549
                if trans_id not in name_conflicts:
 
1550
                    name_conflicts[trans_id] = {}
 
1551
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1552
                                     conflict_args)
 
1553
            if conflict_type == 'contents conflict':
1609
1554
                for trans_id in conflict[1]:
1610
1555
                    file_id = self.tt.final_file_id(trans_id)
1611
1556
                    if file_id is not None:
1612
 
                        # Ok we found the relevant file-id
1613
1557
                        break
1614
1558
                path = fp.get_path(trans_id)
1615
1559
                for suffix in ('.BASE', '.THIS', '.OTHER'):
1616
1560
                    if path.endswith(suffix):
1617
 
                        # Here is the raw path
1618
1561
                        path = path[:-len(suffix)]
1619
1562
                        break
1620
1563
                c = _mod_conflicts.Conflict.factory(conflict_type,
1621
1564
                                                    path=path, file_id=file_id)
1622
 
                content_conflict_file_ids.add(file_id)
1623
 
            elif conflict_type == 'text conflict':
 
1565
                self.cooked_conflicts.append(c)
 
1566
            if conflict_type == 'text conflict':
1624
1567
                trans_id = conflict[1]
1625
1568
                path = fp.get_path(trans_id)
1626
1569
                file_id = self.tt.final_file_id(trans_id)
1627
1570
                c = _mod_conflicts.Conflict.factory(conflict_type,
1628
1571
                                                    path=path, file_id=file_id)
 
1572
                self.cooked_conflicts.append(c)
 
1573
 
 
1574
        for trans_id, conflicts in name_conflicts.iteritems():
 
1575
            try:
 
1576
                this_parent, other_parent = conflicts['parent conflict']
 
1577
                if this_parent == other_parent:
 
1578
                    raise AssertionError()
 
1579
            except KeyError:
 
1580
                this_parent = other_parent = \
 
1581
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1582
            try:
 
1583
                this_name, other_name = conflicts['name conflict']
 
1584
                if this_name == other_name:
 
1585
                    raise AssertionError()
 
1586
            except KeyError:
 
1587
                this_name = other_name = self.tt.final_name(trans_id)
 
1588
            other_path = fp.get_path(trans_id)
 
1589
            if this_parent is not None and this_name is not None:
 
1590
                this_parent_path = \
 
1591
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1592
                this_path = osutils.pathjoin(this_parent_path, this_name)
1629
1593
            else:
1630
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
1631
 
            cooked_conflicts.append(c)
1632
 
 
1633
 
        self.cooked_conflicts = []
1634
 
        # We want to get rid of path conflicts when a corresponding contents
1635
 
        # conflict exists. This can occur when one branch deletes a file while
1636
 
        # the other renames *and* modifies it. In this case, the content
1637
 
        # conflict is enough.
1638
 
        for c in cooked_conflicts:
1639
 
            if (c.typestring == 'path conflict'
1640
 
                and c.file_id in content_conflict_file_ids):
1641
 
                continue
 
1594
                this_path = "<deleted>"
 
1595
            file_id = self.tt.final_file_id(trans_id)
 
1596
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1597
                                                conflict_path=other_path,
 
1598
                                                file_id=file_id)
1642
1599
            self.cooked_conflicts.append(c)
1643
1600
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1644
1601
 
1750
1707
            osutils.rmtree(temp_dir)
1751
1708
 
1752
1709
 
1753
 
class PathNotInTree(errors.BzrError):
1754
 
 
1755
 
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
1756
 
 
1757
 
    def __init__(self, path, tree):
1758
 
        errors.BzrError.__init__(self, path=path, tree=tree)
1759
 
 
1760
 
 
1761
 
class MergeIntoMerger(Merger):
1762
 
    """Merger that understands other_tree will be merged into a subdir.
1763
 
 
1764
 
    This also changes the Merger api so that it uses real Branch, revision_id,
1765
 
    and RevisonTree objects, rather than using revision specs.
1766
 
    """
1767
 
 
1768
 
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1769
 
            source_subpath, other_rev_id=None):
1770
 
        """Create a new MergeIntoMerger object.
1771
 
 
1772
 
        source_subpath in other_tree will be effectively copied to
1773
 
        target_subdir in this_tree.
1774
 
 
1775
 
        :param this_tree: The tree that we will be merging into.
1776
 
        :param other_branch: The Branch we will be merging from.
1777
 
        :param other_tree: The RevisionTree object we want to merge.
1778
 
        :param target_subdir: The relative path where we want to merge
1779
 
            other_tree into this_tree
1780
 
        :param source_subpath: The relative path specifying the subtree of
1781
 
            other_tree to merge into this_tree.
1782
 
        """
1783
 
        # It is assumed that we are merging a tree that is not in our current
1784
 
        # ancestry, which means we are using the "EmptyTree" as our basis.
1785
 
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
1786
 
                                _mod_revision.NULL_REVISION)
1787
 
        super(MergeIntoMerger, self).__init__(
1788
 
            this_branch=this_tree.branch,
1789
 
            this_tree=this_tree,
1790
 
            other_tree=other_tree,
1791
 
            base_tree=null_ancestor_tree,
1792
 
            )
1793
 
        self._target_subdir = target_subdir
1794
 
        self._source_subpath = source_subpath
1795
 
        self.other_branch = other_branch
1796
 
        if other_rev_id is None:
1797
 
            other_rev_id = other_tree.get_revision_id()
1798
 
        self.other_rev_id = self.other_basis = other_rev_id
1799
 
        self.base_is_ancestor = True
1800
 
        self.backup_files = True
1801
 
        self.merge_type = Merge3Merger
1802
 
        self.show_base = False
1803
 
        self.reprocess = False
1804
 
        self.interesting_ids = None
1805
 
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1806
 
              target_subdir=self._target_subdir,
1807
 
              source_subpath=self._source_subpath)
1808
 
        if self._source_subpath != '':
1809
 
            # If this isn't a partial merge make sure the revisions will be
1810
 
            # present.
1811
 
            self._maybe_fetch(self.other_branch, self.this_branch,
1812
 
                self.other_basis)
1813
 
 
1814
 
    def set_pending(self):
1815
 
        if self._source_subpath != '':
1816
 
            return
1817
 
        Merger.set_pending(self)
1818
 
 
1819
 
 
1820
 
class _MergeTypeParameterizer(object):
1821
 
    """Wrap a merge-type class to provide extra parameters.
1822
 
    
1823
 
    This is hack used by MergeIntoMerger to pass some extra parameters to its
1824
 
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
1825
 
    the 'merge_type' member.  It is difficult override do_merge without
1826
 
    re-writing the whole thing, so instead we create a wrapper which will pass
1827
 
    the extra parameters.
1828
 
    """
1829
 
 
1830
 
    def __init__(self, merge_type, **kwargs):
1831
 
        self._extra_kwargs = kwargs
1832
 
        self._merge_type = merge_type
1833
 
 
1834
 
    def __call__(self, *args, **kwargs):
1835
 
        kwargs.update(self._extra_kwargs)
1836
 
        return self._merge_type(*args, **kwargs)
1837
 
 
1838
 
    def __getattr__(self, name):
1839
 
        return getattr(self._merge_type, name)
1840
 
 
1841
 
 
1842
 
class MergeIntoMergeType(Merge3Merger):
1843
 
    """Merger that incorporates a tree (or part of a tree) into another."""
1844
 
 
1845
 
    def __init__(self, *args, **kwargs):
1846
 
        """Initialize the merger object.
1847
 
 
1848
 
        :param args: See Merge3Merger.__init__'s args.
1849
 
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
1850
 
            source_subpath and target_subdir.
1851
 
        :keyword source_subpath: The relative path specifying the subtree of
1852
 
            other_tree to merge into this_tree.
1853
 
        :keyword target_subdir: The relative path where we want to merge
1854
 
            other_tree into this_tree
1855
 
        """
1856
 
        # All of the interesting work happens during Merge3Merger.__init__(),
1857
 
        # so we have have to hack in to get our extra parameters set.
1858
 
        self._source_subpath = kwargs.pop('source_subpath')
1859
 
        self._target_subdir = kwargs.pop('target_subdir')
1860
 
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1861
 
 
1862
 
    def _compute_transform(self):
1863
 
        child_pb = ui.ui_factory.nested_progress_bar()
1864
 
        try:
1865
 
            entries = self._entries_to_incorporate()
1866
 
            entries = list(entries)
1867
 
            for num, (entry, parent_id) in enumerate(entries):
1868
 
                child_pb.update(gettext('Preparing file merge'), num, len(entries))
1869
 
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1870
 
                trans_id = transform.new_by_entry(self.tt, entry,
1871
 
                    parent_trans_id, self.other_tree)
1872
 
        finally:
1873
 
            child_pb.finished()
1874
 
        self._finish_computing_transform()
1875
 
 
1876
 
    def _entries_to_incorporate(self):
1877
 
        """Yields pairs of (inventory_entry, new_parent)."""
1878
 
        other_inv = self.other_tree.root_inventory
1879
 
        subdir_id = other_inv.path2id(self._source_subpath)
1880
 
        if subdir_id is None:
1881
 
            # XXX: The error would be clearer if it gave the URL of the source
1882
 
            # branch, but we don't have a reference to that here.
1883
 
            raise PathNotInTree(self._source_subpath, "Source tree")
1884
 
        subdir = other_inv[subdir_id]
1885
 
        parent_in_target = osutils.dirname(self._target_subdir)
1886
 
        target_id = self.this_tree.path2id(parent_in_target)
1887
 
        if target_id is None:
1888
 
            raise PathNotInTree(self._target_subdir, "Target tree")
1889
 
        name_in_target = osutils.basename(self._target_subdir)
1890
 
        merge_into_root = subdir.copy()
1891
 
        merge_into_root.name = name_in_target
1892
 
        if self.this_tree.has_id(merge_into_root.file_id):
1893
 
            # Give the root a new file-id.
1894
 
            # This can happen fairly easily if the directory we are
1895
 
            # incorporating is the root, and both trees have 'TREE_ROOT' as
1896
 
            # their root_id.  Users will expect this to Just Work, so we
1897
 
            # change the file-id here.
1898
 
            # Non-root file-ids could potentially conflict too.  That's really
1899
 
            # an edge case, so we don't do anything special for those.  We let
1900
 
            # them cause conflicts.
1901
 
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
1902
 
        yield (merge_into_root, target_id)
1903
 
        if subdir.kind != 'directory':
1904
 
            # No children, so we are done.
1905
 
            return
1906
 
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
1907
 
            parent_id = entry.parent_id
1908
 
            if parent_id == subdir.file_id:
1909
 
                # The root's parent ID has changed, so make sure children of
1910
 
                # the root refer to the new ID.
1911
 
                parent_id = merge_into_root.file_id
1912
 
            yield (entry, parent_id)
1913
 
 
1914
 
 
1915
1710
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1916
1711
                backup_files=False,
1917
1712
                merge_type=Merge3Merger,
1921
1716
                other_rev_id=None,
1922
1717
                interesting_files=None,
1923
1718
                this_tree=None,
1924
 
                pb=None,
 
1719
                pb=progress.DummyProgress(),
1925
1720
                change_reporter=None):
1926
1721
    """Primary interface for merging.
1927
1722
 
1928
 
    Typical use is probably::
1929
 
 
1930
 
        merge_inner(branch, branch.get_revision_tree(other_revision),
1931
 
                    branch.get_revision_tree(base_revision))
1932
 
    """
 
1723
        typical use is probably
 
1724
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
1725
                     branch.get_revision_tree(base_revision))'
 
1726
        """
1933
1727
    if this_tree is None:
1934
1728
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1935
 
                              "parameter")
 
1729
                              "parameter as of bzrlib version 0.8.")
1936
1730
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1937
1731
                    pb=pb, change_reporter=change_reporter)
1938
1732
    merger.backup_files = backup_files
1955
1749
    merger.set_base_revision(get_revision_id(), this_branch)
1956
1750
    return merger.do_merge()
1957
1751
 
1958
 
 
1959
 
merge_type_registry = registry.Registry()
1960
 
merge_type_registry.register('diff3', Diff3Merger,
1961
 
                             "Merge using external diff3.")
1962
 
merge_type_registry.register('lca', LCAMerger,
1963
 
                             "LCA-newness merge.")
1964
 
merge_type_registry.register('merge3', Merge3Merger,
1965
 
                             "Native diff3-style merge.")
1966
 
merge_type_registry.register('weave', WeaveMerger,
1967
 
                             "Weave-based merge.")
1968
 
 
1969
 
 
1970
1752
def get_merge_type_registry():
1971
 
    """Merge type registry was previously in bzrlib.option
 
1753
    """Merge type registry is in bzrlib.option to avoid circular imports.
1972
1754
 
1973
 
    This method provides a backwards compatible way to retrieve it.
 
1755
    This method provides a sanctioned way to retrieve it.
1974
1756
    """
1975
 
    return merge_type_registry
 
1757
    from bzrlib import option
 
1758
    return option._merge_type_registry
1976
1759
 
1977
1760
 
1978
1761
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
2403
2186
class _PlanLCAMerge(_PlanMergeBase):
2404
2187
    """
2405
2188
    This merge algorithm differs from _PlanMerge in that:
2406
 
 
2407
2189
    1. comparisons are done against LCAs only
2408
2190
    2. cases where a contested line is new versus one LCA but old versus
2409
2191
       another are marked as conflicts, by emitting the line as conflicted-a
2450
2232
 
2451
2233
        If a line is killed and new, this indicates that the two merge
2452
2234
        revisions contain differing conflict resolutions.
2453
 
 
2454
2235
        :param revision_id: The id of the revision in which the lines are
2455
2236
            unique
2456
2237
        :param unique_line_numbers: The line numbers of unique lines.
2457
 
        :return: a tuple of (new_this, killed_other)
 
2238
        :return a tuple of (new_this, killed_other):
2458
2239
        """
2459
2240
        new = set()
2460
2241
        killed = set()