14
14
# along with this program; if not, write to the Free Software
 
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
 
# TODO: 'bzr resolve' should accept a directory name and work from that
 
 
17
# TODO: 'brz resolve' should accept a directory name and work from that
 
 
20
from __future__ import absolute_import
 
22
 
from bzrlib.lazy_import import lazy_import
 
 
25
from .lazy_import import lazy_import
 
23
26
lazy_import(globals(), """
 
 
50
54
    Merge will do its best to combine the changes in two branches, but there
 
51
55
    are some kinds of problems only a human can fix.  When it encounters those,
 
52
56
    it will mark a conflict.  A conflict means that you need to fix something,
 
53
 
    before you should commit.
 
 
57
    before you can commit.
 
55
59
    Conflicts normally are listed as short, human-readable messages.  If --text
 
56
60
    is supplied, the pathnames of files with text conflicts are listed,
 
57
61
    instead.  (This is useful for editing all files with text conflicts.)
 
59
 
    Use bzr resolve when you have fixed a problem.
 
 
63
    Use brz resolve when you have fixed a problem.
 
63
 
                          help='List paths of files with text conflicts.'),
 
 
68
                      help='List paths of files with text conflicts.'),
 
65
70
    _see_also = ['resolve', 'conflict-types']
 
67
 
    def run(self, text=False):
 
68
 
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
 
 
72
    def run(self, text=False, directory=u'.'):
 
 
73
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
69
74
        for conflict in wt.conflicts():
 
71
76
                if conflict.typestring != 'text conflict':
 
73
78
                self.outf.write(conflict.path + '\n')
 
75
 
                self.outf.write(str(conflict) + '\n')
 
 
80
                self.outf.write(text_type(conflict) + '\n')
 
78
83
resolve_action_registry = registry.Registry()
 
81
86
resolve_action_registry.register(
 
82
 
    'done', 'done', 'Marks the conflict as resolved' )
 
 
87
    'auto', 'auto', 'Detect whether conflict has been resolved by user.')
 
 
88
resolve_action_registry.register(
 
 
89
    'done', 'done', 'Marks the conflict as resolved.')
 
83
90
resolve_action_registry.register(
 
84
91
    'take-this', 'take_this',
 
85
 
    'Resolve the conflict preserving the version in the working tree' )
 
 
92
    'Resolve the conflict preserving the version in the working tree.')
 
86
93
resolve_action_registry.register(
 
87
94
    'take-other', 'take_other',
 
88
 
    'Resolve the conflict taking the merged version into account' )
 
 
95
    'Resolve the conflict taking the merged version into account.')
 
89
96
resolve_action_registry.default_key = 'done'
 
91
99
class ResolveActionOption(option.RegistryOption):
 
93
101
    def __init__(self):
 
 
103
111
    Merge will do its best to combine the changes in two branches, but there
 
104
112
    are some kinds of problems only a human can fix.  When it encounters those,
 
105
113
    it will mark a conflict.  A conflict means that you need to fix something,
 
106
 
    before you should commit.
 
 
114
    before you can commit.
 
108
 
    Once you have fixed a problem, use "bzr resolve" to automatically mark
 
109
 
    text conflicts as fixed, "bzr resolve FILE" to mark a specific conflict as
 
110
 
    resolved, or "bzr resolve --all" to mark all conflicts as resolved.
 
 
116
    Once you have fixed a problem, use "brz resolve" to automatically mark
 
 
117
    text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
 
 
118
    resolved, or "brz resolve --all" to mark all conflicts as resolved.
 
112
120
    aliases = ['resolved']
 
113
121
    takes_args = ['file*']
 
114
122
    takes_options = [
 
115
 
            option.Option('all', help='Resolve all conflicts in this tree.'),
 
116
 
            ResolveActionOption(),
 
 
124
        option.Option('all', help='Resolve all conflicts in this tree.'),
 
 
125
        ResolveActionOption(),
 
118
127
    _see_also = ['conflicts']
 
119
 
    def run(self, file_list=None, all=False, action=None):
 
 
129
    def run(self, file_list=None, all=False, action=None, directory=None):
 
122
 
                raise errors.BzrCommandError("If --all is specified,"
 
123
 
                                             " no FILE may be provided")
 
124
 
            tree = workingtree.WorkingTree.open_containing('.')[0]
 
 
132
                raise errors.BzrCommandError(gettext("If --all is specified,"
 
 
133
                                                     " no FILE may be provided"))
 
 
134
            if directory is None:
 
 
136
            tree = workingtree.WorkingTree.open_containing(directory)[0]
 
125
137
            if action is None:
 
128
 
            tree, file_list = builtins.tree_files(file_list)
 
129
 
            if file_list is None:
 
131
 
                    # FIXME: There is a special case here related to the option
 
132
 
                    # handling that could be clearer and easier to discover by
 
133
 
                    # providing an --auto action (bug #344013 and #383396) and
 
134
 
                    # make it mandatory instead of implicit and active only
 
135
 
                    # when no file_list is provided -- vila 091229
 
 
140
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
 
 
141
                file_list, directory)
 
 
143
                if file_list is None:
 
141
 
            if file_list is None:
 
142
 
                un_resolved, resolved = tree.auto_resolve()
 
143
 
                if len(un_resolved) > 0:
 
144
 
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
 
145
 
                    trace.note('Remaining conflicts:')
 
146
 
                    for conflict in un_resolved:
 
150
 
                    trace.note('All conflicts resolved.')
 
 
147
        before, after = resolve(tree, file_list, action=action)
 
 
148
        # GZ 2012-07-27: Should unify UI below now that auto is less magical.
 
 
149
        if action == 'auto' and file_list is None:
 
 
152
                    ngettext('%d conflict auto-resolved.',
 
 
153
                             '%d conflicts auto-resolved.', before - after),
 
 
155
                trace.note(gettext('Remaining conflicts:'))
 
 
156
                for conflict in tree.conflicts():
 
 
157
                    trace.note(text_type(conflict))
 
153
 
                # FIXME: This can never occur but the block above needs some
 
154
 
                # refactoring to transfer tree.auto_resolve() to
 
155
 
                # conflict.auto(tree) --vila 091242
 
 
160
                trace.note(gettext('All conflicts resolved.'))
 
158
 
            resolve(tree, file_list, action=action)
 
 
163
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
 
 
164
                                '{0} conflicts resolved, {1} remaining',
 
 
165
                                before - after).format(before - after, after))
 
161
168
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
 
 
351
361
    def __init__(self, path, file_id=None):
 
353
 
        # warn turned off, because the factory blindly transfers the Stanza
 
354
 
        # values to __init__ and Stanza is purely a Unicode api.
 
355
 
        self.file_id = osutils.safe_file_id(file_id, warn=False)
 
 
363
        # the factory blindly transfers the Stanza values to __init__ and
 
 
364
        # Stanza is purely a Unicode api.
 
 
365
        if isinstance(file_id, text_type):
 
 
366
            file_id = cache_utf8.encode(file_id)
 
 
367
        self.file_id = osutils.safe_file_id(file_id)
 
357
369
    def as_stanza(self):
 
358
370
        s = rio.Stanza(type=self.typestring, path=self.path)
 
 
436
459
        raise NotImplementedError(self.action_take_other)
 
438
461
    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)
 
 
462
        with tree.get_transform() as tt:
 
 
463
            self._resolve(tt, *args, **kwargs)
 
445
466
class PathConflict(Conflict):
 
 
494
515
            raise AssertionError('bad winner: %r' % (winner,))
 
495
516
        if path_to_create is not None:
 
496
517
            tid = tt.trans_id_tree_path(path_to_create)
 
 
518
            tree = self._revision_tree(tt._tree, revid)
 
497
519
            transform.create_from_tree(
 
498
 
                tt, tt.trans_id_tree_path(path_to_create),
 
499
 
                self._revision_tree(tt._tree, revid), file_id)
 
 
520
                tt, tid, tree, tree.id2path(file_id))
 
500
521
            tt.version_file(file_id, tid)
 
 
523
            tid = tt.trans_id_file_id(file_id)
 
502
524
        # Adjust the path for the retained file id
 
503
 
        tid = tt.trans_id_file_id(file_id)
 
504
525
        parent_tid = tt.get_tree_parent(tid)
 
505
 
        tt.adjust_path(path, parent_tid, tid)
 
 
526
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
 
508
529
    def _revision_tree(self, tree, revid):
 
 
584
605
            # never existed or was already deleted (including the case
 
585
606
            # where the user deleted it)
 
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)
 
 
609
            this_path = tt._tree.id2path(self.file_id)
 
 
610
        except errors.NoSuchId:
 
 
611
            # The file is not present anymore. This may happen if the user
 
 
612
            # deleted the file either manually or when resolving a conflict on
 
 
613
            # the parent.  We may raise some exception to indicate that the
 
 
614
            # conflict doesn't exist anymore and as such doesn't need to be
 
 
615
            # resolved ? -- vila 20110615
 
 
618
            this_tid = tt.trans_id_tree_path(this_path)
 
 
619
        if this_tid is not None:
 
 
620
            # Rename 'item.suffix_to_remove' (note that if
 
 
621
            # 'item.suffix_to_remove' has been deleted, this is a no-op)
 
 
622
            parent_tid = tt.get_tree_parent(this_tid)
 
 
623
            tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
 
594
626
    def action_take_this(self, tree):
 
595
627
        self._resolve_with_cleanups(tree, 'OTHER')
 
 
613
642
    format = 'Text conflict in %(path)s'
 
 
644
    rformat = '%(class)s(%(path)r, %(file_id)r)'
 
 
646
    _conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
 
615
648
    def associated_filenames(self):
 
616
649
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
 
 
651
    def _resolve(self, tt, winner_suffix):
 
 
652
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
 
 
654
        :param tt: The TreeTransform where the conflict is resolved.
 
 
655
        :param winner_suffix: Either 'THIS' or 'OTHER'
 
 
657
        The resolution is symmetric, when taking THIS, item.THIS is renamed
 
 
658
        into item and vice-versa. This takes one of the files as a whole
 
 
659
        ignoring every difference that could have been merged cleanly.
 
 
661
        # To avoid useless copies, we switch item and item.winner_suffix, only
 
 
662
        # item will exist after the conflict has been resolved anyway.
 
 
663
        item_tid = tt.trans_id_file_id(self.file_id)
 
 
664
        item_parent_tid = tt.get_tree_parent(item_tid)
 
 
665
        winner_path = self.path + '.' + winner_suffix
 
 
666
        winner_tid = tt.trans_id_tree_path(winner_path)
 
 
667
        winner_parent_tid = tt.get_tree_parent(winner_tid)
 
 
668
        # Switch the paths to preserve the content
 
 
669
        tt.adjust_path(osutils.basename(self.path),
 
 
670
                       winner_parent_tid, winner_tid)
 
 
671
        tt.adjust_path(osutils.basename(winner_path),
 
 
672
                       item_parent_tid, item_tid)
 
 
673
        # Associate the file_id to the right content
 
 
674
        tt.unversion_file(item_tid)
 
 
675
        tt.version_file(self.file_id, winner_tid)
 
 
678
    def action_auto(self, tree):
 
 
679
        # GZ 2012-07-27: Using NotImplementedError to signal that a conflict
 
 
680
        #                can't be auto resolved does not seem ideal.
 
 
682
            kind = tree.kind(self.path)
 
 
683
        except errors.NoSuchFile:
 
 
686
            raise NotImplementedError("Conflict is not a file")
 
 
687
        conflict_markers_in_line = self._conflict_re.search
 
 
688
        # GZ 2012-07-27: What if not tree.has_id(self.file_id) due to removal?
 
 
689
        with tree.get_file(self.path) as f:
 
 
691
                if conflict_markers_in_line(line):
 
 
692
                    raise NotImplementedError("Conflict markers present")
 
 
694
    def action_take_this(self, tree):
 
 
695
        self._resolve_with_cleanups(tree, 'THIS')
 
 
697
    def action_take_other(self, tree):
 
 
698
        self._resolve_with_cleanups(tree, 'OTHER')
 
619
701
class HandledConflict(Conflict):
 
620
702
    """A path problem that has been provisionally resolved.
 
 
652
734
                 conflict_file_id=None):
 
