~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Martin Pool
  • Date: 2008-12-16 07:56:29 UTC
  • mto: (3882.7.11 progress)
  • mto: This revision was merged to the branch mainline in revision 3940.
  • Revision ID: mbp@sourcefrog.net-20081216075629-zhgjzxgoh32453tu
Choose the UIFactory class depending on the terminal capabilities

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
18
import errno
70
70
 
71
71
class Merger(object):
72
72
    def __init__(self, this_branch, other_tree=None, base_tree=None,
73
 
                 this_tree=None, pb=None, change_reporter=None,
 
73
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
74
74
                 recurse='down', revision_graph=None):
75
75
        object.__init__(self)
76
76
        self.this_branch = this_branch
89
89
        self.interesting_files = None
90
90
        self.show_base = False
91
91
        self.reprocess = False
92
 
        if pb is None:
93
 
            pb = DummyProgress()
94
92
        self._pb = pb
95
93
        self.pp = None
96
94
        self.recurse = recurse
134
132
                                      _set_base_is_other_ancestor)
135
133
 
136
134
    @staticmethod
137
 
    def from_uncommitted(tree, other_tree, pb=None, base_tree=None):
 
135
    def from_uncommitted(tree, other_tree, pb, base_tree=None):
138
136
        """Return a Merger for uncommitted changes in other_tree.
139
137
 
140
138
        :param tree: The tree to merge into
392
390
            if self._is_criss_cross:
393
391
                warning('Warning: criss-cross merge encountered.  See bzr'
394
392
                        ' help criss-cross.')
395
 
                mutter('Criss-cross lcas: %r' % lcas)
396
393
                interesting_revision_ids = [self.base_rev_id]
397
394
                interesting_revision_ids.extend(lcas)
398
395
                interesting_trees = dict((t.get_revision_id(), t)
408
405
                self.base_tree = self.revision_tree(self.base_rev_id)
409
406
        self.base_is_ancestor = True
410
407
        self.base_is_other_ancestor = True
411
 
        mutter('Base revid: %r' % self.base_rev_id)
412
408
 
413
409
    def set_base(self, base_revision):
414
410
        """Set the base revision to use for the merge.
462
458
                               **kwargs)
463
459
 
464
460
    def _do_merge_to(self, merge):
465
 
        if self.other_branch is not None:
466
 
            self.other_branch.update_references(self.this_branch)
467
461
        merge.do_merge()
468
462
        if self.recurse == 'down':
469
463
            for relpath, file_id in self.this_tree.iter_references():
481
475
                    sub_tree.branch.repository.revision_tree(base_revision)
482
476
                sub_merge.base_rev_id = base_revision
483
477
                sub_merge.do_merge()
484
 
 
 
478
        
485
479
    def do_merge(self):
486
480
        self.this_tree.lock_tree_write()
487
481
        try:
538
532
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
539
533
    supports_lca_trees = True
540
534
 
541
 
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
 
535
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
542
536
                 interesting_ids=None, reprocess=False, show_base=False,
543
537
                 pb=DummyProgress(), pp=None, change_reporter=None,
544
538
                 interesting_files=None, do_merge=True,
548
542
        :param working_tree: The working tree to apply the merge to
549
543
        :param this_tree: The local tree in the merge operation
550
544
        :param base_tree: The common tree in the merge operation
551
 
        :param other_tree: The other tree to merge changes from
 
545
        :param other_tree: The other other tree to merge changes from
552
546
        :param interesting_ids: The file_ids of files that should be
553
547
            participate in the merge.  May not be combined with
554
548
            interesting_files.
800
794
            content_changed = True
801
795
            if kind_winner == 'this':
802
796
                # No kind change in OTHER, see if there are *any* changes
803
 
                if other_ie.kind == 'directory':
 
797
                if other_ie.kind == None:
 
798
                    # No content and 'this' wins the kind, so skip this?
 
799
                    # continue
 
800
                    pass
 
801
                elif other_ie.kind == 'directory':
804
802
                    if parent_id_winner == 'this' and name_winner == 'this':
805
803
                        # No change for this directory in OTHER, skip
806
804
                        continue
807
805
                    content_changed = False
