~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-09-29 22:03:03 UTC
  • mfrom: (5416.2.6 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100929220303-cr95h8iwtggco721
(mbp) Add 'break-lock --force'

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(), """
25
24
import errno
26
25
 
27
26
from bzrlib import (
28
 
    builtins,
 
27
    cleanup,
29
28
    commands,
30
29
    errors,
31
30
    osutils,
45
44
 
46
45
 
47
46
class cmd_conflicts(commands.Command):
48
 
    """List files with conflicts.
 
47
    __doc__ = """List files with conflicts.
49
48
 
50
49
    Merge will do its best to combine the changes in two branches, but there
51
50
    are some kinds of problems only a human can fix.  When it encounters those,
59
58
    Use bzr resolve when you have fixed a problem.
60
59
    """
61
60
    takes_options = [
 
61
            'directory',
62
62
            option.Option('text',
63
63
                          help='List paths of files with text conflicts.'),
64
64
        ]
65
65
    _see_also = ['resolve', 'conflict-types']
66
66
 
67
 
    def run(self, text=False):
68
 
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
 
67
    def run(self, text=False, directory=u'.'):
 
68
        wt = workingtree.WorkingTree.open_containing(directory)[0]
69
69
        for conflict in wt.conflicts():
70
70
            if text:
71
71
                if conflict.typestring != 'text conflict':
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,
112
112
    aliases = ['resolved']
113
113
    takes_args = ['file*']
114
114
    takes_options = [
 
115
            'directory',
115
116
            option.Option('all', help='Resolve all conflicts in this tree.'),
116
117
            ResolveActionOption(),
117
118
            ]
118
119
    _see_also = ['conflicts']
119
 
    def run(self, file_list=None, all=False, action=None):
 
120
    def run(self, file_list=None, all=False, action=None, directory=u'.'):
120
121
        if all:
121
122
            if file_list:
122
123
                raise errors.BzrCommandError("If --all is specified,"
123
124
                                             " no FILE may be provided")
124
 
            tree = workingtree.WorkingTree.open_containing('.')[0]
 
125
            tree = workingtree.WorkingTree.open_containing(directory)[0]
125
126
            if action is None:
126
127
                action = 'done'
127
128
        else:
128
 
            tree, file_list = builtins.tree_files(file_list)
 
129
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
 
130
                file_list)
129
131
            if file_list is None:
130
132
                if action is None:
131
133
                    # FIXME: There is a special case here related to the option
435
437
    def action_take_other(self, tree):
436
438
        raise NotImplementedError(self.action_take_other)
437
439
 
 
440
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
 
441
        tt = transform.TreeTransform(tree)
 
442
        op = cleanup.OperationWithCleanups(self._resolve)
 
443
        op.add_cleanup(tt.finalize)
 
444
        op.run_simple(tt, *args, **kwargs)
 
445
 
438
446
 
439
447
class PathConflict(Conflict):
440
448
    """A conflict was encountered merging file paths"""
459
467
        # No additional files have been generated here
460
468
        return []
461
469
 
 
470
    def _resolve(self, tt, file_id, path, winner):
 
471
        """Resolve the conflict.
 
472
 
 
473
        :param tt: The TreeTransform where the conflict is resolved.
 
474
        :param file_id: The retained file id.
 
475
        :param path: The retained path.
 
476
        :param winner: 'this' or 'other' indicates which side is the winner.
 
477
        """
 
478
        path_to_create = None
 
479
        if winner == 'this':
 
480
            if self.path == '<deleted>':
 
481
                return # Nothing to do
 
482
            if self.conflict_path == '<deleted>':
 
483
                path_to_create = self.path
 
484
                revid = tt._tree.get_parent_ids()[0]
 
485
        elif winner == 'other':
 
486
            if self.conflict_path == '<deleted>':
 
487
                return  # Nothing to do
 
488
            if self.path == '<deleted>':
 
489
                path_to_create = self.conflict_path
 
490
                # FIXME: If there are more than two parents we may need to
 
491
                # iterate. Taking the last parent is the safer bet in the mean
 
492
                # time. -- vila 20100309
 
493
                revid = tt._tree.get_parent_ids()[-1]
 
494
        else:
 
495
            # Programmer error
 
496
            raise AssertionError('bad winner: %r' % (winner,))
 
497
        if path_to_create is not None:
 
498
            tid = tt.trans_id_tree_path(path_to_create)
 
499
            transform.create_from_tree(
 
500
                tt, tt.trans_id_tree_path(path_to_create),
 
501
                self._revision_tree(tt._tree, revid), file_id)
 
502
            tt.version_file(file_id, tid)
 
503
 
 
504
        # Adjust the path for the retained file id
 
505
        tid = tt.trans_id_file_id(file_id)
 
506
        parent_tid = tt.get_tree_parent(tid)
 
