/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: Martin Pool
  • Date: 2010-02-23 02:32:52 UTC
  • mto: This revision was merged to the branch mainline in revision 5054.
  • Revision ID: mbp@sourcefrog.net-20100223023252-zk4ds0sbeuga82et
Clarify Launchpad setup example

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import warnings
18
18
 
19
 
from bzrlib.lazy_import import lazy_import
20
 
lazy_import(globals(), """
21
19
from bzrlib import (
22
20
    branch as _mod_branch,
23
21
    conflicts as _mod_conflicts,
24
22
    debug,
 
23
    decorators,
25
24
    errors,
26
25
    graph as _mod_graph,
 
26
    hooks,
27
27
    merge3,
28
28
    osutils,
29
29
    patiencediff,
34
34
    tree as _mod_tree,
35
35
    tsort,
36
36
    ui,
37
 
    versionedfile,
 
37
    versionedfile
38
38
    )
39
39
from bzrlib.cleanup import OperationWithCleanups
40
 
""")
41
 
from bzrlib import (
42
 
    decorators,
43
 
    hooks,
44
 
    )
45
40
from bzrlib.symbol_versioning import (
46
41
    deprecated_in,
47
42
    deprecated_method,
98
93
        return ('not applicable', None)
99
94
 
100
95
 
101
 
class PerFileMerger(AbstractPerFileMerger):
102
 
    """Merge individual files when self.file_matches returns True.
103
 
 
104
 
    This class is intended to be subclassed.  The file_matches and
105
 
    merge_matching methods should be overridden with concrete implementations.
106
 
    """
107
 
 
108
 
    def file_matches(self, params):
109
 
        """Return True if merge_matching should be called on this file.
110
 
 
111
 
        Only called with merges of plain files with no clear winner.
112
 
 
113
 
        Subclasses must override this.
114
 
        """
115
 
        raise NotImplementedError(self.file_matches)
116
 
 
117
 
    def get_filename(self, params, tree):
118
 
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
119
 
        self.merger.this_tree) and a MergeHookParams.
120
 
        """
121
 
        return osutils.basename(tree.id2path(params.file_id))
122
 
 
123
 
    def get_filepath(self, params, tree):
124
 
        """Calculate the path to the file in a tree.
125
 
 
126
 
        :param params: A MergeHookParams describing the file to merge
127
 
        :param tree: a Tree, e.g. self.merger.this_tree.
128
 
        """
129
 
        return tree.id2path(params.file_id)
130
 
 
131
 
    def merge_contents(self, params):
132
 
        """Merge the contents of a single file."""
133
 
        # Check whether this custom merge logic should be used.
134
 
        if (
135
 
            # OTHER is a straight winner, rely on default merge.
136
 
            params.winner == 'other' or
137
 
            # THIS and OTHER aren't both files.
138
 
            not params.is_file_merge() or
139
 
            # The filename doesn't match *.xml
140
 
            not self.file_matches(params)):
141
 
            return 'not_applicable', None
142
 
        return self.merge_matching(params)
143
 
 
144
 
    def merge_matching(self, params):
145
 
        """Merge the contents of a single file that has matched the criteria
146
 
        in PerFileMerger.merge_contents (is a conflict, is a file,
147
 
        self.file_matches is True).
148
 
 
149
 
        Subclasses must override this.
150
 
        """
151
 
        raise NotImplementedError(self.merge_matching)
152
 
 
153
 
 
154
 
class ConfigurableFileMerger(PerFileMerger):
 
96
class ConfigurableFileMerger(AbstractPerFileMerger):
155
97
    """Merge individual files when configured via a .conf file.
156
98
 
157
99
    This is a base class for concrete custom file merging logic. Concrete
180
122
        if self.name_prefix is None:
181
123
            raise ValueError("name_prefix must be set.")
182
124
 
183
 
    def file_matches(self, params):
 
125
    def filename_matches_config(self, params):
184
126
        """Check whether the file should call the merge hook.
185
127
 
186
128
        <name_prefix>_merge_files configuration variable is a list of files
188
130
        """
189
131
        affected_files = self.affected_files
