/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Vincent Ladeuil
  • Date: 2010-01-25 17:48:22 UTC
  • mto: (4987.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4988.
  • Revision ID: v.ladeuil+lp@free.fr-20100125174822-nce4l19sbwx83jvq
Deploying the new overrideAttr facility further reduces the complexity
and make the code clearer.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008 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
18
17
 
19
 
from bzrlib.lazy_import import lazy_import
20
 
lazy_import(globals(), """
21
18
from bzrlib import (
22
19
    branch as _mod_branch,
23
20
    conflicts as _mod_conflicts,
24
21
    debug,
25
 
    generate_ids,
 
22
    decorators,
 
23
    errors,
26
24
    graph as _mod_graph,
 
25
    hooks,
27
26
    merge3,
28
27
    osutils,
29
28
    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,
38
 
    workingtree,
39
 
    )
40
 
from bzrlib.cleanup import OperationWithCleanups
41
 
""")
42
 
from bzrlib import (
43
 
    decorators,
44
 
    errors,
45
 
    hooks,
 
37
    versionedfile
46
38
    )
47
39
from bzrlib.symbol_versioning import (
48
40
    deprecated_in,
53
45
 
54
46
def transform_tree(from_tree, to_tree, interesting_ids=None):
55
47
    from_tree.lock_tree_write()
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)
 
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()
60
53
 
61
54
 
62
55
class MergeHooks(hooks.Hooks):
64
57
    def __init__(self):
65
58
        hooks.Hooks.__init__(self)
66
59
        self.create_hook(hooks.HookPoint('merge_file_content',
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.",
 
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 strings of the "
 
67
            "new file contents.",
76
68
            (2, 1), None))
77
69
 
78
70
 
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
 
 
223
71
class MergeHookParams(object):
224
72
    """Object holding parameters passed to merge_file_content hooks.
225
73
 
226
 
    There are some fields hooks can access:
 
74
    There are 3 fields hooks can access:
227
75
 
 
76
    :ivar merger: the Merger object
228
77
    :ivar file_id: the file ID of the file being merged
229
78
    :ivar trans_id: the transform ID for the merge of this file
230
79
    :ivar this_kind: kind of file_id in 'this' tree
234
83
 
235
84
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
236
85
            winner):
237
 
        self._merger = merger
 
86
        self.merger = merger
238
87
        self.file_id = file_id
239
88
        self.trans_id = trans_id
240
89
        self.this_kind = this_kind
248
97
    @decorators.cachedproperty
249
98
    def base_lines(self):
250
99
        """The lines of the 'base' version of the file."""
251
 
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
 
100
        return self.merger.get_lines(self.merger.base_tree, self.file_id)
252
101
 
253
102
    @decorators.cachedproperty
254
103
    def this_lines(self):
255
104
        """The lines of the 'this' version of the file."""
256
 
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
 
105
        return self.merger.get_lines(self.merger.this_tree, self.file_id)
257
106
 
258
107
    @decorators.cachedproperty
259
108
    def other_lines(self):
260
109
        """The lines of the 'other' version of the file."""
261
 
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
 
110
        return self.merger.get_lines(self.merger.other_tree, self.file_id)
262
111
 
263
112
 
264
113
class Merger(object):
285
134
        self.interesting_files = None
286
135
        self.show_base = False
287
136
        self.reprocess = False
288
 
        if pb is not None:
289
 
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
 
137
        if pb is None:
 
138
            pb = progress.DummyProgress()
 
139
        self._pb = pb
290
140
        self.pp = None
291
141
        self.recurse = recurse
292
142
        self.change_reporter = change_reporter
428
278
        return self._cached_trees[revision_id]
429
279
 
430
280
    def _get_tree(self, treespec, possible_transports=None):
 
281
        from bzrlib import workingtree
431
282
        location, revno = treespec
432
283
        if revno is None:
433
284
            tree = workingtree.WorkingTree.open_containing(location)[0]
501
352
    def _add_parent(self):
502
353
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
503
354
        new_parent_trees = []
504
 
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
505
355
        for revision_id in new_parents:
506
356
            try:
507
357
                tree = self.revision_tree(revision_id)
509
359
                tree = None
510
360
            else:
511
361
                tree.lock_read()
512
 
                operation.add_cleanup(tree.unlock)
513
362
            new_parent_trees.append((revision_id, tree))
514
 
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
 
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()
515
370
 
516
371
    def set_other(self, other_revision, possible_transports=None):
517
372
        """Set the revision and tree to merge from.
640
495
                  'other_tree': self.other_tree,
641
496
                  'interesting_ids': self.interesting_ids,
642
497
                  'interesting_files': self.interesting_files,
643
 
                  'this_branch': self.this_branch,
 
498
                  'pp': self.pp, 'this_branch': self.this_branch,
644
499
                  'do_merge': False}