808
 
                elif other_ie.kind is None or other_ie.kind == 'file':
 
806
                elif other_ie.kind == 'file':
809
807
                    def get_sha1(ie, tree):
810
808
                        if ie.kind != 'file':
811
809
                            return None
878
876
        except NoSuchFile:
879
877
            self.tt.cancel_deletion(self.tt.root)
880
878
        if self.tt.final_file_id(self.tt.root) is None:
881
 
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
 
879
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
882
880
                                 self.tt.root)
883
881
        other_root_file_id = self.other_tree.get_root_id()
884
882
        if other_root_file_id is None:
927
925
        if entry is None:
928
926
            return None
929
927
        return entry.name
930
 
 
 
928
    
931
929
    @staticmethod
932
930
    def contents_sha1(tree, file_id):
933
931
        """Determine the sha1 of the file contents (used as a key method)."""
981
979
        :return: 'this', 'other', or 'conflict' depending on whether an entry
982
980
            changed or not.
983
981
        """
984
 
        # See doc/developers/lca_tree_merging.txt for details about this
 
982
        # See doc/developers/lca_merge_resolution.txt for details about this
985
983
        # algorithm.
986
984
        if other == this:
987
985
            # Either Ambiguously clean, or nothing was actually changed. We
1073
1071
            return
1074
1072
        if name_winner == "conflict":
1075
1073
            trans_id = self.tt.trans_id_file_id(file_id)
1076
 
            self._raw_conflicts.append(('name conflict', trans_id,
 
1074
            self._raw_conflicts.append(('name conflict', trans_id, 
1077
1075
                                        this_name, other_name))
1078
1076
        if parent_id_winner == "conflict":
1079
1077
            trans_id = self.tt.trans_id_file_id(file_id)
1080
 
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1078
            self._raw_conflicts.append(('parent conflict', trans_id, 
1081
1079
                                        this_parent, other_parent))
1082
1080
        if other_name is None:
1083
 
            # it doesn't matter whether the result was 'other' or
 
1081
            # it doesn't matter whether the result was 'other' or 
1084
1082
            # 'conflict'-- if there's no 'other', we leave it alone.
1085
1083
            return
1086
1084
        # if we get here, name_winner and parent_winner are set to safe values.
1092
1090
                                parent_trans_id, trans_id)
1093
1091
 
1094
1092
    def merge_contents(self, file_id):
1095
 
        """Performs a merge on file_id contents."""
 
1093
        """Performa a merge on file_id contents."""
1096
1094
        def contents_pair(tree):
1097
1095
            if file_id not in tree:
1098
1096
                return (None, None)
1113
1111
                self.tt.unversion_file(trans_id)
1114
1112
                if file_id in self.this_tree:
1115
1113
                    self.tt.delete_contents(trans_id)
1116
 
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1114
            file_group = self._dump_conflicts(name, parent_id, file_id, 
1117
1115
                                              set_version=True)
1118
1116
            self._raw_conflicts.append(('contents conflict', file_group))
1119
1117
 
1122
1120
        # file kind...
1123
1121
        base_pair = contents_pair(self.base_tree)
1124
1122
        other_pair = contents_pair(self.other_tree)
1125
 
        if self._lca_trees:
1126
 
            this_pair = contents_pair(self.this_tree)
1127
 
            lca_pairs = [contents_pair(tree) for tree in self._lca_trees]
1128
 
            winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
1129
 
                                         this_pair, allow_overriding_lca=False)
1130
 
        else:
1131
 
            if base_pair == other_pair:
1132
 
                winner = 'this'
1133
 
            else:
1134
 
                # We delayed evaluating this_pair as long as we can to avoid
1135
 
                # unnecessary sha1 calculation
1136
 
                this_pair = contents_pair(self.this_tree)
1137
 
                winner = self._three_way(base_pair, other_pair, this_pair)
1138
 
        if winner == 'this':
1139
 
            # No interesting changes introduced by OTHER
1140
 
            return "unmodified"
1141
 
        trans_id = self.tt.trans_id_file_id(file_id)
1142
 
        if winner == 'other':
1143
 
            # OTHER is a straight winner, so replace this contents with other
1144
 
            file_in_this = file_id in self.this_tree
1145
 
            if file_in_this:
1146
 
                # Remove any existing contents
1147
 
                self.tt.delete_contents(trans_id)
1148
 
            if file_id in self.other_tree:
1149
 
                # OTHER changed the file
1150
 
                create_from_tree(self.tt, trans_id,
1151
 
                                 self.other_tree, file_id)
1152
 
                if not file_in_this:
1153
 
                    self.tt.version_file(file_id, trans_id)
1154
 
                return "modified"
1155
 
            elif file_in_this:
1156
 
                # OTHER deleted the file
1157
 
                self.tt.unversion_file(trans_id)
1158
 
                return "deleted"
1159
 
        else:
1160
 
            # We have a hypothetical conflict, but if we have files, then we
1161
 
            # can try to merge the content
1162
 
            if this_pair[0] == 'file' and other_pair[0] == 'file':
 
1123
        if base_pair == other_pair:
 
1124
            # OTHER introduced no changes
 
1125
            return "unmodified"
 
1126
        this_pair = contents_pair(self.this_tree)
 
1127
        if this_pair == other_pair:
 
1128
            # THIS and OTHER introduced the same changes
 
1129
            return "unmodified"
 
1130
        else:
 
1131
            trans_id = self.tt.trans_id_file_id(file_id)
 
1132
            if this_pair == base_pair:
 
1133
                # only OTHER introduced changes
 
1134
                if file_id in self.this_tree:
 
1135
                    # Remove any existing contents
 
1136
                    self.tt.delete_contents(trans_id)
 
1137
                if file_id in self.other_tree:
 
1138
                    # OTHER changed the file
 
1139
                    create_from_tree(self.tt, trans_id,
 
1140
                                     self.other_tree, file_id)
 
1141
                    if file_id not in self.this_tree:
 
1142
                        self.tt.version_file(file_id, trans_id)
 
1143
                    return "modified"
 
1144
                elif file_id in self.this_tree.inventory:
 
1145
                    # OTHER deleted the file
 
1146
                    self.tt.unversion_file(trans_id)
 
1147
                    return "deleted"
 
1148
            #BOTH THIS and OTHER introduced changes; scalar conflict
 
1149
            elif this_pair[0] == "file" and other_pair[0] == "file":
1163
1150
                # THIS and OTHER are both files, so text merge.  Either
1164
1151
                # BASE is a file, or both converted to files, so at least we
1165
1152
                # have agreement that output should be a file.
1176
1163
                    pass
1177
1164
                return "modified"
1178
1165
            else:
 
1166
                # Scalar conflict, can't text merge.  Dump conflicts
1179
1167
                return contents_conflict()
1180
1168
 
1181
1169
    def get_lines(self, tree, file_id):
1206
1194
 
1207
1195
        def iter_merge3(retval):
1208
1196
            retval["text_conflicts"] = False
1209
 
            for line in m3.merge_lines(name_a = "TREE",
1210
 
                                       name_b = "MERGE-SOURCE",
 
1197
            for line in m3.merge_lines(name_a = "TREE", 
 
1198
                                       name_b = "MERGE-SOURCE", 
1211
1199
                                       name_base = "BASE-REVISION",
1212
 
                                       start_marker=start_marker,
 
1200
                                       start_marker=start_marker, 
1213
1201
                                       base_marker=base_marker,
1214
1202
                                       reprocess=self.reprocess):
1215
1203
                if line.startswith(start_marker):
1224
1212
            self._raw_conflicts.append(('text conflict', trans_id))
1225
1213
            name = self.tt.final_name(trans_id)
1226
1214
            parent_id = self.tt.final_parent(trans_id)
1227
 
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1215
            file_group = self._dump_conflicts(name, parent_id, file_id, 
1228
1216
                                              this_lines, base_lines,
1229
1217
                                              other_lines)
1230
1218
            file_group.append(trans_id)
1231
1219
 
1232
 
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
 
1220
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None, 
1233
1221
                        base_lines=None, other_lines=None, set_version=False,
1234
1222
                        no_base=False):
1235
1223
        """Emit conflict files.