190
132
        if affected_files is None:
191
 
            config = self.merger.this_branch.get_config()
 
133
            config = self.merger.this_tree.branch.get_config()
192
134
            # Until bzr provides a better policy for caching the config, we
193
135
            # just add the part we're interested in to the params to avoid
194
136
            # reading the config files repeatedly (bazaar.conf, location.conf,
200
142
                affected_files = self.default_files
201
143
            self.affected_files = affected_files
202
144
        if affected_files:
203
 
            filepath = self.get_filepath(params, self.merger.this_tree)
204
 
            if filepath in affected_files:
 
145
            filename = self.merger.this_tree.id2path(params.file_id)
 
146
            if filename in affected_files:
205
147
                return True
206
148
        return False
207
149
 
208
 
    def merge_matching(self, params):
 
150
    def merge_contents(self, params):
 
151
        """Merge the contents of a single file."""
 
152
        # First, check whether this custom merge logic should be used.  We
 
153
        # expect most files should not be merged by this handler.
 
154
        if (
 
155
            # OTHER is a straight winner, rely on default merge.
 
156
            params.winner == 'other' or
 
157
            # THIS and OTHER aren't both files.
 
158
            not params.is_file_merge() or
 
159
            # The filename isn't listed in the 'NAME_merge_files' config
 
160
            # option.
 
161
            not self.filename_matches_config(params)):
 
162
            return 'not_applicable', None
209
163
        return self.merge_text(params)
210
164
 
211
165
    def merge_text(self, params):
750
704
        :param this_tree: The local tree in the merge operation
751
705
        :param base_tree: The common tree in the merge operation
752
706
        :param other_tree: The other tree to merge changes from
753
 
        :param this_branch: The branch associated with this_tree.  Defaults to
754
 
            this_tree.branch if not supplied.
 
707
        :param this_branch: The branch associated with this_tree
755
708
        :param interesting_ids: The file_ids of files that should be
756
709
            participate in the merge.  May not be combined with
757
710
            interesting_files.
775
728
        if interesting_files is not None and interesting_ids is not None:
776
729
            raise ValueError(
777
730
                'specify either interesting_ids or interesting_files')
778
 
        if this_branch is None:
779
 
            this_branch = this_tree.branch
780
731
        self.interesting_ids = interesting_ids
781
732
        self.interesting_files = interesting_files
782
733
        self.this_tree = working_tree
1064
1015
                        continue
1065
1016
                else:
1066
1017
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1018
                # XXX: We need to handle kind == 'symlink'
1067
1019
 
1068
1020
            # If we have gotten this far, that means something has changed
