~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-02-11 04:02:41 UTC
  • mfrom: (5017.2.2 tariff)
  • Revision ID: pqm@pqm.ubuntu.com-20100211040241-w6n021dz0uus341n
(mbp) add import-tariff tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
# point down
19
19
 
20
20
import os
 
21
import re
21
22
 
22
23
from bzrlib.lazy_import import lazy_import
23
24
lazy_import(globals(), """
25
26
 
26
27
from bzrlib import (
27
28
    builtins,
28
 
    cleanup,
29
29
    commands,
30
30
    errors,
31
31
    osutils,
45
45
 
46
46
 
47
47
class cmd_conflicts(commands.Command):
48
 
    __doc__ = """List files with conflicts.
 
48
    """List files with conflicts.
49
49
 
50
50
    Merge will do its best to combine the changes in two branches, but there
51
51
    are some kinds of problems only a human can fix.  When it encounters those,
98
98
 
99
99
 
100
100
class cmd_resolve(commands.Command):
101
 
    __doc__ = """Mark a conflict as resolved.
 
101
    """Mark a conflict as resolved.
102
102
 
103
103
    Merge will do its best to combine the changes in two branches, but there
104
104
    are some kinds of problems only a human can fix.  When it encounters those,
412
412
            raise NotImplementedError(self.__class__.__name__ + '.' + action)
413
413
        meth(tree)
414
414
 
415
 
    def associated_filenames(self):
416
 
        """The names of the files generated to help resolve the conflict."""
417
 
        raise NotImplementedError(self.associated_filenames)
418
 
 
419
415
    def cleanup(self, tree):
420
 
        for fname in self.associated_filenames():
421
 
            try:
422
 
                osutils.delete_any(tree.abspath(fname))
423
 
            except OSError, e:
424
 
                if e.errno != errno.ENOENT:
425
 
                    raise
 
416
        raise NotImplementedError(self.cleanup)
426
417
 
427
418
    def action_done(self, tree):
428
419
        """Mark the conflict as solved once it has been handled."""
435
426
    def action_take_other(self, tree):
436
427
        raise NotImplementedError(self.action_take_other)
437
428
 
438
 
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
439
 
        tt = transform.TreeTransform(tree)
440
 
        op = cleanup.OperationWithCleanups(self._resolve)
441
 
        op.add_cleanup(tt.finalize)
442
 
        op.run_simple(tt, *args, **kwargs)
443
 
 
444
429
 
445
430
class PathConflict(Conflict):
446
431
    """A conflict was encountered merging file paths"""
461
446
            s.add('conflict_path', self.conflict_path)
462
447
        return s
463
448
 
464
 
    def associated_filenames(self):
 
449
    def cleanup(self, tree):
465
450
        # No additional files have been generated here
466
 
        return []
467
 
 
468
 
    def _resolve(self, tt, file_id, path, winner):
469
 
        """Resolve the conflict.
470
 
 
471
 
        :param tt: The TreeTransform where the conflict is resolved.
472
 
        :param file_id: The retained file id.
473
 
        :param path: The retained path.
474
 
        :param winner: 'this' or 'other' indicates which side is the winner.
475
 
        """
476
 
        path_to_create = None
477
 
        if winner == 'this':
478
 
            if self.path == '<deleted>':
479
 
                return # Nothing to do
480
 
            if self.conflict_path == '<deleted>':
481
 
                path_to_create = self.path
482
 
                revid = tt._tree.get_parent_ids()[0]
483
 
        elif winner == 'other':
484
 
            if self.conflict_path == '<deleted>':
485
 
                return  # Nothing to do
486
 
            if self.path == '<deleted>':
487
 
                path_to_create = self.conflict_path
488
 
                # FIXME: If there are more than two parents we may need to
489
 
                # iterate. Taking the last parent is the safer bet in the mean
490
 
                # time. -- vila 20100309
491
 
                revid = tt._tree.get_parent_ids()[-1]
492
 
        else:
493
 
            # Programmer error
494
 
            raise AssertionError('bad winner: %r' % (winner,))
495
 
        if path_to_create is not None:
496
 
            tid = tt.trans_id_tree_path(path_to_create)
497
 
            transform.create_from_tree(
498
 
                tt, tt.trans_id_tree_path(path_to_create),
499
 
                self._revision_tree(tt._tree, revid), file_id)
500
 
            tt.version_file(file_id, tid)
501
 
 
502
 
        # Adjust the path for the retained file id
503
 
        tid = tt.trans_id_file_id(file_id)
504
 
        parent_tid = tt.get_tree_parent(tid)
505
 
        tt.adjust_path(path, parent_tid, tid)
506
 
        tt.apply()
507
 
 
508
 
    def _revision_tree(self, tree, revid):
509
 
        return tree.branch.repository.revision_tree(revid)
510
 
 
511
 
    def _infer_file_id(self, tree):
512
 
        # Prior to bug #531967, file_id wasn't always set, there may still be
513
 
        # conflict files in the wild so we need to cope with them
514
 
        # Establish which path we should use to find back the file-id
515
 
        possible_paths = []
516
 
        for p in (self.path, self.conflict_path):
517
 
            if p == '<deleted>':
518
 
                # special hard-coded path 
519
 
                continue
520
 
            if p is not None:
521
 
                possible_paths.append(p)
522
 
        # Search the file-id in the parents with any path available
523
 
        file_id = None
524
 
        for revid in tree.get_parent_ids():
525
 
            revtree = self._revision_tree(tree, revid)
526
 
            for p in possible_paths:
527
 
                file_id = revtree.path2id(p)
528
 
                if file_id is not None:
529
 
                    return revtree, file_id
530
 
        return None, None
 
451
        pass
531
452
 
532
453
    def action_take_this(self, tree):
533
 
        if self.file_id is not None:
534
 
            self._resolve_with_cleanups(tree, self.file_id, self.path,
535
 
                                        winner='this')
536
 
        else:
537
 
            # Prior to bug #531967 we need to find back the file_id and restore
538
 
            # the content from there
539
 
            revtree, file_id = self._infer_file_id(tree)
540
 
            tree.revert([revtree.id2path(file_id)],
541
 
                        old_tree=revtree, backups=False)
 
454
        tree.rename_one(self.conflict_path, self.path)
542
455
 
543
456
    def action_take_other(self, tree):
544
 
        if self.file_id is not None:
545
 
            self._resolve_with_cleanups(tree, self.file_id,
546
 
                                        self.conflict_path,
547
 
                                        winner='other')
548
 
        else:
549
 
            # Prior to bug #531967 we need to find back the file_id and restore
550
 
            # the content from there
551
 
            revtree, file_id = self._infer_file_id(tree)
552
 
            tree.revert([revtree.id2path(file_id)],
553
 
                        old_tree=revtree, backups=False)
 
457
        # just acccept bzr proposal
 
458
        pass
554
459
 
555
460
 
556
461
class ContentsConflict(PathConflict):
557
 
    """The files are of different types (or both binary), or not present"""
 
462
    """The files are of different types, or not present"""
558
463
 
559
464
    has_files = True
560
465
 
562
467
 
563
468
    format = 'Contents conflict in %(path)s'
564
469
 
565
 
    def associated_filenames(self):
566
 
        return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
567
 
 
568
 
    def _resolve(self, tt, suffix_to_remove):
569
 
        """Resolve the conflict.
570
 
 
571
 
        :param tt: The TreeTransform where the conflict is resolved.
572
 
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
573
 
 
574
 
        The resolution is symmetric, when taking THIS, OTHER is deleted and
575
 
        item.THIS is renamed into item and vice-versa.
576
 
        """
577
 
        try:
578
 
            # Delete 'item.THIS' or 'item.OTHER' depending on
579
 
            # suffix_to_remove
580
 
            tt.delete_contents(
581
 
                tt.trans_id_tree_path(self.path + '.' + suffix_to_remove))
582
 
        except errors.NoSuchFile:
583
 
            # There are valid cases where 'item.suffix_to_remove' either
584
 
            # never existed or was already deleted (including the case
585
 
            # where the user deleted it)
586
 
            pass
587
 
        # Rename 'item.suffix_to_remove' (note that if
588
 
        # 'item.suffix_to_remove' has been deleted, this is a no-op)
589
 
        this_tid = tt.trans_id_file_id(self.file_id)
590
 
        parent_tid = tt.get_tree_parent(this_tid)
591
 
        tt.adjust_path(self.path, parent_tid, this_tid)
592
 
        tt.apply()
593
 
 
 
470
    def cleanup(self, tree):
 
471
        for suffix in ('.BASE', '.OTHER'):
 
472
            try:
 
473
                osutils.delete_any(tree.abspath(self.path + suffix))
 
474
            except OSError, e:
 
475
                if e.errno != errno.ENOENT:
 
476
                    raise
 
477
 
 
478
    # FIXME: I smell something weird here and it seems we should be able to be
 
479
    # more coherent with some other conflict ? bzr *did* a choice there but
 
480
    # neither action_take_this nor action_take_other reflect that...
 
481
    # -- vila 20091224
594
482
    def action_take_this(self, tree):
595
 
        self._resolve_with_cleanups(tree, 'OTHER')
 
483
        tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
596
484
 
597
485
    def action_take_other(self, tree):
598
 
        self._resolve_with_cleanups(tree, 'THIS')
 
486
        tree.remove([self.path], force=True, keep_files=False)
 
487
 
599
488
 
600
489
 
601
490
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
612
501
 
613
502
    format = 'Text conflict in %(path)s'
614
503
 
615
 
    def associated_filenames(self):
616
 
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
 
504
    def cleanup(self, tree):
 
505
        for suffix in CONFLICT_SUFFIXES:
 
506
            try:
 
507
                osutils.delete_any(tree.abspath(self.path+suffix))
 
508
            except OSError, e:
 
509
                if e.errno != errno.ENOENT:
 
510
                    raise
617
511
 
618
512
 
619
513
class HandledConflict(Conflict):
635
529
        s.add('action', self.action)
636
530
        return s
637
531
 
638
 
    def associated_filenames(self):
639
 
        # Nothing has been generated here
640
 
        return []
 
532
    def cleanup(self, tree):
 
533
        """Nothing to cleanup."""
 
534
        pass
641
535
 
642
536
 
643
537
class HandledPathConflict(HandledConflict):
706
600
 
707
601
    typestring = 'parent loop'
708
602
 
709
 
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
 
603
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
710
604
 
711
605
    def action_take_this(self, tree):
712
606
        # just acccept bzr proposal