1237
1225
        determined automatically.  If set_version is true, the .OTHER, .THIS
1238
1226
        or .BASE (in that order) will be created as versioned files.
1239
1227
        """
1240
 
        data = [('OTHER', self.other_tree, other_lines),
 
1228
        data = [('OTHER', self.other_tree, other_lines), 
1241
1229
                ('THIS', self.this_tree, this_lines)]
1242
1230
        if not no_base:
1243
1231
            data.append(('BASE', self.base_tree, base_lines))
1252
1240
                    self.tt.version_file(file_id, trans_id)
1253
1241
                    versioned = True
1254
1242
        return file_group
1255
 
 
 
1243
           
1256
1244
    def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1257
1245
                       lines=None):
1258
1246
        """Emit a single conflict file."""
1316
1304
                conflict_args = conflict[2:]
1317
1305
                if trans_id not in name_conflicts:
1318
1306
                    name_conflicts[trans_id] = {}
1319
 
                unique_add(name_conflicts[trans_id], conflict_type,
 
1307
                unique_add(name_conflicts[trans_id], conflict_type, 
1320
1308
                           conflict_args)
1321
1309
            if conflict_type == 'contents conflict':
1322
1310
                for trans_id in conflict[1]:
1400
1388
        """
1401
1389
        lines, conflicts = self._merged_lines(file_id)