645
500
        if self.merge_type.requires_base:
646
501
            kwargs['base_tree'] = self.base_tree
664
519
        if self._is_criss_cross and getattr(self.merge_type,
665
520
                                            'supports_lca_trees', False):
666
521
            kwargs['lca_trees'] = self._lca_trees
667
 
        return self.merge_type(pb=None,
 
522
        return self.merge_type(pb=self._pb,
668
523
                               change_reporter=self.change_reporter,
669
524
                               **kwargs)
670
525
 
671
 
    def _do_merge_to(self):
672
 
        merge = self.make_merger()
 
526
    def _do_merge_to(self, merge):
673
527
        if self.other_branch is not None:
674
528
            self.other_branch.update_references(self.this_branch)
675
529
        merge.do_merge()
689
543
                    sub_tree.branch.repository.revision_tree(base_revision)
690
544
                sub_merge.base_rev_id = base_revision
691
545
                sub_merge.do_merge()
692
 
        return merge
693
546
 
694
547
    def do_merge(self):
695
 
        operation = OperationWithCleanups(self._do_merge_to)
696
548
        self.this_tree.lock_tree_write()
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()
 
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()
705
566
        if len(merge.cooked_conflicts) == 0:
706
567
            if not self.ignore_zero and not trace.is_quiet():
707
568
                trace.note("All changes applied successfully.")
742
603
 
743
604
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
744
605
                 interesting_ids=None, reprocess=False, show_base=False,
745
 
                 pb=None, pp=None, change_reporter=None,
 
606
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
746
607
                 interesting_files=None, do_merge=True,
747
608
                 cherrypick=False, lca_trees=None, this_branch=None):
748
609
        """Initialize the merger object and perform the merge.
751
612
        :param this_tree: The local tree in the merge operation
752
613
        :param base_tree: The common tree in the merge operation
753
614
        :param other_tree: The other tree to merge changes from
754
 
        :param this_branch: The branch associated with this_tree.  Defaults to
755
 
            this_tree.branch if not supplied.
 
615
        :param this_branch: The branch associated with this_tree
756
616
        :param interesting_ids: The file_ids of files that should be
757
617
            participate in the merge.  May not be combined with
758
618
            interesting_files.
759
619
        :param: reprocess If True, perform conflict-reduction processing.
760
620
        :param show_base: If True, show the base revision in text conflicts.
761
621
            (incompatible with reprocess)
762
 
        :param pb: ignored
 
622
        :param pb: A Progress bar
763
623
        :param pp: A ProgressPhase object
764
624
        :param change_reporter: An object that should report changes made
765
625
        :param interesting_files: The tree-relative paths of files that should
776
636
        if interesting_files is not None and interesting_ids is not None:
777
637
            raise ValueError(
778
638
                'specify either interesting_ids or interesting_files')
779
 
        if this_branch is None:
780
 
            this_branch = this_tree.branch
781
639
        self.interesting_ids = interesting_ids
782
640
        self.interesting_files = interesting_files
783
641
        self.this_tree = working_tree
794
652
        # making sure we haven't missed any corner cases.
795
653
        # if lca_trees is None:
796
654
        #     self._lca_trees = [self.base_tree]
 
655
        self.pb = pb
 
656
        self.pp = pp
797
657
        self.change_reporter = change_reporter
798
658
        self.cherrypick = cherrypick
 
659
        if self.pp is None:
 
660
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
799
661
        if do_merge:
800
662
            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")
805
663
 
806
664
    def do_merge(self):
807
 
        operation = OperationWithCleanups(self._do_merge)
808
665
        self.this_tree.lock_tree_write()
809
 
        operation.add_cleanup(self.this_tree.unlock)
810
666
        self.base_tree.lock_read()
811
 
        operation.add_cleanup(self.base_tree.unlock)
812
667
        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)
822
668
        try:
823
 
            self.this_tree.add_conflicts(self.cooked_conflicts)
824
 
        except errors.UnsupportedOperation:
825
 
            pass
 
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()
826
687
 
827
688
    def make_preview_transform(self):
828
 
        operation = OperationWithCleanups(self._make_preview_transform)
829
689
        self.base_tree.lock_read()
830
 
        operation.add_cleanup(self.base_tree.unlock)
831
690
        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):
836
691
        self.tt = transform.TransformPreview(self.this_tree)
837
 
        self._compute_transform()
 
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()
838
700
        return self.tt
839
701
 
840
702
    def _compute_transform(self):
846
708
            resolver = self._lca_multi_way
847
709
        child_pb = ui.ui_factory.nested_progress_bar()
848
710
        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]
852
711
            for num, (file_id, changed, parents3, names3,
853
712
                      executable3) in enumerate(entries):
854
713
                child_pb.update('Preparing file merge', num, len(entries))
855
714
                self._merge_names(file_id, parents3, names3, resolver=resolver)
856
715
                if changed:
857
 
                    file_status = self._do_merge_contents(file_id)
 
716
                    file_status = self.merge_contents(file_id)
858
717
                else:
859
718
                    file_status = 'unmodified'
860
719
                self._merge_executable(file_id,
862
721
        finally:
863
722
            child_pb.finished()
864
723
        self.fix_root()
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
 
        """
 
