~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Martin Pool
  • Date: 2010-02-03 00:08:23 UTC
  • mto: This revision was merged to the branch mainline in revision 5002.
  • Revision ID: mbp@sourcefrog.net-20100203000823-fcyf2791xrl3fbfo
expand tabs

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
18
from bzrlib import (
20
19
    branch as _mod_branch,
27
26
    merge3,
28
27
    osutils,
29
28
    patiencediff,
 
29
    progress,
30
30
    revision as _mod_revision,
31
31
    textfile,
32
32
    trace,
36
36
    ui,
37
37
    versionedfile
38
38
    )
39
 
from bzrlib.cleanup import OperationWithCleanups
40
39
from bzrlib.symbol_versioning import (
41
40
    deprecated_in,
42
41
    deprecated_method,
46
45
 
47
46
def transform_tree(from_tree, to_tree, interesting_ids=None):
48
47
    from_tree.lock_tree_write()
49
 
    operation = OperationWithCleanups(merge_inner)
50
 
    operation.add_cleanup(from_tree.unlock)
51
 
    operation.run_simple(from_tree.branch, to_tree, from_tree,
52
 
        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()
53
53
 
54
54
 
55
55
class MergeHooks(hooks.Hooks):
93
93
        return ('not applicable', None)
94
94
 
95
95
 
96
 
class PerFileMerger(AbstractPerFileMerger):
97
 
    """Merge individual files when self.file_matches returns True.
98
 
 
99
 
    This class is intended to be subclassed.  The file_matches and
100
 
    merge_matching methods should be overridden with concrete implementations.
101
 
    """
102
 
 
103
 
    def file_matches(self, params):
104
 
        """Return True if merge_matching should be called on this file.
105
 
 
106
 
        Only called with merges of plain files with no clear winner.
107
 
 
108
 
        Subclasses must override this.
109
 
        """
110
 
        raise NotImplementedError(self.file_matches)
111
 
 
112
 
    def get_filename(self, params, tree):
113
 
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
114
 
        self.merger.this_tree) and a MergeHookParams.
115
 
        """
116
 
        return osutils.basename(tree.id2path(params.file_id))
117
 
 
118
 
    def get_filepath(self, params, tree):
119
 
        """Calculate the path to the file in a tree.
120
 
 
121
 
        :param params: A MergeHookParams describing the file to merge
122
 
        :param tree: a Tree, e.g. self.merger.this_tree.
123
 
        """
124
 
        return tree.id2path(params.file_id)
125
 
 
126
 
    def merge_contents(self, params):
127
 
        """Merge the contents of a single file."""
128
 
        # Check whether this custom merge logic should be used.
129
 
        if (
130
 
            # OTHER is a straight winner, rely on default merge.
131
 
            params.winner == 'other' or
132
 
            # THIS and OTHER aren't both files.
133
 
            not params.is_file_merge() or
134
 
            # The filename doesn't match *.xml
135
 
            not self.file_matches(params)):
136
 
            return 'not_applicable', None
137
 
        return self.merge_matching(params)
138
 
 
139
 
    def merge_matching(self, params):
140
 
        """Merge the contents of a single file that has matched the criteria
141
 
        in PerFileMerger.merge_contents (is a conflict, is a file,
142
 
        self.file_matches is True).
143
 
 
144
 
        Subclasses must override this.
145
 
        """
146
 
        raise NotImplementedError(self.merge_matching)
147
 
 
148
 
 
149
 
class ConfigurableFileMerger(PerFileMerger):
 
96
class ConfigurableFileMerger(AbstractPerFileMerger):
150
97
    """Merge individual files when configured via a .conf file.
151
98
 
152
99
    This is a base class for concrete custom file merging logic. Concrete
153
100
    classes should implement ``merge_text``.
154
101
 
155
 
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
156
 
    
157
102
    :ivar affected_files: The configured file paths to merge.
158
 
 
159
103
    :cvar name_prefix: The prefix to use when looking up configuration
160
 
        details. <name_prefix>_merge_files describes the files targeted by the
161
 
        hook for example.
162
 
        
 
104
        details.
163
105
    :cvar default_files: The default file paths to merge when no configuration
164
106
        is present.
165
107
    """
175
117
        if self.name_prefix is None:
176
118
            raise ValueError("name_prefix must be set.")
177
119
 
178
 
    def file_matches(self, params):
179
 
        """Check whether the file should call the merge hook.
180
 
 
181
 
        <name_prefix>_merge_files configuration variable is a list of files
182
 
        that should use the hook.
183
 
        """
 
120
    def filename_matches_config(self, params):
184
121
        affected_files = self.affected_files
185
122
        if affected_files is None:
186
 
            config = self.merger.this_branch.get_config()
 
123
            config = self.merger.this_tree.branch.get_config()
187
124
            # Until bzr provides a better policy for caching the config, we
188
125
            # just add the part we're interested in to the params to avoid
189
126
            # reading the config files repeatedly (bazaar.conf, location.conf,
195
132
                affected_files = self.default_files
196
133
            self.affected_files = affected_files
197
134
        if affected_files:
198
 
            filepath = self.get_filepath(params, self.merger.this_tree)
199
 
            if filepath in affected_files:
 
135
            filename = self.merger.this_tree.id2path(params.file_id)
 
136
            if filename in affected_files:
200
137
                return True
201
138
        return False
202
139
 
203
 
    def merge_matching(self, params):
204
 
        return self.merge_text(params)
 
140
    def merge_contents(self, params):
 
141
        """Merge the contents of a single file."""
 
142
        # First, check whether this custom merge logic should be used.  We
 
143
        # expect most files should not be merged by this handler.
 
144
        if (
 
145
            # OTHER is a straight winner, rely on default merge.
 
146
            params.winner == 'other' or
 
147
            # THIS and OTHER aren't both files.
 
148
            not params.is_file_merge() or
 
149
            # The filename isn't listed in the 'NAME_merge_files' config
 
150
            # option.
 
151
            not self.filename_matches_config(params)):
 
152
            return 'not_applicable', None
 
153
        return self.merge_text(self, params)
205
154
 
206
155
    def merge_text(self, params):
207
156
        """Merge the byte contents of a single file.
278
227
        self.interesting_files = None
279
228
        self.show_base = False
280
229
        self.reprocess = False
281
 
        if pb is not None:
282
 
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
 
230
        if pb is None:
 
231
            pb = progress.DummyProgress()
 
232
        self._pb = pb
283
233
        self.pp = None
284
234
        self.recurse = recurse
285
235
        self.change_reporter = change_reporter
495
445
    def _add_parent(self):
496
446
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
497
447
        new_parent_trees = []
498
 
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
499
448
        for revision_id in new_parents:
500
449
            try:
501
450
                tree = self.revision_tree(revision_id)
503
452
                tree = None
504
453
            else:
505
454
                tree.lock_read()
506
 
                operation.add_cleanup(tree.unlock)
507
455
            new_parent_trees.append((revision_id, tree))
508
 
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
 
456
        try:
 
457
            self.this_tree.set_parent_trees(new_parent_trees,
 
458
                                            allow_leftmost_as_ghost=True)
 
459
        finally:
 
460
            for _revision_id, tree in new_parent_trees:
 
461
                if tree is not None:
 
462
                    tree.unlock()
509
463
 
510
464
    def set_other(self, other_revision, possible_transports=None):
511
465
        """Set the revision and tree to merge from.
634
588
                  'other_tree': self.other_tree,
635
589
                  'interesting_ids': self.interesting_ids,
636
590
                  'interesting_files': self.interesting_files,
637
 
                  'this_branch': self.this_branch,
 
591
                  'pp': self.pp, 'this_branch': self.this_branch,
638
592
                  'do_merge': False}
639
593
        if self.merge_type.requires_base:
640
594
            kwargs['base_tree'] = self.base_tree
658
612
        if self._is_criss_cross and getattr(self.merge_type,
659
613
                                            'supports_lca_trees', False):
660
614
            kwargs['lca_trees'] = self._lca_trees
661
 
        return self.merge_type(pb=None,
 
615
        return self.merge_type(pb=self._pb,
662
616
                               change_reporter=self.change_reporter,
663
617
                               **kwargs)
664
618
 
665
 
    def _do_merge_to(self):
666
 
        merge = self.make_merger()
 
619
    def _do_merge_to(self, merge):
667
620
        if self.other_branch is not None:
668
621
            self.other_branch.update_references(self.this_branch)
669
622
        merge.do_merge()
683
636
                    sub_tree.branch.repository.revision_tree(base_revision)
684
637
                sub_merge.base_rev_id = base_revision
685
638
                sub_merge.do_merge()
686
 
        return merge
687
639
 
688
640
    def do_merge(self):
689
 
        operation = OperationWithCleanups(self._do_merge_to)
690
641
        self.this_tree.lock_tree_write()
691
 
        operation.add_cleanup(self.this_tree.unlock)
692
 
        if self.base_tree is not None:
693
 
            self.base_tree.lock_read()
694
 
            operation.add_cleanup(self.base_tree.unlock)
695
 
        if self.other_tree is not None:
696
 
            self.other_tree.lock_read()
697
 
            operation.add_cleanup(self.other_tree.unlock)
698
 
        merge = operation.run_simple()
 
642
        try:
 
643
            if self.base_tree is not None:
 
644
                self.base_tree.lock_read()
 
645
            try:
 
646
                if self.other_tree is not None:
 
647
                    self.other_tree.lock_read()
 
648
                try:
 
649
                    merge = self.make_merger()
 
650
                    self._do_merge_to(merge)
 
651
                finally:
 
652
                    if self.other_tree is not None:
 
653
                        self.other_tree.unlock()
 
654
            finally:
 
655
                if self.base_tree is not None:
 
656
                    self.base_tree.unlock()
 
657
        finally:
 
658
            self.this_tree.unlock()
699
659
        if len(merge.cooked_conflicts) == 0:
700
660
            if not self.ignore_zero and not trace.is_quiet():
701
661
                trace.note("All changes applied successfully.")
736
696
 
737
697
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
738
698
                 interesting_ids=None, reprocess=False, show_base=False,
739
 
                 pb=None, pp=None, change_reporter=None,
 
699
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
740
700
                 interesting_files=None, do_merge=True,
741
701
                 cherrypick=False, lca_trees=None, this_branch=None):
742
702
        """Initialize the merger object and perform the merge.
745
705
        :param this_tree: The local tree in the merge operation
746
706
        :param base_tree: The common tree in the merge operation
747
707
        :param other_tree: The other tree to merge changes from
748
 
        :param this_branch: The branch associated with this_tree.  Defaults to
749
 
            this_tree.branch if not supplied.
 
708
        :param this_branch: The branch associated with this_tree
750
709
        :param interesting_ids: The file_ids of files that should be
751
710
            participate in the merge.  May not be combined with
752
711
            interesting_files.
753
712
        :param: reprocess If True, perform conflict-reduction processing.
754
713
        :param show_base: If True, show the base revision in text conflicts.
755
714
            (incompatible with reprocess)
756
 
        :param pb: ignored
 
715
        :param pb: A Progress bar
757
716
        :param pp: A ProgressPhase object
758
717
        :param change_reporter: An object that should report changes made
759
718
        :param interesting_files: The tree-relative paths of files that should
770
729
        if interesting_files is not None and interesting_ids is not None:
771
730
            raise ValueError(
772
731
                'specify either interesting_ids or interesting_files')
773
 
        if this_branch is None:
774
 
            this_branch = this_tree.branch
775
732
        self.interesting_ids = interesting_ids
776
733
        self.interesting_files = interesting_files
777
734
        self.this_tree = working_tree
788
745
        # making sure we haven't missed any corner cases.
789
746
        # if lca_trees is None:
790
747
        #     self._lca_trees = [self.base_tree]
 
748
        self.pb = pb
 
749
        self.pp = pp
791
750
        self.change_reporter = change_reporter
792
751
        self.cherrypick = cherrypick
 
752
        if self.pp is None:
 
753
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
793
754
        if do_merge:
794
755
            self.do_merge()
795
 
        if pp is not None:
796
 
            warnings.warn("pp argument to Merge3Merger is deprecated")
797
 
        if pb is not None:
798
 
            warnings.warn("pb argument to Merge3Merger is deprecated")
799
756
 
800
757
    def do_merge(self):
801
 
        operation = OperationWithCleanups(self._do_merge)
802
758
        self.this_tree.lock_tree_write()
803
 
        operation.add_cleanup(self.this_tree.unlock)
804
759
        self.base_tree.lock_read()
805
 
        operation.add_cleanup(self.base_tree.unlock)
806
760
        self.other_tree.lock_read()
807
 
        operation.add_cleanup(self.other_tree.unlock)
808
 
        operation.run()
809
 
 
810
 
    def _do_merge(self, operation):
811
 
        self.tt = transform.TreeTransform(self.this_tree, None)
812
 
        operation.add_cleanup(self.tt.finalize)
813
 
        self._compute_transform()
814
 
        results = self.tt.apply(no_conflicts=True)
815
 
        self.write_modified(results)
816
761
        try:
817
 
            self.this_tree.add_conflicts(self.cooked_conflicts)
818
 
        except errors.UnsupportedOperation:
819
 
            pass
 
762
            self.tt = transform.TreeTransform(self.this_tree, self.pb)
 
763
            try:
 
764
                self.pp.next_phase()
 
765
                self._compute_transform()
 
766
                self.pp.next_phase()
 
767
                results = self.tt.apply(no_conflicts=True)
 
768
                self.write_modified(results)
 
769
                try:
 
770
                    self.this_tree.add_conflicts(self.cooked_conflicts)
 
771
                except errors.UnsupportedOperation:
 
772
                    pass
 
773
            finally:
 
774
                self.tt.finalize()
 
775
        finally:
 
776
            self.other_tree.unlock()
 
777
            self.base_tree.unlock()
 
778
            self.this_tree.unlock()
 
779
            self.pb.clear()
820
780
 
821
781
    def make_preview_transform(self):
822
 
        operation = OperationWithCleanups(self._make_preview_transform)
823
782
        self.base_tree.lock_read()
824
 
        operation.add_cleanup(self.base_tree.unlock)
825
783
        self.other_tree.lock_read()
826
 
        operation.add_cleanup(self.other_tree.unlock)
827
 
        return operation.run_simple()
828
 
 
829
 
    def _make_preview_transform(self):
830
784
        self.tt = transform.TransformPreview(self.this_tree)
831
 
        self._compute_transform()
 
785
        try:
 
786
            self.pp.next_phase()
 
787
            self._compute_transform()
 
788
            self.pp.next_phase()
 
789
        finally:
 
790
            self.other_tree.unlock()
 
791
            self.base_tree.unlock()
 
792
            self.pb.clear()
832
793
        return self.tt
833
794
 
834
795
    def _compute_transform(self):
856
817
        finally:
857
818
            child_pb.finished()
858
819
        self.fix_root()
 
820
        self.pp.next_phase()
859
821
        child_pb = ui.ui_factory.nested_progress_bar()
860
822
        try:
861
823
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1059
1021
                        continue
1060
1022
                else:
1061
1023
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1024
                # XXX: We need to handle kind == 'symlink'
1062
1025
 
1063
1026
            # If we have gotten this far, that means something has changed
1064
1027
            result.append((file_id, content_changed,
1086
1049
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1087
1050
        if other_root == self.tt.root:
1088
1051
            return
1089
 
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
1090
 
            # the other tree's root is a non-root in the current tree (as when
1091
 
            # a previously unrelated branch is merged into another)
1092
 
            return
1093
1052
        try:
1094
1053
            self.tt.final_kind(other_root)
1095
 
            other_root_is_present = True
1096
1054
        except errors.NoSuchFile:
1097
 
            # other_root doesn't have a physical representation. We still need
1098
 
            # to move any references to the actual root of the tree.
1099
 
            other_root_is_present = False
1100
 
        # 'other_tree.inventory.root' is not present in this tree. We are
1101
 
        # calling adjust_path for children which *want* to be present with a
1102
 
        # correct place to go.
1103
 
        for thing, child in self.other_tree.inventory.root.children.iteritems():
 
1055
            return
 
1056
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
 
1057
            # the other tree's root is a non-root in the current tree
 
1058
            return
 
1059
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
1060
        self.tt.cancel_creation(other_root)
 
1061
        self.tt.cancel_versioning(other_root)
 
1062
 
 
1063
    def reparent_children(self, ie, target):
 
1064
        for thing, child in ie.children.iteritems():
1104
1065
            trans_id = self.tt.trans_id_file_id(child.file_id)
1105
 
            if not other_root_is_present:
1106
 
                # FIXME: Make final_kind returns None instead of raising
1107
 
                # NoSuchFile to avoid the ugly construct below -- vila 20100402
1108
 
                try:
1109
 
                    self.tt.final_kind(trans_id)
1110
 
                    # The item exist in the final tree and has a defined place
1111
 
                    # to go already.
1112
 
                    continue
1113
 
                except errors.NoSuchFile, e:
1114
 
                    pass
1115
 
            # Move the item into the root
1116
 
            self.tt.adjust_path(self.tt.final_name(trans_id),
1117
 
                                self.tt.root, trans_id)
1118
 
        if other_root_is_present:
1119
 
            self.tt.cancel_creation(other_root)
1120
 
            self.tt.cancel_versioning(other_root)
 
1066
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1121
1067
 
1122
1068
    def write_modified(self, results):
1123
1069
        modified_hashes = {}
1170
1116
 
1171
1117
    @staticmethod
1172
1118
    def _three_way(base, other, this):
 
1119
        #if base == other, either they all agree, or only THIS has changed.
1173
1120
        if base == other:
1174
 
            # if 'base == other', either they all agree, or only 'this' has
1175
 
            # changed.
1176
1121
            return 'this'
1177
1122
        elif this not in (base, other):
1178
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1179
1123
            return 'conflict'
 
1124
        # "Ambiguous clean merge" -- both sides have made the same change.
1180
1125
        elif this == other:
1181
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1182
1126
            return "this"
 
1127
        # this == base: only other has changed.
1183
1128
        else:
1184
 
            # this == base: only other has changed.
1185
1129
            return "other"
1186
1130
 
1187
1131
    @staticmethod
1231
1175
                # only has an lca value
1232
1176
                return 'other'
1233
1177
 
1234
 
        # At this point, the lcas disagree, and the tip disagree
 
1178
        # At this point, the lcas disagree, and the tips disagree
1235
1179
        return 'conflict'
1236
1180
 
1237
1181
    @staticmethod
1238
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1239
1182
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1240
1183
        """Do a three-way test on a scalar.
1241
1184
        Return "this", "other" or "conflict", depending whether a value wins.
1291
1234
                parent_id_winner = "other"
1292
1235
        if name_winner == "this" and parent_id_winner == "this":
1293
1236
            return
1294
 
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
1295
 
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1296
 
            # road if a ContentConflict needs to be created so we should not do
1297
 
            # that
1298
 
            trans_id = self.tt.trans_id_file_id(file_id)
1299
 
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1300
 
                                        this_parent, this_name,
1301
 
                                        other_parent, other_name))
 
1237
        if name_winner == "conflict":
 
1238
            trans_id = self.tt.trans_id_file_id(file_id)
 
1239
            self._raw_conflicts.append(('name conflict', trans_id,
 
1240
                                        this_name, other_name))
 
1241
        if parent_id_winner == "conflict":
 
1242
            trans_id = self.tt.trans_id_file_id(file_id)
 
1243
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1244
                                        this_parent, other_parent))
1302
1245
        if other_name is None:
1303
1246
            # it doesn't matter whether the result was 'other' or
1304
1247
            # 'conflict'-- if there's no 'other', we leave it alone.
1305
1248
            return
 
1249
        # if we get here, name_winner and parent_winner are set to safe values.
 
1250
        trans_id = self.tt.trans_id_file_id(file_id)
1306
1251
        parent_id = parents[self.winner_idx[parent_id_winner]]
1307
1252
        if parent_id is not None:
1308
 
            # if we get here, name_winner and parent_winner are set to safe
1309
 
            # values.
 
1253
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
1310
1254
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1311
 
                                self.tt.trans_id_file_id(parent_id),
1312
 
                                self.tt.trans_id_file_id(file_id))
 
1255
                                parent_trans_id, trans_id)
1313
1256
 
1314
1257
    def _do_merge_contents(self, file_id):
1315
1258
        """Performs a merge on file_id contents."""
1594
1537
 
1595
1538
    def cook_conflicts(self, fs_conflicts):
1596
1539
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1540
        name_conflicts = {}
1597
1541
        self.cooked_conflicts.extend(transform.cook_conflicts(
1598
1542
                fs_conflicts, self.tt))
1599
1543
        fp = transform.FinalPaths(self.tt)
1600
1544
        for conflict in self._raw_conflicts:
1601
1545
            conflict_type = conflict[0]
1602
 
            if conflict_type == 'path conflict':
1603
 
                (trans_id, file_id,
1604
 
                this_parent, this_name,
1605
 
                other_parent, other_name) = conflict[1:]
1606
 
                if this_parent is None or this_name is None:
1607
 
                    this_path = '<deleted>'
1608
 
                else:
1609
 
                    parent_path =  fp.get_path(
1610
 
                        self.tt.trans_id_file_id(this_parent))
1611
 
                    this_path = osutils.pathjoin(parent_path, this_name)
1612
 
                if other_parent is None or other_name is None:
1613
 
                    other_path = '<deleted>'
1614
 
                else:
1615
 
                    parent_path =  fp.get_path(
1616
 
                        self.tt.trans_id_file_id(other_parent))
1617
 
                    other_path = osutils.pathjoin(parent_path, other_name)
1618
 
                c = _mod_conflicts.Conflict.factory(
1619
 
                    'path conflict', path=this_path,
1620
 
                    conflict_path=other_path,
1621
 
                    file_id=file_id)
1622
 
            elif conflict_type == 'contents conflict':
 
1546
            if conflict_type in ('name conflict', 'parent conflict'):
 
1547
                trans_id = conflict[1]
 
1548
                conflict_args = conflict[2:]
 
1549
                if trans_id not in name_conflicts:
 
1550
                    name_conflicts[trans_id] = {}
 
1551
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1552
                                     conflict_args)
 
1553
            if conflict_type == 'contents conflict':
1623
1554
                for trans_id in conflict[1]:
1624
1555
                    file_id = self.tt.final_file_id(trans_id)
1625
1556
                    if file_id is not None:
1631
1562
                        break
1632
1563
                c = _mod_conflicts.Conflict.factory(conflict_type,
1633
1564
                                                    path=path, file_id=file_id)
1634
 
            elif conflict_type == 'text conflict':
 
1565
                self.cooked_conflicts.append(c)
 
1566
            if conflict_type == 'text conflict':
1635
1567
                trans_id = conflict[1]
1636
1568
                path = fp.get_path(trans_id)
1637
1569
                file_id = self.tt.final_file_id(trans_id)
1638
1570
                c = _mod_conflicts.Conflict.factory(conflict_type,
1639
1571
                                                    path=path, file_id=file_id)
 
1572
                self.cooked_conflicts.append(c)
 
1573
 
 
1574
        for trans_id, conflicts in name_conflicts.iteritems():
 
1575
            try:
 
1576
                this_parent, other_parent = conflicts['parent conflict']
 
1577
                if this_parent == other_parent:
 
1578
                    raise AssertionError()
 
1579
            except KeyError:
 
1580
                this_parent = other_parent = \
 
1581
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1582
            try:
 
1583
                this_name, other_name = conflicts['name conflict']
 
1584
                if this_name == other_name:
 
1585
                    raise AssertionError()
 
1586
            except KeyError:
 
1587
                this_name = other_name = self.tt.final_name(trans_id)
 
1588
            other_path = fp.get_path(trans_id)
 
1589
            if this_parent is not None and this_name is not None:
 
1590
                this_parent_path = \
 
1591
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1592
                this_path = osutils.pathjoin(this_parent_path, this_name)
1640
1593
            else:
1641
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1594
                this_path = "<deleted>"
 
1595
            file_id = self.tt.final_file_id(trans_id)
 
1596
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1597
                                                conflict_path=other_path,
 
1598
                                                file_id=file_id)
1642
1599
            self.cooked_conflicts.append(c)
1643
1600
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1644
1601
 
1759
1716
                other_rev_id=None,
1760
1717
                interesting_files=None,
1761
1718
                this_tree=None,
1762
 
                pb=None,
 
1719
                pb=progress.DummyProgress(),
1763
1720
                change_reporter=None):
1764
1721
    """Primary interface for merging.
1765
1722