1402
1390
        lines = list(lines)
1403
 
        # Note we're checking whether the OUTPUT is binary in this case,
 
1391
        # Note we're checking whether the OUTPUT is binary in this case, 
1404
1392
        # because we don't want to get into weave merge guts.
1405
1393
        check_text_lines(lines)
1406
1394
        self.tt.create_file(lines, trans_id)
1408
1396
            self._raw_conflicts.append(('text conflict', trans_id))
1409
1397
            name = self.tt.final_name(trans_id)
1410
1398
            parent_id = self.tt.final_parent(trans_id)
1411
 
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1399
            file_group = self._dump_conflicts(name, parent_id, file_id, 
1412
1400
                                              no_base=True)
1413
1401
            file_group.append(trans_id)
1414
1402
 
1491
1479
                this_tree=None,
1492
1480
                pb=DummyProgress(),
1493
1481
                change_reporter=None):
1494
 
    """Primary interface for merging.
 
1482
    """Primary interface for merging. 
1495
1483
 
1496
 
        typical use is probably
 
1484
        typical use is probably 
1497
1485
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1498
1486
                     branch.get_revision_tree(base_revision))'
1499
1487
        """
1591
1579
 
1592
1580
    def get_lines(self, revisions):
1593
1581
        """Get lines for revisions from the backing VersionedFiles.
1594
 
 
 
1582
        
1595
1583
        :raises RevisionNotPresent: on absent texts.
1596
1584
        """
1597
1585
        keys = [(self._key_prefix + (rev,)) for rev in revisions]
1599
1587
        for record in self.vf.get_record_stream(keys, 'unordered', True):
1600
1588
            if record.storage_kind == 'absent':
1601
1589
                raise errors.RevisionNotPresent(record.key, self.vf)
1602
 
            result[record.key[-1]] = osutils.chunks_to_lines(
1603
 
                record.get_bytes_as('chunked'))
 
1590
            result[record.key[-1]] = osutils.split_lines(
 
1591
                record.get_bytes_as('fulltext'))
1604
1592
        return result
1605
1593
 
1606
1594
    def plan_merge(self):
1796
1784
 
1797
1785
    def _find_unique_parents(self, tip_keys, base_key):
1798
1786
        """Find ancestors of tip that aren't ancestors of base.
1799
 
 
 
1787
        
1800
1788
        :param tip_keys: Nodes that are interesting
1801
1789
        :param base_key: Cull all ancestors of this node
1802
1790
        :return: The parent map for all revisions between tip_keys and
1862
1850
    @staticmethod
1863
1851
    def _prune_tails(parent_map, child_map, tails_to_remove):
1864
1852
        """Remove tails from the parent map.
1865
 
 
 
1853
        
1866
1854
        This will remove the supplied revisions until no more children have 0
1867
1855
        parents.
1868
1856