~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

Refactor so that the default text merge is the final hook tried.  Other hooks can now invoke it directly via merger.default_text_merge if they wish.

Show diffs side-by-side

added added

removed removed

Lines of Context:
83
83
        params.merger.get_lines(params.merger.this_tree, params.file_id)
84
84
    """
85
85
 
86
 
    def __init__(self, merger, file_id, trans_id):
 
86
    def __init__(self, merger, file_id, trans_id, this_pair, other_pair):
87
87
        self.merger = merger
88
88
        self.file_id = file_id
89
89
        self.trans_id = trans_id
 
90
        self.this_pair = this_pair
 
91
        self.other_pair = other_pair
90
92
        
 
93
    def is_file_merge(self):
 
94
        return self.this_pair[0] == 'file' and self.other_pair[0] == 'file'
 
95
    
91
96
    @decorators.cachedproperty
92
97
    def base_lines(self):
93
98
        """The lines of the 'base' version of the file."""
1229
1234
                # OTHER deleted the file
1230
1235
                self.tt.unversion_file(trans_id)
1231
1236
                return "deleted"
1232
 
        else:
1233
 
            hooks = Merger.hooks['merge_file_content']
1234
 
            params = MergeHookParams(self, file_id, trans_id)
1235
 
            hook_ran = False
1236
 
            for hook in hooks:
1237
 
                hook_status, lines = hook(params)
1238
 
                if hook_status == 'not_applicable':
1239
 
                    continue
1240
 
                elif hook_status == 'success':
1241
 
                    self.tt.create_file(lines, trans_id)
1242
 
                elif hook_status == 'conflicted':
1243
 
                    # XXX: perhaps the hook should be able to provide
1244
 
                    # the BASE/THIS/OTHER files?
1245
 
                    self.tt.create_file(lines, trans_id)
1246
 
                    self._raw_conflicts.append(('text conflict', trans_id))
1247
 
                    name = self.tt.final_name(trans_id)
1248
 
                    parent_id = self.tt.final_parent(trans_id)
1249
 
                    file_group = self._dump_conflicts(name, parent_id, file_id)
1250
 
                    file_group.append(trans_id)
1251
 
                elif hook_status == 'delete':
1252
 
                    self.tt.unversion_file(trans_id)
1253
 
                    if file_id in self.this_tree:
1254
 
                        self.tt.delete_contents(trans_id)
1255
 
                    return "deleted"
1256
 
                else:
1257
 
                    raise AssertionError(
1258
 
                        'unknown hook_status: %r' % (hook_status,))
1259
 
                # This hook function applied, so don't try any other
1260
 
                # hook functions.
1261
 
                hook_ran = True
 
1237
        # We have a hypothetical conflict, but if we have files, then we
 
1238
        # can try to merge the content
 
1239
        hooks = Merger.hooks['merge_file_content']
 
1240
        hooks = list(hooks) + [self.default_text_merge]
 
1241
        params = MergeHookParams(self, file_id, trans_id, this_pair,
 
1242
            other_pair)
 
1243
        hook_status = 'not_applicable'
 
1244
        for hook in hooks:
 
1245
            hook_status, lines = hook(params)
 
1246
            if hook_status != 'not_applicable':
 
1247
                # Don't try any more hooks, this one applies.
1262
1248
                break
1263
 
            # We have a hypothetical conflict, but if we have files, then we
1264
 
            # can try to merge the content
1265
 
            if (this_pair[0] == 'file' and other_pair[0] == 'file') or hook_ran:
1266
 
                # THIS and OTHER are both files, so text merge.  Either
1267
 
                # BASE is a file, or both converted to files, so at least we
1268
 
                # have agreement that output should be a file.
1269
 
                try:
1270
 
                    if not hook_ran:
1271
 
                        # if no hook functions applied, do the default merge.
1272
 
                        self.text_merge(file_id, trans_id)
1273
 
                except errors.BinaryFile:
1274
 
                    return contents_conflict()
1275
 
                if file_id not in self.this_tree:
1276
 
                    self.tt.version_file(file_id, trans_id)
1277
 
                try:
1278
 
                    self.tt.tree_kind(trans_id)
1279
 
                    self.tt.delete_contents(trans_id)
1280
 
                except errors.NoSuchFile:
1281
 
                    pass
1282
 
                return "modified"
1283
 
            else:
1284
 
                return contents_conflict()
 
1249
        if hook_status == 'not_applicable':
 
1250
            # No function to merge the file's contents was found, so this must
 
1251
            # be a contents conflict.
 
1252
            contents_conflict()
 
1253
            return
 
1254
        elif hook_status == 'success':
 
1255
            self.tt.create_file(lines, trans_id)
 
1256
        elif hook_status == 'conflicted':
 
1257
            # XXX: perhaps the hook should be able to provide
 
1258
            # the BASE/THIS/OTHER files?
 
1259
            self.tt.create_file(lines, trans_id)
 
1260
            self._raw_conflicts.append(('text conflict', trans_id))
 
1261
            name = self.tt.final_name(trans_id)
 
1262
            parent_id = self.tt.final_parent(trans_id)
 
1263
            file_group = self._dump_conflicts(name, parent_id, file_id)
 
1264
            file_group.append(trans_id)
 
1265
        elif hook_status == 'delete':
 
1266
            self.tt.unversion_file(trans_id)
 
1267
            if file_id in self.this_tree:
 
1268
                self.tt.delete_contents(trans_id)
 
1269
            return "deleted"
 
1270
        elif hook_status == 'done':
 
1271
            # The hook function did whatever it needs to do directly, no
 
1272
            # further action needed here.
 
1273
            pass
 
1274
        else:
 
1275
            raise AssertionError(
 
1276
                'unknown hook_status: %r' % (hook_status,))
 
1277
        if file_id not in self.this_tree:
 
1278
            self.tt.version_file(file_id, trans_id)
 
1279
        try:
 
1280
            self.tt.tree_kind(trans_id)
 
1281
            self.tt.delete_contents(trans_id)
 
1282
        except errors.NoSuchFile:
 
1283
            pass
 
1284
        return "modified"
 
1285
 
 
1286
    def default_text_merge(self, merge_hook_params):
 
1287
        if merge_hook_params.is_file_merge():
 
1288
            # THIS and OTHER are both files, so text merge.  Either
 
1289
            # BASE is a file, or both converted to files, so at least we
 
1290
            # have agreement that output should be a file.
 
1291
            try:
 
1292
                self.text_merge(merge_hook_params.file_id,
 
1293
                    merge_hook_params.trans_id)
 
1294
            except errors.BinaryFile:
 
1295
                return 'not_applicable', None
 
1296
        else:
 
1297
            return 'not_applicable', None
 
1298
        return 'done', None
1285
1299
 
1286
1300
    def get_lines(self, tree, file_id):
1287
1301
        """Return the lines in a file, or an empty list."""