~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Jelmer Vernooij
  • Date: 2010-03-21 21:39:33 UTC
  • mfrom: (5102 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5143.
  • Revision ID: jelmer@samba.org-20100321213933-fexeh9zcoz8oaju2
merge bzr.dev.

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(), """
24
25
import errno
25
26
 
26
27
from bzrlib import (
 
28
    builtins,
27
29
    cleanup,
28
30
    commands,
29
31
    errors,
44
46
 
45
47
 
46
48
class cmd_conflicts(commands.Command):
47
 
    __doc__ = """List files with conflicts.
 
49
    """List files with conflicts.
48
50
 
49
51
    Merge will do its best to combine the changes in two branches, but there
50
52
    are some kinds of problems only a human can fix.  When it encounters those,
58
60
    Use bzr resolve when you have fixed a problem.
59
61
    """
60
62
    takes_options = [
61
 
            'directory',
62
63
            option.Option('text',
63
64
                          help='List paths of files with text conflicts.'),
64
65
        ]
65
66
    _see_also = ['resolve', 'conflict-types']
66
67
 
67
 
    def run(self, text=False, directory=u'.'):
68
 
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
68
    def run(self, text=False):
 
69
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
69
70
        for conflict in wt.conflicts():
70
71
            if text:
71
72
                if conflict.typestring != 'text conflict':
98
99
 
99
100
 
100
101
class cmd_resolve(commands.Command):
101
 
    __doc__ = """Mark a conflict as resolved.
 
102
    """Mark a conflict as resolved.
102
103
 
103
104
    Merge will do its best to combine the changes in two branches, but there
104
105
    are some kinds of problems only a human can fix.  When it encounters those,
112
113
    aliases = ['resolved']
113
114
    takes_args = ['file*']
114
115
    takes_options = [
115
 
            'directory',
116
116
            option.Option('all', help='Resolve all conflicts in this tree.'),
117
117
            ResolveActionOption(),
118
118
            ]
119
119
    _see_also = ['conflicts']
120
 
    def run(self, file_list=None, all=False, action=None, directory=u'.'):
 
120
    def run(self, file_list=None, all=False, action=None):
121
121
        if all:
122
122
            if file_list:
123
123
                raise errors.BzrCommandError("If --all is specified,"
124
124
                                             " no FILE may be provided")
125
 
            tree = workingtree.WorkingTree.open_containing(directory)[0]
 
125
            tree = workingtree.WorkingTree.open_containing('.')[0]
126
126
            if action is None:
127
127
                action = 'done'
128
128
        else:
129
 
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
130
 
                file_list)
 
129
            tree, file_list = builtins.tree_files(file_list)
131
130
            if file_list is None:
132
131
                if action is None:
133
132
                    # FIXME: There is a special case here related to the option
437
436
    def action_take_other(self, tree):
438
437
        raise NotImplementedError(self.action_take_other)
439
438
 
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
 
 
446
439
 
447
440
class PathConflict(Conflict):
448
441
    """A conflict was encountered merging file paths"""
467
460
        # No additional files have been generated here
468
461
        return []
469
462
 
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
 
 
534
463
    def action_take_this(self, tree):
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
        tree.rename_one(self.conflict_path, self.path)
544
465
 
545
466
    def action_take_other(self, tree):
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)
 
467
        # just acccept bzr proposal
 
468
        pass
556
469
 
557
470
 
558
471
class ContentsConflict(PathConflict):
559
 
    """The files are of different types (or both binary), or not present"""
 
472
    """The files are of different types, or not present"""
560
473
 
561
474
    has_files = True
562
475
 
567
480
    def associated_filenames(self):
568
481
        return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
569
482
 
570
 
    def _resolve(self, tt, suffix_to_remove):
 
483
    def _take_it(self, tt, suffix_to_remove):
571
484
        """Resolve the conflict.
572
485
 
573
486
        :param tt: The TreeTransform where the conflict is resolved.
593
506
        tt.adjust_path(self.path, parent_tid, this_tid)
594
507
        tt.apply()
595
508
 
 
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
 
596
515
    def action_take_this(self, tree):
597
 
        self._resolve_with_cleanups(tree, 'OTHER')
 
516
        self._take_it_with_cleanups(tree, 'OTHER')
598
517
 
599
518
    def action_take_other(self, tree):
600
 
        self._resolve_with_cleanups(tree, 'THIS')
 
519
        self._take_it_with_cleanups(tree, 'THIS')
601
520
 
602
521
 
603
522
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
708
627
 
709
628
    typestring = 'parent loop'
710
629
 
711
 
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
 
630
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
712
631
 
713
632
    def action_take_this(self, tree):
714
633
        # just acccept bzr proposal