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(), """
36
from breezy.i18n import gettext, ngettext
45
from .sixish import text_type
44
48
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
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):