1069
1021
            result.append((file_id, content_changed,
1091
1043
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1092
1044
        if other_root == self.tt.root:
1093
1045
            return
1094
 
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
1095
 
            # the other tree's root is a non-root in the current tree (as when
1096
 
            # a previously unrelated branch is merged into another)
1097
 
            return
1098
1046
        try:
1099
1047
            self.tt.final_kind(other_root)
1100
 
            other_root_is_present = True
1101
1048
        except errors.NoSuchFile:
1102
 
            # other_root doesn't have a physical representation. We still need
1103
 
            # to move any references to the actual root of the tree.
1104
 
            other_root_is_present = False
1105
 
        # 'other_tree.inventory.root' is not present in this tree. We are
1106
 
        # calling adjust_path for children which *want* to be present with a
1107
 
        # correct place to go.
1108
 
        for thing, child in self.other_tree.inventory.root.children.iteritems():
 
1049
            return
 
1050
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
 
1051
            # the other tree's root is a non-root in the current tree
 
1052
            return
 
1053
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
1054
        self.tt.cancel_creation(other_root)
 
1055
        self.tt.cancel_versioning(other_root)
 
1056
 
 
1057
    def reparent_children(self, ie, target):
 
1058
        for thing, child in ie.children.iteritems():
1109
1059
            trans_id = self.tt.trans_id_file_id(child.file_id)
1110
 
            if not other_root_is_present:
1111
 
                # FIXME: Make final_kind returns None instead of raising
1112
 
                # NoSuchFile to avoid the ugly construct below -- vila 20100402
1113
 
                try:
1114
 
                    self.tt.final_kind(trans_id)
1115
 
                    # The item exist in the final tree and has a defined place
1116
 
                    # to go already.
1117
 
                    continue
1118
 
                except errors.NoSuchFile, e:
1119
 
                    pass
1120
 
            # Move the item into the root
1121
 
            self.tt.adjust_path(self.tt.final_name(trans_id),
1122
 
                                self.tt.root, trans_id)
1123
 
        if other_root_is_present:
1124
 
            self.tt.cancel_creation(other_root)
1125
 
            self.tt.cancel_versioning(other_root)
 
1060
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1126
1061
 
1127
1062
    def write_modified(self, results):
1128
1063
        modified_hashes = {}
1175
1110
 
1176
1111
    @staticmethod
1177
1112
    def _three_way(base, other, this):
 
1113
        #if base == other, either they all agree, or only THIS has changed.
1178
1114
        if base == other:
1179
 
            # if 'base == other', either they all agree, or only 'this' has
1180
 
            # changed.
1181
1115
            return 'this'
1182
1116
        elif this not in (base, other):
1183
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1184
1117
            return 'conflict'
 
1118
        # "Ambiguous clean merge" -- both sides have made the same change.
1185
1119
        elif this == other:
1186
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1187
1120
            return "this"
 
1121
        # this == base: only other has changed.
1188
1122
        else:
1189
 
            # this == base: only other has changed.
1190
1123
            return "other"
1191
1124
 
1192
1125
    @staticmethod
1236
1169
                # only has an lca value
1237
1170
                return 'other'
1238
1171
 
1239
 
        # At this point, the lcas disagree, and the tip disagree
 
1172
        # At this point, the lcas disagree, and the tips disagree
1240
1173
        return 'conflict'
1241
1174
 
1242
1175
    @staticmethod
1243
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1244
1176
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1245
1177
        """Do a three-way test on a scalar.
1246
1178
        Return "this", "other" or "conflict", depending whether a value wins.
1296
1228
                parent_id_winner = "other"
1297
1229
        if name_winner == "this" and parent_id_winner == "this":
1298
1230
            return
1299
 
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
1300
 
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1301
 
            # road if a ContentConflict needs to be created so we should not do
1302
 
            # that
1303
 
            trans_id = self.tt.trans_id_file_id(file_id)
1304
 
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1305
 
                                        this_parent, this_name,
1306
 
                                        other_parent, other_name))
 
1231
        if name_winner == "conflict":
 
1232
            trans_id = self.tt.trans_id_file_id(file_id)
 
1233
            self._raw_conflicts.append(('name conflict', trans_id,
 
1234
                                        this_name, other_name))
 
1235
        if parent_id_winner == "conflict":
 
1236
            trans_id = self.tt.trans_id_file_id(file_id)
 
1237
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1238
                                        this_parent, other_parent))
1307
1239
        if other_name is None:
1308
1240
            # it doesn't matter whether the result was 'other' or
1309
1241
            # 'conflict'-- if there's no 'other', we leave it alone.
1310
1242
            return
 
1243
        # if we get here, name_winner and parent_winner are set to safe values.
 
1244
        trans_id = self.tt.trans_id_file_id(file_id)
1311
1245
        parent_id = parents[self.winner_idx[parent_id_winner]]
1312
1246
        if parent_id is not None:
1313
 
            # if we get here, name_winner and parent_winner are set to safe
1314
 
            # values.
 
1247
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
1315
1248
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1316
 
                                self.tt.trans_id_file_id(parent_id),
1317
 
                                self.tt.trans_id_file_id(file_id))
 
1249
                                parent_trans_id, trans_id)
1318
1250
 
1319
1251
    def _do_merge_contents(self, file_id):
1320
1252
        """Performs a merge on file_id contents."""
1460
1392
    def get_lines(self, tree, file_id):
1461
1393
        """Return the lines in a file, or an empty list."""
1462
1394
        if tree.has_id(file_id):
1463
 
            return tree.get_file_lines(file_id)
 
1395
            return tree.get_file(file_id).readlines()
1464
1396
        else:
1465
1397
            return []
1466
1398
 
1599
1531
 
1600
1532
    def cook_conflicts(self, fs_conflicts):
1601
1533
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1534
        name_conflicts = {}
1602
1535
        self.cooked_conflicts.extend(transform.cook_conflicts(
1603
1536
                fs_conflicts, self.tt))
1604
1537
        fp = transform.FinalPaths(self.tt)
1605
1538
        for conflict in self._raw_conflicts:
1606
1539
            conflict_type = conflict[0]
1607
 
            if conflict_type == 'path conflict':
1608
 
                (trans_id, file_id,
1609
 
                this_parent, this_name,
1610
 
                other_parent, other_name) = conflict[1:]
1611
 
                if this_parent is None or this_name is None:
1612
 
                    this_path = '<deleted>'
1613
 
                else:
1614
 
                    parent_path =  fp.get_path(
1615
 
                        self.tt.trans_id_file_id(this_parent))
1616
 
                    this_path = osutils.pathjoin(parent_path, this_name)
1617
 
                if other_parent is None or other_name is None:
1618
 
                    other_path = '<deleted>'
1619
 
                else:
1620
 
                    parent_path =  fp.get_path(
1621
 
                        self.tt.trans_id_file_id(other_parent))
1622
 
                    other_path = osutils.pathjoin(parent_path, other_name)
1623
 
                c = _mod_conflicts.Conflict.factory(
1624
 
                    'path conflict', path=this_path,
1625
 
                    conflict_path=other_path,
1626
 
                    file_id=file_id)
1627
 
            elif conflict_type == 'contents conflict':
 
1540
            if conflict_type in ('name conflict', 'parent conflict'):
 
1541
                trans_id = conflict[1]
 
1542
                conflict_args = conflict[2:]
 
1543
                if trans_id not in name_conflicts:
 
1544
                    name_conflicts[trans_id] = {}
 
1545
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1546
                                     conflict_args)
 
1547
            if conflict_type == 'contents conflict':
1628
1548
                for trans_id in conflict[1]:
1629
1549
                    file_id = self.tt.final_file_id(trans_id)
1630
1550
                    if file_id is not None:
1636
1556
                        break
1637
1557
                c = _mod_conflicts.Conflict.factory(conflict_type,
1638
1558
                                                    path=path, file_id=file_id)
1639
 
            elif conflict_type == 'text conflict':
 
1559
                self.cooked_conflicts.append(c)
 
1560
            if conflict_type == 'text conflict':
1640
1561
                trans_id = conflict[1]
1641
1562
                path = fp.get_path(trans_id)
1642
1563
                file_id = self.tt.final_file_id(trans_id)
1643
1564
                c = _mod_conflicts.Conflict.factory(conflict_type,
1644
1565
                                                    path=path, file_id=file_id)
 
1566
                self.cooked_conflicts.append(c)
 
1567
 
 
1568
        for trans_id, conflicts in name_conflicts.iteritems():
 
1569
            try:
 
1570
                this_parent, other_parent = conflicts['parent conflict']
 
1571
                if this_parent == other_parent:
 
1572
                    raise AssertionError()
 
1573
            except KeyError:
 
1574
                this_parent = other_parent = \
 
1575
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1576
            try:
 
1577
                this_name, other_name = conflicts['name conflict']
 
1578
                if this_name == other_name:
 
1579
                    raise AssertionError()
 
1580
            except KeyError:
 
1581
                this_name = other_name = self.tt.final_name(trans_id)
 
1582
            other_path = fp.get_path(trans_id)
 
1583
            if this_parent is not None and this_name is not None:
 
1584
                this_parent_path = \
 
1585
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1586
                this_path = osutils.pathjoin(this_parent_path, this_name)
1645
1587
            else:
1646
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1588
                this_path = "<deleted>"
 
1589
            file_id = self.tt.final_file_id(trans_id)
 
1590
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1591
                                                conflict_path=other_path,
 
1592
                                                file_id=file_id)
1647
1593
            self.cooked_conflicts.append(c)
1648
1594
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1649
1595