/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

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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
22
21
 
23
22
from bzrlib.lazy_import import lazy_import
24
23
lazy_import(globals(), """
26
25
 
27
26
from bzrlib import (
28
27
    builtins,
 
28
    cleanup,
29
29
    commands,
30
30
    errors,
31
31
    osutils,
45
45
 
46
46
 
47
47
class cmd_conflicts(commands.Command):
48
 
    """List files with conflicts.
 
48
    __doc__ = """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
 
    """Mark a conflict as resolved.
 
101
    __doc__ = """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
 
415
419
    def cleanup(self, tree):
416
 
        raise NotImplementedError(self.cleanup)
 
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
417
426
 
418
427
    def action_done(self, tree):
419
428
        """Mark the conflict as solved once it has been handled."""
426
435
    def action_take_other(self, tree):
427
436
        raise NotImplementedError(self.action_take_other)
428
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)
 
443
 
429
444
 
430
445
class PathConflict(Conflict):
431
446
    """A conflict was encountered merging file paths"""
446
461
            s.add('conflict_path', self.conflict_path)
447
462
        return s
448
463
 
449
 
    def cleanup(self, tree):
 
464
    def associated_filenames(self):
450
465
        # No additional files have been generated here
451
 
        pass
 
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
452
531
 
453
532
    def action_take_this(self, tree):
454
 
        tree.rename_one(self.conflict_path, self.path)
 
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)
455
542
 
456
543
    def action_take_other(self, tree):
457
 
        # just acccept bzr proposal
458
 
        pass
 
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)
459
554
 
460
555
 
461
556
class ContentsConflict(PathConflict):
462
 
    """The files are of different types, or not present"""
 
557
    """The files are of different types (or both binary), or not present"""
463
558
 
464
559
    has_files = True
465
560
 
467
562
 
468
563
    format = 'Contents conflict in %(path)s'
469
564
 
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
 
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
 
482
594
    def action_take_this(self, tree):
483
 
        tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
 
595
        self._resolve_with_cleanups(tree, 'OTHER')
484
596
 
485
597
    def action_take_other(self, tree):
486
 
        tree.remove([self.path], force=True, keep_files=False)
487
 
 
 
598
        self._resolve_with_cleanups(tree, 'THIS')
488
599
 
489
600
 
490
601
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
501
612
 
502
613
    format = 'Text conflict in %(path)s'
503
614
 
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
 
615
    def associated_filenames(self):
 
616
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
511
617
 
512
618
 
513
619
class HandledConflict(Conflict):
529
635
        s.add('action', self.action)
530
636
        return s
531
637
 
532
 
    def cleanup(self, tree):
533
 
        """Nothing to cleanup."""
534
 
        pass
 
638
    def associated_filenames(self):
 
639
        # Nothing has been generated here
 
640
        return []
535
641
 
536
642
 
537
643
class HandledPathConflict(HandledConflict):
600
706
 
601
707
    typestring = 'parent loop'
602
708
 
603
 
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
 
709
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
604
710
 
605
711
    def action_take_this(self, tree):
606
712
        # just acccept bzr proposal