~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-25 09:38:23 UTC
  • mfrom: (5112.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100325093823-kwkh6crnkc0xfxh6
(vila) Better PathConflict resolution when a deletion is involved

Show diffs side-by-side

added added

removed removed

Lines of Context:
436
436
    def action_take_other(self, tree):
437
437
        raise NotImplementedError(self.action_take_other)
438
438
 
 
439
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
 
440
        tt = transform.TreeTransform(tree)
 
441
        op = cleanup.OperationWithCleanups(self._resolve)
 
442
        op.add_cleanup(tt.finalize)
 
443
        op.run_simple(tt, *args, **kwargs)
 
444
 
439
445
 
440
446
class PathConflict(Conflict):
441
447
    """A conflict was encountered merging file paths"""
460
466
        # No additional files have been generated here
461
467
        return []
462
468
 
 
469
    def _resolve(self, tt, file_id, path, winner):
 
470
        """Resolve the conflict.
 
471
 
 
472
        :param tt: The TreeTransform where the conflict is resolved.
 
473
        :param file_id: The retained file id.
 
474
        :param path: The retained path.
 
475
        :param winner: 'this' or 'other' indicates which side is the winner.
 
476
        """
 
477
        path_to_create = None
 
478
        if winner == 'this':
 
479
            if self.path == '<deleted>':
 
480
                return # Nothing to do
 
481
            if self.conflict_path == '<deleted>':
 
482
                path_to_create = self.path
 
483
                revid = tt._tree.get_parent_ids()[0]
 
484
        elif winner == 'other':
 
485
            if self.conflict_path == '<deleted>':
 
486
                return  # Nothing to do
 
487
            if self.path == '<deleted>':
 
488
                path_to_create = self.conflict_path
 
489
                # FIXME: If there are more than two parents we may need to
 
490
                # iterate. Taking the last parent is the safer bet in the mean
 
491
                # time. -- vila 20100309
 
492
                revid = tt._tree.get_parent_ids()[-1]
 
493
        else:
 
494
            # Programmer error
 
495
            raise AssertionError('bad winner: %r' % (winner,))
 
496
        if path_to_create is not None:
 
497
            tid = tt.trans_id_tree_path(path_to_create)
 
498
            transform.create_from_tree(
 
499
                tt, tt.trans_id_tree_path(path_to_create),
 
500
                self._revision_tree(tt._tree, revid), file_id)
 
501
            tt.version_file(file_id, tid)
 
502
 
 
503
        # Adjust the path for the retained file id
 
504
        tid = tt.trans_id_file_id(file_id)
 
505
        parent_tid = tt.get_tree_parent(tid)
 
506
        tt.adjust_path(path, parent_tid, tid)
 
507
        tt.apply()
 
508
 
 
509
    def _revision_tree(self, tree, revid):
 
510
        return tree.branch.repository.revision_tree(revid)
 
511
 
 
512
    def _infer_file_id(self, tree):
 
513
        # Prior to bug #531967, file_id wasn't always set, there may still be
 
514
        # conflict files in the wild so we need to cope with them
 
515
        # Establish which path we should use to find back the file-id
 
516
        possible_paths = []
 
517
        for p in (self.path, self.conflict_path):
 
518
            if p == '<deleted>':
 
519
                # special hard-coded path 
 
520
                continue
 
521
            if p is not None:
 
522
                possible_paths.append(p)
 
523
        # Search the file-id in the parents with any path available
 
524
        file_id = None
 
525
        for revid in tree.get_parent_ids():
 
526
            revtree = self._revision_tree(tree, revid)
 
527
            for p in possible_paths:
 
528
                file_id = revtree.path2id(p)
 
529
                if file_id is not None:
 
530
                    return revtree, file_id
 
531
        return None, None
 
532
 
463
533
    def action_take_this(self, tree):
464
 
        tree.rename_one(self.conflict_path, self.path)
 
534
        if self.file_id is not None:
 
535
            self._resolve_with_cleanups(tree, self.file_id, self.path,
 
536
                                        winner='this')
 
537
        else:
 
538
            # Prior to bug #531967 we need to find back the file_id and restore
 
539
            # the content from there
 
540
            revtree, file_id = self._infer_file_id(tree)
 
541
            tree.revert([revtree.id2path(file_id)],
 
542
                        old_tree=revtree, backups=False)
465
543
 
466
544
    def action_take_other(self, tree):
467
 
        # just acccept bzr proposal
468
 
        pass
 
545
        if self.file_id is not None:
 
546
            self._resolve_with_cleanups(tree, self.file_id,
 
547
                                        self.conflict_path,
 
548
                                        winner='other')
 
549
        else:
 
550
            # Prior to bug #531967 we need to find back the file_id and restore
 
551
            # the content from there
 
552
            revtree, file_id = self._infer_file_id(tree)
 
553
            tree.revert([revtree.id2path(file_id)],
 
554
                        old_tree=revtree, backups=False)
469
555
 
470
556
 
471
557
class ContentsConflict(PathConflict):
480
566
    def associated_filenames(self):
481
567
        return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
482
568
 
483
 
    def _take_it(self, tt, suffix_to_remove):
 
569
    def _resolve(self, tt, suffix_to_remove):
484
570
        """Resolve the conflict.
485
571
 
486
572
        :param tt: The TreeTransform where the conflict is resolved.
506
592
        tt.adjust_path(self.path, parent_tid, this_tid)
507
593
        tt.apply()
508
594
 
509
 
    def _take_it_with_cleanups(self, tree, suffix_to_remove):
510
 
        tt = transform.TreeTransform(tree)
511
 
        op = cleanup.OperationWithCleanups(self._take_it)
512
 
        op.add_cleanup(tt.finalize)
513
 
        op.run_simple(tt, suffix_to_remove)
514
 
 
515
595
    def action_take_this(self, tree):
516
 
        self._take_it_with_cleanups(tree, 'OTHER')
 
596
        self._resolve_with_cleanups(tree, 'OTHER')
517
597
 
518
598
    def action_take_other(self, tree):
519
 
        self._take_it_with_cleanups(tree, 'THIS')
 
599
        self._resolve_with_cleanups(tree, 'THIS')
520
600
 
521
601
 
522
602
# FIXME: TextConflict is about a single file-id, there never is a conflict_path