724
        self.pp.next_phase()
872
725
        child_pb = ui.ui_factory.nested_progress_bar()
873
726
        try:
874
727
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1072
925
                        continue
1073
926
                else:
1074
927
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
928
                # XXX: We need to handle kind == 'symlink'
1075
929
 
1076
930
            # If we have gotten this far, that means something has changed
1077
931
            result.append((file_id, content_changed,
1084
938
                          ))
1085
939
        return result
1086
940
 
 
941
 
1087
942
    def fix_root(self):
1088
943
        try:
1089
944
            self.tt.final_kind(self.tt.root)
1098
953
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1099
954
        if other_root == self.tt.root:
1100
955
            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
1105
956
        try:
1106
957
            self.tt.final_kind(other_root)
1107
 
            other_root_is_present = True
1108
958
        except errors.NoSuchFile:
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():
 
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():
1116
969
            trans_id = self.tt.trans_id_file_id(child.file_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)
 
970
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1133
971
 
1134
972
    def write_modified(self, results):
1135
973
        modified_hashes = {}
1182
1020
 
1183
1021
    @staticmethod
1184
1022
    def _three_way(base, other, this):
 
1023
        #if base == other, either they all agree, or only THIS has changed.
1185
1024
        if base == other:
1186
 
            # if 'base == other', either they all agree, or only 'this' has
1187
 
            # changed.
1188
1025
            return 'this'
1189
1026
        elif this not in (base, other):
1190
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1191
1027
            return 'conflict'
 
1028
        # "Ambiguous clean merge" -- both sides have made the same change.
1192
1029
        elif this == other:
1193
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1194
1030
            return "this"
 
1031
        # this == base: only other has changed.
1195
1032
        else:
1196
 
            # this == base: only other has changed.
1197
1033
            return "other"
1198
1034
 
1199
1035
    @staticmethod
1243
1079
                # only has an lca value
1244
1080
                return 'other'
1245
1081
 
1246
 
        # At this point, the lcas disagree, and the tip disagree
 
1082
        # At this point, the lcas disagree, and the tips disagree
1247
1083
        return 'conflict'
1248
1084
 
1249
1085
    @staticmethod
