/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/conflicts.py

Merge cleanup into description

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2007, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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,
81
81
resolve_action_registry.register(
82
82
    'done', 'done', 'Marks the conflict as resolved' )
83
83
resolve_action_registry.register(
84
 
    'take-this', 'take_this',
 
84
    'keep-mine', 'keep_mine',
85
85
    'Resolve the conflict preserving the version in the working tree' )
86
86
resolve_action_registry.register(
87
 
    'take-other', 'take_other',
 
87
    'take-theirs', 'take_theirs',
88
88
    'Resolve the conflict taking the merged version into account' )
89
89
resolve_action_registry.default_key = 'done'
90
90
 
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,
407
407
 
408
408
        :param tree: The tree passed as a parameter to the method.
409
409
        """
410
 
        meth = getattr(self, 'action_%s' % action, None)
 
410
        meth = getattr(self, action, None)
411
411
        if meth is None:
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
 
    def action_done(self, tree):
 
418
    def done(self, tree):
428
419
        """Mark the conflict as solved once it has been handled."""
429
420
        # This method does nothing but simplifies the design of upper levels.
430
421
        pass
431
422
 
432
 
    def action_take_this(self, tree):
433
 
        raise NotImplementedError(self.action_take_this)
434
 
 
435
 
    def action_take_other(self, tree):
436
 
        raise NotImplementedError(self.action_take_other)
437
 
 
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)
 
423
    def keep_mine(self, tree):
 
424
        raise NotImplementedError(self.keep_mine)
 
425
 
 
426
    def take_theirs(self, tree):
 
427
        raise NotImplementedError(self.take_theirs)
443
428
 
444
429
 
445
430
class PathConflict(Conflict):
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
531
 
 
532
 
    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)
542
 
 
543
 
    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)
 
451
        pass
 
452
 
 
453
    def keep_mine(self, tree):
 
454
        tree.rename_one(self.conflict_path, self.path)
 
455
 
 
456
    def take_theirs(self, tree):
 
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
 
 
594
 
    def action_take_this(self, tree):
595
 
        self._resolve_with_cleanups(tree, 'OTHER')
596
 
 
597
 
    def action_take_other(self, tree):
598
 
        self._resolve_with_cleanups(tree, 'THIS')
 
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 keep_mine nor take_theirs reflect that... -- vila 091224
 
481
    def keep_mine(self, tree):
 
482
        tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
 
483
 
 
484
    def take_theirs(self, tree):
 
485
        tree.remove([self.path], force=True, keep_files=False)
 
486
 
599
487
 
600
488
 
601
489
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
612
500
 
613
501
    format = 'Text conflict in %(path)s'
614
502
 
615
 
    def associated_filenames(self):
616
 
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
 
503
    def cleanup(self, tree):
 
504
        for suffix in CONFLICT_SUFFIXES:
 
505
            try:
 
506
                osutils.delete_any(tree.abspath(self.path+suffix))
 
507
            except OSError, e:
 
508
                if e.errno != errno.ENOENT:
 
509
                    raise
617
510
 
618
511
 
619
512
class HandledConflict(Conflict):
635
528
        s.add('action', self.action)
636
529
        return s
637
530
 
638
 
    def associated_filenames(self):
639
 
        # Nothing has been generated here
640
 
        return []
 
531
    def cleanup(self, tree):
 
532
        """Nothing to cleanup."""
 
533
        pass
641
534
 
642
535
 
643
536
class HandledPathConflict(HandledConflict):
685
578
 
686
579
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
687
580
 
688
 
    def action_take_this(self, tree):
 
581
    def keep_mine(self, tree):
689
582
        tree.remove([self.conflict_path], force=True, keep_files=False)
690
583
        tree.rename_one(self.path, self.conflict_path)
691
584
 
692
 
    def action_take_other(self, tree):
 
585
    def take_theirs(self, tree):
693
586
        tree.remove([self.path], force=True, keep_files=False)
694
587
 
695
588
 
706
599
 
707
600
    typestring = 'parent loop'
708
601
 
709
 
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
 
602
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
710
603
 
711
 
    def action_take_this(self, tree):
 
604
    def keep_mine(self, tree):
712
605
        # just acccept bzr proposal
713
606
        pass
714
607
 
715
 
    def action_take_other(self, tree):
 
608
    def take_theirs(self, tree):
716
609
        # FIXME: We shouldn't have to manipulate so many paths here (and there
717
610
        # is probably a bug or two...)
718
611
        base_path = osutils.basename(self.path)
744
637
    # FIXME: We silently do nothing to make tests pass, but most probably the
745
638
    # conflict shouldn't exist (the long story is that the conflict is
746
639
    # generated with another one that can be resolved properly) -- vila 091224
747
 
    def action_take_this(self, tree):
 
640
    def keep_mine(self, tree):
748
641
        pass
749
642
 
750
 
    def action_take_other(self, tree):
 
643
    def take_theirs(self, tree):
751
644
        pass
752
645
 
753
646
 
762
655
 
763
656
    format = 'Conflict adding files to %(path)s.  %(action)s.'
764
657
 
765
 
    def action_take_this(self, tree):
 
658
    def keep_mine(self, tree):
766
659
        tree.remove([self.path], force=True, keep_files=False)
767
660
 
768
 
    def action_take_other(self, tree):
 
661
    def take_theirs(self, tree):
769
662
        # just acccept bzr proposal
770
663
        pass
771
664
 
784
677
    # FIXME: It's a bit strange that the default action is not coherent with
785
678
    # MissingParent from the *user* pov.
786
679
 
787
 
    def action_take_this(self, tree):
 
680
    def keep_mine(self, tree):
788
681
        # just acccept bzr proposal
789
682
        pass
790
683
 
791
 
    def action_take_other(self, tree):
 
684
    def take_theirs(self, tree):
792
685
        tree.remove([self.path], force=True, keep_files=False)
793
686
 
794
687
 
802
695
    format = "Conflict: %(path)s is not a directory, but has files in it."\
803
696
             "  %(action)s."
804
697
 
805
 
    # FIXME: .OTHER should be used instead of .new when the conflict is created
806
 
 
807
 
    def action_take_this(self, tree):
 
698
    def keep_mine(self, tree):
808
699
        # FIXME: we should preserve that path when the conflict is generated !
809
700
        if self.path.endswith('.new'):
810
701
            conflict_path = self.path[:-(len('.new'))]
811
702
            tree.remove([self.path], force=True, keep_files=False)
812
703
            tree.add(conflict_path)
813
704
        else:
814
 
            raise NotImplementedError(self.action_take_this)
 
705
            raise NotImplementedError(self.keep_mine)
815
706
 
816
 
    def action_take_other(self, tree):
 
707
    def take_theirs(self, tree):
817
708
        # FIXME: we should preserve that path when the conflict is generated !
818
709
        if self.path.endswith('.new'):
819
710
            conflict_path = self.path[:-(len('.new'))]
820
711
            tree.remove([conflict_path], force=True, keep_files=False)
821
712
            tree.rename_one(self.path, conflict_path)
822
713
        else:
823
 
            raise NotImplementedError(self.action_take_other)
 
714
            raise NotImplementedError(self.take_theirs)
824
715
 
825
716
 
826
717
ctype = {}