653
735
        HandledConflict.__init__(self, action, path, file_id)
 
654
736
        self.conflict_path = conflict_path
 
655
 
        # warn turned off, because the factory blindly transfers the Stanza
 
656
 
        # values to __init__.
 
657
 
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
 
 
737
        # the factory blindly transfers the Stanza values to __init__,
 
 
738
        # so they can be unicode.
 
 
739
        if isinstance(conflict_file_id, text_type):
 
 
740
            conflict_file_id = cache_utf8.encode(conflict_file_id)
 
 
741
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
 
660
743
    def _cmp_list(self):
 
661
744
        return HandledConflict._cmp_list(self) + [self.conflict_path,
 
 
709
792
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
 
711
794
    def action_take_this(self, tree):
 
712
 
        # just acccept bzr proposal
 
 
795
        # just acccept brz proposal
 
715
798
    def action_take_other(self, tree):
 
716
 
        # FIXME: We shouldn't have to manipulate so many paths here (and there
 
717
 
        # is probably a bug or two...)
 
718
 
        base_path = osutils.basename(self.path)
 
719
 
        conflict_base_path = osutils.basename(self.conflict_path)
 
720
 
        tt = transform.TreeTransform(tree)
 
 
799
        with tree.get_transform() as tt:
 
722
800
            p_tid = tt.trans_id_file_id(self.file_id)
 
723
801
            parent_tid = tt.get_tree_parent(p_tid)
 
724
802
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
 
725
803
            cparent_tid = tt.get_tree_parent(cp_tid)
 
726
 
            tt.adjust_path(base_path, cparent_tid, cp_tid)
 
727
 
            tt.adjust_path(conflict_base_path, parent_tid, p_tid)
 
 
804
            tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
 
 
805
            tt.adjust_path(osutils.basename(self.conflict_path),
 
733
810
class UnversionedParent(HandledConflict):