1250
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1251
1086
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1252
1087
        """Do a three-way test on a scalar.
1253
1088
        Return "this", "other" or "conflict", depending whether a value wins.
1303
1138
                parent_id_winner = "other"
1304
1139
        if name_winner == "this" and parent_id_winner == "this":
1305
1140
            return
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))
 
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))
1314
1149
        if other_name is None:
1315
1150
            # it doesn't matter whether the result was 'other' or
1316
1151
            # 'conflict'-- if there's no 'other', we leave it alone.
1317
1152
            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)
1318
1155
        parent_id = parents[self.winner_idx[parent_id_winner]]
1319
1156
        if parent_id is not None:
1320
 
            # if we get here, name_winner and parent_winner are set to safe
1321
 
            # values.
 
1157
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
1322
1158
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1323
 
                                self.tt.trans_id_file_id(parent_id),
1324
 
                                self.tt.trans_id_file_id(file_id))
 
1159
                                parent_trans_id, trans_id)
1325
1160
 
1326
 
    def _do_merge_contents(self, file_id):
 
1161
    def merge_contents(self, file_id):
1327
1162
        """Performs a merge on file_id contents."""
1328
1163
        def contents_pair(tree):
1329
1164
            if file_id not in tree:
1363
1198
        trans_id = self.tt.trans_id_file_id(file_id)
1364
1199
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1365
1200
            other_pair[0], winner)
1366
 
        hooks = self.active_hooks
 
1201
        hooks = Merger.hooks['merge_file_content']
 
1202
        hooks = list(hooks) + [self.default_text_merge]
1367
1203
        hook_status = 'not_applicable'
1368
1204
        for hook in hooks:
1369
 
            hook_status, lines = hook.merge_contents(params)
 
1205
            hook_status, lines = hook(params)
1370
1206
            if hook_status != 'not_applicable':
1371
1207
                # Don't try any more hooks, this one applies.
1372
1208
                break
1443
1279
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1444
1280
                % (file_id,))
1445
1281
 
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
    def default_text_merge(self, merge_hook_params):
1451
1283
        if merge_hook_params.winner == 'other':
1452
1284
            # OTHER is a straight winner, so replace this contents with other
1453
1285
            return self._default_other_winner_merge(merge_hook_params)
1467
1299
    def get_lines(self, tree, file_id):
1468
1300
        """Return the lines in a file, or an empty list."""
1469
1301
        if tree.has_id(file_id):
1470
 
            return tree.get_file_lines(file_id)
 
1302
            return tree.get_file(file_id).readlines()
1471
1303
        else:
1472
1304
            return []
1473
1305
 
1606
1438
 
1607
1439
    def cook_conflicts(self, fs_conflicts):
1608
1440
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1441
        name_conflicts = {}
1609
1442
        self.cooked_conflicts.extend(transform.cook_conflicts(
1610
1443
                fs_conflicts, self.tt))
1611
1444
        fp = transform.FinalPaths(self.tt)
1612
1445
        for conflict in self._raw_conflicts:
1613
1446
            conflict_type = conflict[0]
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':
 
1447
            if conflict_type in ('name conflict', 'parent conflict'):
 
1448
                trans_id = conflict[1]
 
1449
                conflict_args = conflict[2:]
 
1450
                if trans_id not in name_conflicts:
 
1451
                    name_conflicts[trans_id] = {}
 
1452
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1453
                                     conflict_args)
 
1454
            if conflict_type == 'contents conflict':
1635
1455
                for trans_id in conflict[1]:
1636
1456
                    file_id = self.tt.final_file_id(trans_id)
1637
1457
                    if file_id is not None:
1643
1463
                        break
1644
1464
                c = _mod_conflicts.Conflict.factory(conflict_type,
1645
1465
                                                    path=path, file_id=file_id)
1646
 
            elif conflict_type == 'text conflict':
 
1466
                self.cooked_conflicts.append(c)
 
1467
            if conflict_type == 'text conflict':
1647
1468
                trans_id = conflict[1]
1648
1469
                path = fp.get_path(trans_id)
1649
1470
                file_id = self.tt.final_file_id(trans_id)
1650
1471
                c = _mod_conflicts.Conflict.factory(conflict_type,
1651
1472
                                                    path=path, file_id=file_id)
 
1473
                self.cooked_conflicts.append(c)
 
1474
 
 
1475
        for trans_id, conflicts in name_conflicts.iteritems():
 
1476
            try:
 
1477
                this_parent, other_parent = conflicts['parent conflict']
 
1478
                if this_parent == other_parent:
 
1479
                    raise AssertionError()
 
1480
            except KeyError:
 
1481
                this_parent = other_parent = \
 
1482
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1483
            try:
 
1484
                this_name, other_name = conflicts['name conflict']
 
1485
                if this_name == other_name:
 
1486
                    raise AssertionError()
 
1487
            except KeyError:
 
1488
                this_name = other_name = self.tt.final_name(trans_id)
 
1489
            other_path = fp.get_path(trans_id)
 
1490
            if this_parent is not None and this_name is not None:
 
1491
                this_parent_path = \
 
1492
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1493
                this_path = osutils.pathjoin(this_parent_path, this_name)
1652
1494
            else:
1653
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1495
                this_path = "<deleted>"
 
1496
            file_id = self.tt.final_file_id(trans_id)
 
1497
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1498
                                                conflict_path=other_path,
 
1499
                                                file_id=file_id)
1654
1500
            self.cooked_conflicts.append(c)
1655
1501
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1656
1502
 
1762
1608
            osutils.rmtree(temp_dir)
1763
1609
 
1764
1610
 
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
 
 
1927
1611
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1928
1612
                backup_files=False,
1929
1613
                merge_type=Merge3Merger,
1933
1617
                other_rev_id=None,
1934
1618
                interesting_files=None,
1935
1619
                this_tree=None,
1936
 
                pb=None,
 
1620
                pb=progress.DummyProgress(),
1937
1621
                change_reporter=None):
1938
1622
    """Primary interface for merging.
1939
1623
 
1940
 
    Typical use is probably::
1941
 
 
1942
 
        merge_inner(branch, branch.get_revision_tree(other_revision),
1943
 
                    branch.get_revision_tree(base_revision))
1944
 
    """
 
1624
        typical use is probably
 
1625
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
1626
                     branch.get_revision_tree(base_revision))'
 
1627
        """
1945
1628
    if this_tree is None:
1946
1629
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1947
1630
                              "parameter as of bzrlib version 0.8.")