507
        tt.adjust_path(path, parent_tid, tid)
 
508
        tt.apply()
 
509
 
 
510
    def _revision_tree(self, tree, revid):
 
511
        return tree.branch.repository.revision_tree(revid)
 
512
 
 
513
    def _infer_file_id(self, tree):
 
514
        # Prior to bug #531967, file_id wasn't always set, there may still be
 
515
        # conflict files in the wild so we need to cope with them
 
516
        # Establish which path we should use to find back the file-id
 
517
        possible_paths = []
 
518
        for p in (self.path, self.conflict_path):
 
519
            if p == '<deleted>':
 
520
                # special hard-coded path 
 
521
                continue
 
522
            if p is not None:
 
523
                possible_paths.append(p)
 
524
        # Search the file-id in the parents with any path available
 
525
        file_id = None
 
526
        for revid in tree.get_parent_ids():
 
527
            revtree = self._revision_tree(tree, revid)
 
528
            for p in possible_paths:
 
529
                file_id = revtree.path2id(p)
 
530
                if file_id is not None:
 
531
                    return revtree, file_id
 
532
        return None, None
 
533
 
462
534
    def action_take_this(self, tree):
463
 
        tree.rename_one(self.conflict_path, self.path)
 
535
        if self.file_id is not None:
 
536
            self._resolve_with_cleanups(tree, self.file_id, self.path,
 
537
                                        winner='this')
 
538
        else:
 
539
            # Prior to bug #531967 we need to find back the file_id and restore
 
540
            # the content from there
 
541
            revtree, file_id = self._infer_file_id(tree)
 
542
            tree.revert([revtree.id2path(file_id)],
 
543
                        old_tree=revtree, backups=False)
464
544
 
465
545
    def action_take_other(self, tree):
466
 
        # just acccept bzr proposal
467
 
        pass
 
546
        if self.file_id is not None:
 
547
            self._resolve_with_cleanups(tree, self.file_id,
 
548
                                        self.conflict_path,
 
549
                                        winner='other')
 
550
        else:
 
551
            # Prior to bug #531967 we need to find back the file_id and restore
 
552
            # the content from there
 
553
            revtree, file_id = self._infer_file_id(tree)
 
554
            tree.revert([revtree.id2path(file_id)],
 
555
                        old_tree=revtree, backups=False)
468
556
 
469
557
 
470
558
class ContentsConflict(PathConflict):
471
 
    """The files are of different types, or not present"""
 
559
    """The files are of different types (or both binary), or not present"""
472
560
 
473
561
    has_files = True
474
562
 
479
567
    def associated_filenames(self):
480
568
        return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
481
569
 
482
 
    # FIXME: I smell something weird here and it seems we should be able to be
483
 
    # more coherent with some other conflict ? bzr *did* a choice there but
484
 
    # neither action_take_this nor action_take_other reflect that...
485
 
    # -- vila 20091224
 
570
    def _resolve(self, tt, suffix_to_remove):
 
571
        """Resolve the conflict.
 
572
 
 
573
        :param tt: The TreeTransform where the conflict is resolved.
 
574
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
 
575
 
 
576
        The resolution is symmetric, when taking THIS, OTHER is deleted and
 
577
        item.THIS is renamed into item and vice-versa.
 
578
        """
 
579
        try:
 
580
            # Delete 'item.THIS' or 'item.OTHER' depending on
 
581
            # suffix_to_remove
 
582
            tt.delete_contents(
 
583
                tt.trans_id_tree_path(self.path + '.' + suffix_to_remove))
 
584
        except errors.NoSuchFile:
 
585
            # There are valid cases where 'item.suffix_to_remove' either
 
586
            # never existed or was already deleted (including the case
 
587
            # where the user deleted it)
 
588
            pass
 
589
        # Rename 'item.suffix_to_remove' (note that if
 
590
        # 'item.suffix_to_remove' has been deleted, this is a no-op)
 
591
        this_tid = tt.trans_id_file_id(self.file_id)
 
592
        parent_tid = tt.get_tree_parent(this_tid)
 
593
        tt.adjust_path(self.path, parent_tid, this_tid)
 
594
        tt.apply()
 
595
 
486
596
    def action_take_this(self, tree):
487
 
        tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
 
597
        self._resolve_with_cleanups(tree, 'OTHER')
488
598
 
489
599
    def action_take_other(self, tree):
490
 
        tree.remove([self.path], force=True, keep_files=False)
491
 
 
 
600
        self._resolve_with_cleanups(tree, 'THIS')
492
601
 
493
602
 
494
603
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
599
708
 
600
709
    typestring = 'parent loop'
601
710
 
602
 
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
 
711
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
603
712
 
604
713
    def action_take_this(self, tree):
605
714
        # just acccept bzr proposal