~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-09-01 08:02:42 UTC
  • mfrom: (5390.3.3 faster-revert-593560)
  • Revision ID: pqm@pqm.ubuntu.com-20100901080242-esg62ody4frwmy66
(spiv) Avoid repeatedly calling self.target.all_file_ids() in
 InterTree.iter_changes. (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008 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
import warnings
17
18
 
 
19
from bzrlib.lazy_import import lazy_import
 
20
lazy_import(globals(), """
18
21
from bzrlib import (
19
22
    branch as _mod_branch,
20
23
    conflicts as _mod_conflicts,
21
24
    debug,
22
 
    decorators,
23
 
    errors,
 
25
    generate_ids,
24
26
    graph as _mod_graph,
25
 
    hooks,
26
27
    merge3,
27
28
    osutils,
28
29
    patiencediff,
29
 
    progress,
30
30
    revision as _mod_revision,
31
31
    textfile,
32
32
    trace,
34
34
    tree as _mod_tree,
35
35
    tsort,
36
36
    ui,
37
 
    versionedfile
 
37
    versionedfile,
 
38
    workingtree,
 
39
    )
 
40
from bzrlib.cleanup import OperationWithCleanups
 
41
""")
 
42
from bzrlib import (
 
43
    decorators,
 
44
    errors,
 
45
    hooks,
38
46
    )
39
47
from bzrlib.symbol_versioning import (
40
48
    deprecated_in,
45
53
 
46
54
def transform_tree(from_tree, to_tree, interesting_ids=None):
47
55
    from_tree.lock_tree_write()
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()
 
56
    operation = OperationWithCleanups(merge_inner)
 
57
    operation.add_cleanup(from_tree.unlock)
 
58
    operation.run_simple(from_tree.branch, to_tree, from_tree,
 
59
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
53
60
 
54
61
 
55
62
class MergeHooks(hooks.Hooks):
57
64
    def __init__(self):
58
65
        hooks.Hooks.__init__(self)
59
66
        self.create_hook(hooks.HookPoint('merge_file_content',
60
 
            "Called when file content needs to be merged (including when one "
61
 
            "side has deleted the file and the other has changed it)."
62
 
            "merge_file_content is called with a "
63
 
            "bzrlib.merge.MergeHookParams. The function should return a tuple "
64
 
            "of (status, lines), where status is one of 'not_applicable', "
65
 
            "'success', 'conflicted', or 'delete'.  If status is success or "
66
 
            "conflicted, then lines should be an iterable of the new lines "
67
 
            "for the file.",
 
67
            "Called with a bzrlib.merge.Merger object to create a per file "
 
68
            "merge object when starting a merge. "
 
69
            "Should return either None or a subclass of "
 
70
            "``bzrlib.merge.AbstractPerFileMerger``. "
 
71
            "Such objects will then be called per file "
 
72
            "that needs to be merged (including when one "
 
73
            "side has deleted the file and the other has changed it). "
 
74
            "See the AbstractPerFileMerger API docs for details on how it is "
 
75
            "used by merge.",
68
76
            (2, 1), None))
69
77
 
70
78
 
 
79
class AbstractPerFileMerger(object):
 
80
    """PerFileMerger objects are used by plugins extending merge for bzrlib.
 
81
 
 
82
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
83
    
 
84
    :ivar merger: The Merge3Merger performing the merge.
 
85
    """
 
86
 
 
87
    def __init__(self, merger):
 
88
        """Create a PerFileMerger for use with merger."""
 
89
        self.merger = merger
 
90
 
 
91
    def merge_contents(self, merge_params):
 
92
        """Attempt to merge the contents of a single file.
 
93
        
 
94
        :param merge_params: A bzrlib.merge.MergeHookParams
 
95
        :return : A tuple of (status, chunks), where status is one of
 
96
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
 
97
            is 'success' or 'conflicted', then chunks should be an iterable of
 
98
            strings for the new file contents.
 
99
        """
 
100
        return ('not applicable', None)
 
101
 
 
102
 
 
103
class PerFileMerger(AbstractPerFileMerger):
 
104
    """Merge individual files when self.file_matches returns True.
 
105
 
 
106
    This class is intended to be subclassed.  The file_matches and
 
107
    merge_matching methods should be overridden with concrete implementations.
 
108
    """
 
109
 
 
110
    def file_matches(self, params):
 
111
        """Return True if merge_matching should be called on this file.
 
112
 
 
113
        Only called with merges of plain files with no clear winner.
 
114
 
 
115
        Subclasses must override this.
 
116
        """
 
117
        raise NotImplementedError(self.file_matches)
 
118
 
 
119
    def get_filename(self, params, tree):
 
120
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
 
121
        self.merger.this_tree) and a MergeHookParams.
 
122
        """
 
123
        return osutils.basename(tree.id2path(params.file_id))
 
124
 
 
125
    def get_filepath(self, params, tree):
 
126
        """Calculate the path to the file in a tree.
 
127
 
 
128
        :param params: A MergeHookParams describing the file to merge
 
129
        :param tree: a Tree, e.g. self.merger.this_tree.
 
130
        """
 
131
        return tree.id2path(params.file_id)
 
132
 
 
133
    def merge_contents(self, params):
 
134
        """Merge the contents of a single file."""
 
135
        # Check whether this custom merge logic should be used.
 
136
        if (
 
137
            # OTHER is a straight winner, rely on default merge.
 
138
            params.winner == 'other' or
 
139
            # THIS and OTHER aren't both files.
 
140
            not params.is_file_merge() or
 
141
            # The filename doesn't match *.xml
 
142
            not self.file_matches(params)):
 
143
            return 'not_applicable', None
 
144
        return self.merge_matching(params)
 
145
 
 
146
    def merge_matching(self, params):
 
147
        """Merge the contents of a single file that has matched the criteria
 
148
        in PerFileMerger.merge_contents (is a conflict, is a file,
 
149
        self.file_matches is True).
 
150
 
 
151
        Subclasses must override this.
 
152
        """
 
153
        raise NotImplementedError(self.merge_matching)
 
154
 
 
155
 
 
156
class ConfigurableFileMerger(PerFileMerger):
 
157
    """Merge individual files when configured via a .conf file.
 
158
 
 
159
    This is a base class for concrete custom file merging logic. Concrete
 
160
    classes should implement ``merge_text``.
 
161
 
 
162
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
163
    
 
164
    :ivar affected_files: The configured file paths to merge.
 
165
 
 
166
    :cvar name_prefix: The prefix to use when looking up configuration
 
167
        details. <name_prefix>_merge_files describes the files targeted by the
 
168
        hook for example.
 
169
        
 
170
    :cvar default_files: The default file paths to merge when no configuration
 
171
        is present.
 
172
    """
 
173
 
 
174
    name_prefix = None
 
175
    default_files = None
 
176
 
 
177
    def __init__(self, merger):
 
178
        super(ConfigurableFileMerger, self).__init__(merger)
 
179
        self.affected_files = None
 
180
        self.default_files = self.__class__.default_files or []
 
181
        self.name_prefix = self.__class__.name_prefix
 
182
        if self.name_prefix is None:
 
183
            raise ValueError("name_prefix must be set.")
 
184
 
 
185
    def file_matches(self, params):
 
186
        """Check whether the file should call the merge hook.
 
187
 
 
188
        <name_prefix>_merge_files configuration variable is a list of files
 
189
        that should use the hook.
 
190
        """
 
191
        affected_files = self.affected_files
 
192
        if affected_files is None:
 
193
            config = self.merger.this_branch.get_config()
 
194
            # Until bzr provides a better policy for caching the config, we
 
195
            # just add the part we're interested in to the params to avoid
 
196
            # reading the config files repeatedly (bazaar.conf, location.conf,
 
197
            # branch.conf).
 
198
            config_key = self.name_prefix + '_merge_files'
 
199
            affected_files = config.get_user_option_as_list(config_key)
 
200
            if affected_files is None:
 
201
                # If nothing was specified in the config, use the default.
 
202
                affected_files = self.default_files
 
203
            self.affected_files = affected_files
 
204
        if affected_files:
 
205
            filepath = self.get_filepath(params, self.merger.this_tree)
 
206
            if filepath in affected_files:
 
207
                return True
 
208
        return False
 
209
 
 
210
    def merge_matching(self, params):
 
211
        return self.merge_text(params)
 
212
 
 
213
    def merge_text(self, params):
 
214
        """Merge the byte contents of a single file.
 
215
 
 
216
        This is called after checking that the merge should be performed in
 
217
        merge_contents, and it should behave as per
 
218
        ``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
 
219
        """
 
220
        raise NotImplementedError(self.merge_text)
 
221
 
 
222
 
71
223
class MergeHookParams(object):
72
224
    """Object holding parameters passed to merge_file_content hooks.
73
225
 
74
 
    There are 3 fields hooks can access:
 
226
    There are some fields hooks can access:
75
227
 
76
 
    :ivar merger: the Merger object
77
228
    :ivar file_id: the file ID of the file being merged
78
229
    :ivar trans_id: the transform ID for the merge of this file
79
230
    :ivar this_kind: kind of file_id in 'this' tree
83
234
 
84
235
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
85
236
            winner):
86
 
        self.merger = merger
 
237
        self._merger = merger
87
238
        self.file_id = file_id
88
239
        self.trans_id = trans_id
89
240
        self.this_kind = this_kind
90
241
        self.other_kind = other_kind
91
242
        self.winner = winner
92
 
        
 
243
 
93
244
    def is_file_merge(self):
94
245
        """True if this_kind and other_kind are both 'file'."""
95
246
        return self.this_kind == 'file' and self.other_kind == 'file'
96
 
    
 
247
 
97
248
    @decorators.cachedproperty
98
249
    def base_lines(self):
99
250
        """The lines of the 'base' version of the file."""
100
 
        return self.merger.get_lines(self.merger.base_tree, self.file_id)
 
251
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
101
252
 
102
253
    @decorators.cachedproperty
103
254
    def this_lines(self):
104
255
        """The lines of the 'this' version of the file."""
105
 
        return self.merger.get_lines(self.merger.this_tree, self.file_id)
 
256
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
106
257
 
107
258
    @decorators.cachedproperty
108
259
    def other_lines(self):
109
260
        """The lines of the 'other' version of the file."""
110
 
        return self.merger.get_lines(self.merger.other_tree, self.file_id)
 
261
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
111
262
 
112
263
 
113
264
class Merger(object):
134
285
        self.interesting_files = None
135
286
        self.show_base = False
136
287
        self.reprocess = False
137
 
        if pb is None:
138
 
            pb = progress.DummyProgress()
139
 
        self._pb = pb
 
288
        if pb is not None:
 
289
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
140
290
        self.pp = None
141
291
        self.recurse = recurse
142
292
        self.change_reporter = change_reporter
278
428
        return self._cached_trees[revision_id]
279
429
 
280
430
    def _get_tree(self, treespec, possible_transports=None):
281
 
        from bzrlib import workingtree
282
431
        location, revno = treespec
283
432
        if revno is None:
284
433
            tree = workingtree.WorkingTree.open_containing(location)[0]
352
501
    def _add_parent(self):
353
502
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
354
503
        new_parent_trees = []
 
504
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
355
505
        for revision_id in new_parents:
356
506
            try:
357
507
                tree = self.revision_tree(revision_id)
359
509
                tree = None
360
510
            else:
361
511
                tree.lock_read()
 
512
                operation.add_cleanup(tree.unlock)
362
513
            new_parent_trees.append((revision_id, tree))
363
 
        try:
364
 
            self.this_tree.set_parent_trees(new_parent_trees,
365
 
                                            allow_leftmost_as_ghost=True)
366
 
        finally:
367
 
            for _revision_id, tree in new_parent_trees:
368
 
                if tree is not None:
369
 
                    tree.unlock()
 
514
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
370
515
 
371
516
    def set_other(self, other_revision, possible_transports=None):
372
517
        """Set the revision and tree to merge from.
495
640
                  'other_tree': self.other_tree,
496
641
                  'interesting_ids': self.interesting_ids,
497
642
                  'interesting_files': self.interesting_files,
498
 
                  'pp': self.pp, 'this_branch': self.this_branch,
 
643
                  'this_branch': self.this_branch,
499
644
                  'do_merge': False}
500
645
        if self.merge_type.requires_base:
501
646
            kwargs['base_tree'] = self.base_tree
519
664
        if self._is_criss_cross and getattr(self.merge_type,
520
665
                                            'supports_lca_trees', False):
521
666
            kwargs['lca_trees'] = self._lca_trees
522
 
        return self.merge_type(pb=self._pb,
 
667
        return self.merge_type(pb=None,
523
668
                               change_reporter=self.change_reporter,
524
669
                               **kwargs)
525
670
 
526
 
    def _do_merge_to(self, merge):
 
671
    def _do_merge_to(self):
 
672
        merge = self.make_merger()
527
673
        if self.other_branch is not None:
528
674
            self.other_branch.update_references(self.this_branch)
529
675
        merge.do_merge()
543
689
                    sub_tree.branch.repository.revision_tree(base_revision)
544
690
                sub_merge.base_rev_id = base_revision
545
691
                sub_merge.do_merge()
 
692
        return merge
546
693
 
547
694
    def do_merge(self):
 
695
        operation = OperationWithCleanups(self._do_merge_to)
548
696
        self.this_tree.lock_tree_write()
549
 
        try:
550
 
            if self.base_tree is not None:
551
 
                self.base_tree.lock_read()
552
 
            try:
553
 
                if self.other_tree is not None:
554
 
                    self.other_tree.lock_read()
555
 
                try:
556
 
                    merge = self.make_merger()
557
 
                    self._do_merge_to(merge)
558
 
                finally:
559
 
                    if self.other_tree is not None:
560
 
                        self.other_tree.unlock()
561
 
            finally:
562
 
                if self.base_tree is not None:
563
 
                    self.base_tree.unlock()
564
 
        finally:
565
 
            self.this_tree.unlock()
 
697
        operation.add_cleanup(self.this_tree.unlock)
 
698
        if self.base_tree is not None:
 
699
            self.base_tree.lock_read()
 
700
            operation.add_cleanup(self.base_tree.unlock)
 
701
        if self.other_tree is not None:
 
702
            self.other_tree.lock_read()
 
703
            operation.add_cleanup(self.other_tree.unlock)
 
704
        merge = operation.run_simple()
566
705
        if len(merge.cooked_conflicts) == 0:
567
706
            if not self.ignore_zero and not trace.is_quiet():
568
707
                trace.note("All changes applied successfully.")
603
742
 
604
743
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
605
744
                 interesting_ids=None, reprocess=False, show_base=False,
606
 
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
 
745
                 pb=None, pp=None, change_reporter=None,
607
746
                 interesting_files=None, do_merge=True,
608
747
                 cherrypick=False, lca_trees=None, this_branch=None):
609
748
        """Initialize the merger object and perform the merge.
612
751
        :param this_tree: The local tree in the merge operation
613
752
        :param base_tree: The common tree in the merge operation
614
753
        :param other_tree: The other tree to merge changes from
615
 
        :param this_branch: The branch associated with this_tree
 
754
        :param this_branch: The branch associated with this_tree.  Defaults to
 
755
            this_tree.branch if not supplied.
616
756
        :param interesting_ids: The file_ids of files that should be
617
757
            participate in the merge.  May not be combined with
618
758
            interesting_files.
619
759
        :param: reprocess If True, perform conflict-reduction processing.
620
760
        :param show_base: If True, show the base revision in text conflicts.
621
761
            (incompatible with reprocess)
622
 
        :param pb: A Progress bar
 
762
        :param pb: ignored
623
763
        :param pp: A ProgressPhase object
624
764
        :param change_reporter: An object that should report changes made
625
765
        :param interesting_files: The tree-relative paths of files that should
636
776
        if interesting_files is not None and interesting_ids is not None:
637
777
            raise ValueError(
638
778
                'specify either interesting_ids or interesting_files')
 
779
        if this_branch is None:
 
780
            this_branch = this_tree.branch
639
781
        self.interesting_ids = interesting_ids
640
782
        self.interesting_files = interesting_files
641
783
        self.this_tree = working_tree
652
794
        # making sure we haven't missed any corner cases.
653
795
        # if lca_trees is None:
654
796
        #     self._lca_trees = [self.base_tree]
655
 
        self.pb = pb
656
 
        self.pp = pp
657
797
        self.change_reporter = change_reporter
658
798
        self.cherrypick = cherrypick
659
 
        if self.pp is None:
660
 
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
661
799
        if do_merge:
662
800
            self.do_merge()
 
801
        if pp is not None:
 
802
            warnings.warn("pp argument to Merge3Merger is deprecated")
 
803
        if pb is not None:
 
804
            warnings.warn("pb argument to Merge3Merger is deprecated")
663
805
 
664
806
    def do_merge(self):
 
807
        operation = OperationWithCleanups(self._do_merge)
665
808
        self.this_tree.lock_tree_write()
 
809
        operation.add_cleanup(self.this_tree.unlock)
666
810
        self.base_tree.lock_read()
 
811
        operation.add_cleanup(self.base_tree.unlock)
667
812
        self.other_tree.lock_read()
 
813
        operation.add_cleanup(self.other_tree.unlock)
 
814
        operation.run()
 
815
 
 
816
    def _do_merge(self, operation):
 
817
        self.tt = transform.TreeTransform(self.this_tree, None)
 
818
        operation.add_cleanup(self.tt.finalize)
 
819
        self._compute_transform()
 
820
        results = self.tt.apply(no_conflicts=True)
 
821
        self.write_modified(results)
668
822
        try:
669
 
            self.tt = transform.TreeTransform(self.this_tree, self.pb)
670
 
            try:
671
 
                self.pp.next_phase()
672
 
                self._compute_transform()
673
 
                self.pp.next_phase()
674
 
                results = self.tt.apply(no_conflicts=True)
675
 
                self.write_modified(results)
676
 
                try:
677
 
                    self.this_tree.add_conflicts(self.cooked_conflicts)
678
 
                except errors.UnsupportedOperation:
679
 
                    pass
680
 
            finally:
681
 
                self.tt.finalize()
682
 
        finally:
683
 
            self.other_tree.unlock()
684
 
            self.base_tree.unlock()
685
 
            self.this_tree.unlock()
686
 
            self.pb.clear()
 
823
            self.this_tree.add_conflicts(self.cooked_conflicts)
 
824
        except errors.UnsupportedOperation:
 
825
            pass
687
826
 
688
827
    def make_preview_transform(self):
 
828
        operation = OperationWithCleanups(self._make_preview_transform)
689
829
        self.base_tree.lock_read()
 
830
        operation.add_cleanup(self.base_tree.unlock)
690
831
        self.other_tree.lock_read()
 
832
        operation.add_cleanup(self.other_tree.unlock)
 
833
        return operation.run_simple()
 
834
 
 
835
    def _make_preview_transform(self):
691
836
        self.tt = transform.TransformPreview(self.this_tree)
692
 
        try:
693
 
            self.pp.next_phase()
694
 
            self._compute_transform()
695
 
            self.pp.next_phase()
696
 
        finally:
697
 
            self.other_tree.unlock()
698
 
            self.base_tree.unlock()
699
 
            self.pb.clear()
 
837
        self._compute_transform()
700
838
        return self.tt
701
839
 
702
840
    def _compute_transform(self):
708
846
            resolver = self._lca_multi_way
709
847
        child_pb = ui.ui_factory.nested_progress_bar()
710
848
        try:
 
849
            factories = Merger.hooks['merge_file_content']
 
850
            hooks = [factory(self) for factory in factories] + [self]
 
851
            self.active_hooks = [hook for hook in hooks if hook is not None]
711
852
            for num, (file_id, changed, parents3, names3,
712
853
                      executable3) in enumerate(entries):
713
854
                child_pb.update('Preparing file merge', num, len(entries))
714
855
                self._merge_names(file_id, parents3, names3, resolver=resolver)
715
856
                if changed:
716
 
                    file_status = self.merge_contents(file_id)
 
857
                    file_status = self._do_merge_contents(file_id)
717
858
                else:
718
859
                    file_status = 'unmodified'
719
860
                self._merge_executable(file_id,
721
862
        finally:
722
863
            child_pb.finished()
723
864
        self.fix_root()
724
 
        self.pp.next_phase()
 
865
        self._finish_computing_transform()
 
866
 
 
867
    def _finish_computing_transform(self):
 
868
        """Finalize the transform and report the changes.
 
869
 
 
870
        This is the second half of _compute_transform.
 
871
        """
725
872
        child_pb = ui.ui_factory.nested_progress_bar()
726
873
        try:
727
874
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
925
1072
                        continue
926
1073
                else:
927
1074
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
928
 
                # XXX: We need to handle kind == 'symlink'
929
1075
 
930
1076
            # If we have gotten this far, that means something has changed
931
1077
            result.append((file_id, content_changed,
938
1084
                          ))
939
1085
        return result
940
1086
 
941
 
 
942
1087
    def fix_root(self):
943
1088
        try:
944
1089
            self.tt.final_kind(self.tt.root)
953
1098
        other_root = self.tt.trans_id_file_id(other_root_file_id)
954
1099
        if other_root == self.tt.root:
955
1100
            return
 
1101
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
 
1102
            # the other tree's root is a non-root in the current tree (as when
 
1103
            # a previously unrelated branch is merged into another)
 
1104
            return
956
1105
        try:
957
1106
            self.tt.final_kind(other_root)
 
1107
            other_root_is_present = True
958
1108
        except errors.NoSuchFile:
959
 
            return
960
 
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
961
 
            # the other tree's root is a non-root in the current tree
962
 
            return
963
 
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
964
 
        self.tt.cancel_creation(other_root)
965
 
        self.tt.cancel_versioning(other_root)
966
 
 
967
 
    def reparent_children(self, ie, target):
968
 
        for thing, child in ie.children.iteritems():
 
1109
            # other_root doesn't have a physical representation. We still need
 
1110
            # to move any references to the actual root of the tree.
 
1111
            other_root_is_present = False
 
1112
        # 'other_tree.inventory.root' is not present in this tree. We are
 
1113
        # calling adjust_path for children which *want* to be present with a
 
1114
        # correct place to go.
 
1115
        for thing, child in self.other_tree.inventory.root.children.iteritems():
969
1116
            trans_id = self.tt.trans_id_file_id(child.file_id)
970
 
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
1117
            if not other_root_is_present:
 
1118
                # FIXME: Make final_kind returns None instead of raising
 
1119
                # NoSuchFile to avoid the ugly construct below -- vila 20100402
 
1120
                try:
 
1121
                    self.tt.final_kind(trans_id)
 
1122
                    # The item exist in the final tree and has a defined place
 
1123
                    # to go already.
 
1124
                    continue
 
1125
                except errors.NoSuchFile, e:
 
1126
                    pass
 
1127
            # Move the item into the root
 
1128
            self.tt.adjust_path(self.tt.final_name(trans_id),
 
1129
                                self.tt.root, trans_id)
 
1130
        if other_root_is_present:
 
1131
            self.tt.cancel_creation(other_root)
 
1132
            self.tt.cancel_versioning(other_root)
971
1133
 
972
1134
    def write_modified(self, results):
973
1135
        modified_hashes = {}
1020
1182
 
1021
1183
    @staticmethod
1022
1184
    def _three_way(base, other, this):
1023
 
        #if base == other, either they all agree, or only THIS has changed.
1024
1185
        if base == other:
 
1186
            # if 'base == other', either they all agree, or only 'this' has
 
1187
            # changed.
1025
1188
            return 'this'
1026
1189
        elif this not in (base, other):
 
1190
            # 'this' is neither 'base' nor 'other', so both sides changed
1027
1191
            return 'conflict'
1028
 
        # "Ambiguous clean merge" -- both sides have made the same change.
1029
1192
        elif this == other:
 
1193
            # "Ambiguous clean merge" -- both sides have made the same change.
1030
1194
            return "this"
1031
 
        # this == base: only other has changed.
1032
1195
        else:
 
1196
            # this == base: only other has changed.
1033
1197
            return "other"
1034
1198
 
1035
1199
    @staticmethod
1079
1243
                # only has an lca value
1080
1244
                return 'other'
1081
1245
 
1082
 
        # At this point, the lcas disagree, and the tips disagree
 
1246
        # At this point, the lcas disagree, and the tip disagree
1083
1247
        return 'conflict'
1084
1248
 
1085
1249
    @staticmethod
 
1250
    @deprecated_method(deprecated_in((2, 2, 0)))
1086
1251
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1087
1252
        """Do a three-way test on a scalar.
1088
1253
        Return "this", "other" or "conflict", depending whether a value wins.
1138
1303
                parent_id_winner = "other"
1139
1304
        if name_winner == "this" and parent_id_winner == "this":
1140
1305
            return
1141
 
        if name_winner == "conflict":
1142
 
            trans_id = self.tt.trans_id_file_id(file_id)
1143
 
            self._raw_conflicts.append(('name conflict', trans_id,
1144
 
                                        this_name, other_name))
1145
 
        if parent_id_winner == "conflict":
1146
 
            trans_id = self.tt.trans_id_file_id(file_id)
1147
 
            self._raw_conflicts.append(('parent conflict', trans_id,
1148
 
                                        this_parent, other_parent))
 
1306
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
 
1307
            # Creating helpers (.OTHER or .THIS) here cause problems down the
 
1308
            # road if a ContentConflict needs to be created so we should not do
 
1309
            # that
 
1310
            trans_id = self.tt.trans_id_file_id(file_id)
 
1311
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
 
1312
                                        this_parent, this_name,
 
1313
                                        other_parent, other_name))
1149
1314
        if other_name is None:
1150
1315
            # it doesn't matter whether the result was 'other' or
1151
1316
            # 'conflict'-- if there's no 'other', we leave it alone.
1152
1317
            return
1153
 
        # if we get here, name_winner and parent_winner are set to safe values.
1154
 
        trans_id = self.tt.trans_id_file_id(file_id)
1155
1318
        parent_id = parents[self.winner_idx[parent_id_winner]]
1156
1319
        if parent_id is not None:
1157
 
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1320
            # if we get here, name_winner and parent_winner are set to safe
 
1321
            # values.
1158
1322
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1159
 
                                parent_trans_id, trans_id)
 
1323
                                self.tt.trans_id_file_id(parent_id),
 
1324
                                self.tt.trans_id_file_id(file_id))
1160
1325
 
1161
 
    def merge_contents(self, file_id):
 
1326
    def _do_merge_contents(self, file_id):
1162
1327
        """Performs a merge on file_id contents."""
1163
1328
        def contents_pair(tree):
1164
1329
            if file_id not in tree:
1198
1363
        trans_id = self.tt.trans_id_file_id(file_id)
1199
1364
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1200
1365
            other_pair[0], winner)
1201
 
        hooks = Merger.hooks['merge_file_content']
1202
 
        hooks = list(hooks) + [self.default_text_merge]
 
1366
        hooks = self.active_hooks
1203
1367
        hook_status = 'not_applicable'
1204
1368
        for hook in hooks:
1205
 
            hook_status, lines = hook(params)
 
1369
            hook_status, lines = hook.merge_contents(params)
1206
1370
            if hook_status != 'not_applicable':
1207
1371
                # Don't try any more hooks, this one applies.
1208
1372
                break
1239
1403
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
1240
1404
        if not self.this_tree.has_id(file_id) and result == "modified":
1241
1405
            self.tt.version_file(file_id, trans_id)
 
1406
        # The merge has been performed, so the old contents should not be
 
1407
        # retained.
1242
1408
        try:
1243
 
            self.tt.tree_kind(trans_id)
1244
1409
            self.tt.delete_contents(trans_id)
1245
1410
        except errors.NoSuchFile:
1246
1411
            pass
1278
1443
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1279
1444
                % (file_id,))
1280
1445
 
1281
 
    def default_text_merge(self, merge_hook_params):
 
1446
    def merge_contents(self, merge_hook_params):
 
1447
        """Fallback merge logic after user installed hooks."""
 
1448
        # This function is used in merge hooks as the fallback instance.
 
1449
        # Perhaps making this function and the functions it calls be a 
 
1450
        # a separate class would be better.
1282
1451
        if merge_hook_params.winner == 'other':
1283
1452
            # OTHER is a straight winner, so replace this contents with other
1284
1453
            return self._default_other_winner_merge(merge_hook_params)
1298
1467
    def get_lines(self, tree, file_id):
1299
1468
        """Return the lines in a file, or an empty list."""
1300
1469
        if tree.has_id(file_id):
1301
 
            return tree.get_file(file_id).readlines()
 
1470
            return tree.get_file_lines(file_id)
1302
1471
        else:
1303
1472
            return []
1304
1473
 
1437
1606
 
1438
1607
    def cook_conflicts(self, fs_conflicts):
1439
1608
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1440
 
        name_conflicts = {}
1441
1609
        self.cooked_conflicts.extend(transform.cook_conflicts(
1442
1610
                fs_conflicts, self.tt))
1443
1611
        fp = transform.FinalPaths(self.tt)
1444
1612
        for conflict in self._raw_conflicts:
1445
1613
            conflict_type = conflict[0]
1446
 
            if conflict_type in ('name conflict', 'parent conflict'):
1447
 
                trans_id = conflict[1]
1448
 
                conflict_args = conflict[2:]
1449
 
                if trans_id not in name_conflicts:
1450
 
                    name_conflicts[trans_id] = {}
1451
 
                transform.unique_add(name_conflicts[trans_id], conflict_type,
1452
 
                                     conflict_args)
1453
 
            if conflict_type == 'contents conflict':
 
1614
            if conflict_type == 'path conflict':
 
1615
                (trans_id, file_id,
 
1616
                this_parent, this_name,
 
1617
                other_parent, other_name) = conflict[1:]
 
1618
                if this_parent is None or this_name is None:
 
1619
                    this_path = '<deleted>'
 
1620
                else:
 
1621
                    parent_path =  fp.get_path(
 
1622
                        self.tt.trans_id_file_id(this_parent))
 
1623
                    this_path = osutils.pathjoin(parent_path, this_name)
 
1624
                if other_parent is None or other_name is None:
 
1625
                    other_path = '<deleted>'
 
1626
                else:
 
1627
                    parent_path =  fp.get_path(
 
1628
                        self.tt.trans_id_file_id(other_parent))
 
1629
                    other_path = osutils.pathjoin(parent_path, other_name)
 
1630
                c = _mod_conflicts.Conflict.factory(
 
1631
                    'path conflict', path=this_path,
 
1632
                    conflict_path=other_path,
 
1633
                    file_id=file_id)
 
1634
            elif conflict_type == 'contents conflict':
1454
1635
                for trans_id in conflict[1]:
1455
1636
                    file_id = self.tt.final_file_id(trans_id)
1456
1637
                    if file_id is not None:
1462
1643
                        break
1463
1644
                c = _mod_conflicts.Conflict.factory(conflict_type,
1464
1645
                                                    path=path, file_id=file_id)
1465
 
                self.cooked_conflicts.append(c)
1466
 
            if conflict_type == 'text conflict':
 
1646
            elif conflict_type == 'text conflict':
1467
1647
                trans_id = conflict[1]
1468
1648
                path = fp.get_path(trans_id)
1469
1649
                file_id = self.tt.final_file_id(trans_id)
1470
1650
                c = _mod_conflicts.Conflict.factory(conflict_type,
1471
1651
                                                    path=path, file_id=file_id)
1472
 
                self.cooked_conflicts.append(c)
1473
 
 
1474
 
        for trans_id, conflicts in name_conflicts.iteritems():
1475
 
            try:
1476
 
                this_parent, other_parent = conflicts['parent conflict']
1477
 
                if this_parent == other_parent:
1478
 
                    raise AssertionError()
1479
 
            except KeyError:
1480
 
                this_parent = other_parent = \
1481
 
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
1482
 
            try:
1483
 
                this_name, other_name = conflicts['name conflict']
1484
 
                if this_name == other_name:
1485
 
                    raise AssertionError()
1486
 
            except KeyError:
1487
 
                this_name = other_name = self.tt.final_name(trans_id)
1488
 
            other_path = fp.get_path(trans_id)
1489
 
            if this_parent is not None and this_name is not None:
1490
 
                this_parent_path = \
1491
 
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
1492
 
                this_path = osutils.pathjoin(this_parent_path, this_name)
1493
1652
            else:
1494
 
                this_path = "<deleted>"
1495
 
            file_id = self.tt.final_file_id(trans_id)
1496
 
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1497
 
                                                conflict_path=other_path,
1498
 
                                                file_id=file_id)
 
1653
                raise AssertionError('bad conflict type: %r' % (conflict,))
1499
1654
            self.cooked_conflicts.append(c)
1500
1655
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1501
1656
 
1607
1762
            osutils.rmtree(temp_dir)
1608
1763
 
1609
1764
 
 
1765
class PathNotInTree(errors.BzrError):
 
1766
 
 
1767
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1768
 
 
1769
    def __init__(self, path, tree):
 
1770
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1771
 
 
1772
 
 
1773
class MergeIntoMerger(Merger):
 
1774
    """Merger that understands other_tree will be merged into a subdir.
 
1775
 
 
1776
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1777
    and RevisonTree objects, rather than using revision specs.
 
1778
    """
 
1779
 
 
1780
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1781
            source_subpath, other_rev_id=None):
 
1782
        """Create a new MergeIntoMerger object.
 
1783
 
 
1784
        source_subpath in other_tree will be effectively copied to
 
1785
        target_subdir in this_tree.
 
1786
 
 
1787
        :param this_tree: The tree that we will be merging into.
 
1788
        :param other_branch: The Branch we will be merging from.
 
1789
        :param other_tree: The RevisionTree object we want to merge.
 
1790
        :param target_subdir: The relative path where we want to merge
 
1791
            other_tree into this_tree
 
1792
        :param source_subpath: The relative path specifying the subtree of
 
1793
            other_tree to merge into this_tree.
 
1794
        """
 
1795
        # It is assumed that we are merging a tree that is not in our current
 
1796
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1797
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1798
                                _mod_revision.NULL_REVISION)
 
1799
        super(MergeIntoMerger, self).__init__(
 
1800
            this_branch=this_tree.branch,
 
1801
            this_tree=this_tree,
 
1802
            other_tree=other_tree,
 
1803
            base_tree=null_ancestor_tree,
 
1804
            )
 
1805
        self._target_subdir = target_subdir
 
1806
        self._source_subpath = source_subpath
 
1807
        self.other_branch = other_branch
 
1808
        if other_rev_id is None:
 
1809
            other_rev_id = other_tree.get_revision_id()
 
1810
        self.other_rev_id = self.other_basis = other_rev_id
 
1811
        self.base_is_ancestor = True
 
1812
        self.backup_files = True
 
1813
        self.merge_type = Merge3Merger
 
1814
        self.show_base = False
 
1815
        self.reprocess = False
 
1816
        self.interesting_ids = None
 
1817
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1818
              target_subdir=self._target_subdir,
 
1819
              source_subpath=self._source_subpath)
 
1820
        if self._source_subpath != '':
 
1821
            # If this isn't a partial merge make sure the revisions will be
 
1822
            # present.
 
1823
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1824
                self.other_basis)
 
1825
 
 
1826
    def set_pending(self):
 
1827
        if self._source_subpath != '':
 
1828
            return
 
1829
        Merger.set_pending(self)
 
1830
 
 
1831
 
 
1832
class _MergeTypeParameterizer(object):
 
1833
    """Wrap a merge-type class to provide extra parameters.
 
1834
    
 
1835
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1836
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1837
    the 'merge_type' member.  It is difficult override do_merge without
 
1838
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1839
    the extra parameters.
 
1840
    """
 
1841
 
 
1842
    def __init__(self, merge_type, **kwargs):
 
1843
        self._extra_kwargs = kwargs
 
1844
        self._merge_type = merge_type
 
1845
 
 
1846
    def __call__(self, *args, **kwargs):
 
1847
        kwargs.update(self._extra_kwargs)
 
1848
        return self._merge_type(*args, **kwargs)
 
1849
 
 
1850
    def __getattr__(self, name):
 
1851
        return getattr(self._merge_type, name)
 
1852
 
 
1853
 
 
1854
class MergeIntoMergeType(Merge3Merger):
 
1855
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1856
 
 
1857
    def __init__(self, *args, **kwargs):
 
1858
        """Initialize the merger object.
 
1859
 
 
1860
        :param args: See Merge3Merger.__init__'s args.
 
1861
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1862
            source_subpath and target_subdir.
 
1863
        :keyword source_subpath: The relative path specifying the subtree of
 
1864
            other_tree to merge into this_tree.
 
1865
        :keyword target_subdir: The relative path where we want to merge
 
1866
            other_tree into this_tree
 
1867
        """
 
1868
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1869
        # so we have have to hack in to get our extra parameters set.
 
1870
        self._source_subpath = kwargs.pop('source_subpath')
 
1871
        self._target_subdir = kwargs.pop('target_subdir')
 
1872
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1873
 
 
1874
    def _compute_transform(self):
 
1875
        child_pb = ui.ui_factory.nested_progress_bar()
 
1876
        try:
 
1877
            entries = self._entries_to_incorporate()
 
1878
            entries = list(entries)
 
1879
            for num, (entry, parent_id) in enumerate(entries):
 
1880
                child_pb.update('Preparing file merge', num, len(entries))
 
1881
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1882
                trans_id = transform.new_by_entry(self.tt, entry,
 
1883
                    parent_trans_id, self.other_tree)
 
1884
        finally:
 
1885
            child_pb.finished()
 
1886
        self._finish_computing_transform()
 
1887
 
 
1888
    def _entries_to_incorporate(self):
 
1889
        """Yields pairs of (inventory_entry, new_parent)."""
 
1890
        other_inv = self.other_tree.inventory
 
1891
        subdir_id = other_inv.path2id(self._source_subpath)
 
1892
        if subdir_id is None:
 
1893
            # XXX: The error would be clearer if it gave the URL of the source
 
1894
            # branch, but we don't have a reference to that here.
 
1895
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1896
        subdir = other_inv[subdir_id]
 
1897
        parent_in_target = osutils.dirname(self._target_subdir)
 
1898
        target_id = self.this_tree.inventory.path2id(parent_in_target)
 
1899
        if target_id is None:
 
1900
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1901
        name_in_target = osutils.basename(self._target_subdir)
 
1902
        merge_into_root = subdir.copy()
 
1903
        merge_into_root.name = name_in_target
 
1904
        if merge_into_root.file_id in self.this_tree.inventory:
 
1905
            # Give the root a new file-id.
 
1906
            # This can happen fairly easily if the directory we are
 
1907
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1908
            # their root_id.  Users will expect this to Just Work, so we
 
1909
            # change the file-id here.
 
1910
            # Non-root file-ids could potentially conflict too.  That's really
 
1911
            # an edge case, so we don't do anything special for those.  We let
 
1912
            # them cause conflicts.
 
1913
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1914
        yield (merge_into_root, target_id)
 
1915
        if subdir.kind != 'directory':
 
1916
            # No children, so we are done.
 
1917
            return
 
1918
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1919
            parent_id = entry.parent_id
 
1920
            if parent_id == subdir.file_id:
 
1921
                # The root's parent ID has changed, so make sure children of
 
1922
                # the root refer to the new ID.
 
1923
                parent_id = merge_into_root.file_id
 
1924
            yield (entry, parent_id)
 
1925
 
 
1926
 
1610
1927
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1611
1928
                backup_files=False,
1612
1929
                merge_type=Merge3Merger,
1616
1933
                other_rev_id=None,
1617
1934
                interesting_files=None,
1618
1935
                this_tree=None,
1619
 
                pb=progress.DummyProgress(),
 
1936
                pb=None,
1620
1937
                change_reporter=None):
1621
1938
    """Primary interface for merging.
1622
1939
 
1623
 
        typical use is probably
1624
 
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1625
 
                     branch.get_revision_tree(base_revision))'
1626
 
        """
 
1940
    Typical use is probably::
 
1941
 
 
1942
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1943
                    branch.get_revision_tree(base_revision))
 
1944
    """
1627
1945
    if this_tree is None:
1628
1946
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1629
1947
                              "parameter as of bzrlib version 0.8.")