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: 'brz resolve' should accept a directory name and work from that
17
# TODO: 'bzr resolve' should accept a directory name and work from that
20
from __future__ import absolute_import
25
from .lazy_import import lazy_import
22
from bzrlib.lazy_import import lazy_import
26
23
lazy_import(globals(), """
36
from breezy.i18n import gettext, ngettext
45
from .sixish import text_type
48
44
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
54
50
Merge will do its best to combine the changes in two branches, but there
55
51
are some kinds of problems only a human can fix. When it encounters those,
56
52
it will mark a conflict. A conflict means that you need to fix something,
57
before you can commit.
53
before you should commit.
59
55
Conflicts normally are listed as short, human-readable messages. If --text
60
56
is supplied, the pathnames of files with text conflicts are listed,
61
57
instead. (This is useful for editing all files with text conflicts.)
63
Use brz resolve when you have fixed a problem.
59
Use bzr resolve when you have fixed a problem.
68
help='List paths of files with text conflicts.'),
63
help='List paths of files with text conflicts.'),
70
65
_see_also = ['resolve', 'conflict-types']
72
def run(self, text=False, directory=u'.'):
73
wt = workingtree.WorkingTree.open_containing(directory)[0]
67
def run(self, text=False):
68
wt = workingtree.WorkingTree.open_containing(u'.')[0]
74
69
for conflict in wt.conflicts():
76
71
if conflict.typestring != 'text conflict':
78
73
self.outf.write(conflict.path + '\n')
80
self.outf.write(text_type(conflict) + '\n')
75
self.outf.write(str(conflict) + '\n')
83
78
resolve_action_registry = registry.Registry()
86
81
resolve_action_registry.register(
87
'auto', 'auto', 'Detect whether conflict has been resolved by user.')
88
resolve_action_registry.register(
89
'done', 'done', 'Marks the conflict as resolved.')
82
'done', 'done', 'Marks the conflict as resolved' )
90
83
resolve_action_registry.register(
91
84
'take-this', 'take_this',
92
'Resolve the conflict preserving the version in the working tree.')
85
'Resolve the conflict preserving the version in the working tree' )
93
86
resolve_action_registry.register(
94
87
'take-other', 'take_other',
95
'Resolve the conflict taking the merged version into account.')
88
'Resolve the conflict taking the merged version into account' )
96
89
resolve_action_registry.default_key = 'done'
99
91
class ResolveActionOption(option.RegistryOption):
101
93
def __init__(self):
111
103
Merge will do its best to combine the changes in two branches, but there
112
104
are some kinds of problems only a human can fix. When it encounters those,
113
105
it will mark a conflict. A conflict means that you need to fix something,
114
before you can commit.
106
before you should commit.
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.
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.
120
112
aliases = ['resolved']
121
113
takes_args = ['file*']
122
114
takes_options = [
124
option.Option('all', help='Resolve all conflicts in this tree.'),
125
ResolveActionOption(),
115
option.Option('all', help='Resolve all conflicts in this tree.'),
116
ResolveActionOption(),
127
118
_see_also = ['conflicts']
129
def run(self, file_list=None, all=False, action=None, directory=None):
119
def run(self, file_list=None, all=False, action=None):
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]
122
raise errors.BzrCommandError("If --all is specified,"
123
" no FILE may be provided")
124
tree = workingtree.WorkingTree.open_containing('.')[0]
137
125
if action is None:
140
tree, file_list = workingtree.WorkingTree.open_containing_paths(
141
file_list, directory)
143
if file_list 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
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))
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.')
160
trace.note(gettext('All conflicts resolved.'))
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
163
trace.note(ngettext('{0} conflict resolved, {1} remaining',
164
'{0} conflicts resolved, {1} remaining',
165
before - after).format(before - after, after))
158
resolve(tree, file_list, action=action)
168
161
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
361
351
def __init__(self, path, file_id=None):
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)
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)
369
357
def as_stanza(self):
370
358
s = rio.Stanza(type=self.typestring, path=self.path)
459
436
raise NotImplementedError(self.action_take_other)
461
438
def _resolve_with_cleanups(self, tree, *args, **kwargs):
462
with tree.get_transform() as tt:
463
self._resolve(tt, *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)
466
445
class PathConflict(Conflict):
515
494
raise AssertionError('bad winner: %r' % (winner,))
516
495
if path_to_create is not None:
517
496
tid = tt.trans_id_tree_path(path_to_create)
518
tree = self._revision_tree(tt._tree, revid)
519
497
transform.create_from_tree(
520
tt, tid, tree, tree.id2path(file_id))
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
521
500
tt.version_file(file_id, tid)
523
tid = tt.trans_id_file_id(file_id)
524
502
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
525
504
parent_tid = tt.get_tree_parent(tid)
526
tt.adjust_path(osutils.basename(path), parent_tid, tid)
505
tt.adjust_path(path, parent_tid, tid)
529
508
def _revision_tree(self, tree, revid):
605
584
# never existed or was already deleted (including the case
606
585
# where the user deleted it)
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)
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)
626
594
def action_take_this(self, tree):
627
595
self._resolve_with_cleanups(tree, 'OTHER')
642
613
format = 'Text conflict in %(path)s'
644
rformat = '%(class)s(%(path)r, %(file_id)r)'
646
_conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
648
615
def associated_filenames(self):
649
616
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')
701
619
class HandledConflict(Conflict):
702
620
"""A path problem that has been provisionally resolved.
734
652
conflict_file_id=None):
735
653
HandledConflict.__init__(self, action, path, file_id)
736
654
self.conflict_path = conflict_path
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)
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,
743
660
def _cmp_list(self):
744
661
return HandledConflict._cmp_list(self) + [self.conflict_path,
792
709
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
794
711
def action_take_this(self, tree):
795
# just acccept brz proposal
712
# just acccept bzr proposal
798
715
def action_take_other(self, tree):
799
with tree.get_transform() as tt:
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)
800
722
p_tid = tt.trans_id_file_id(self.file_id)
801
723
parent_tid = tt.get_tree_parent(p_tid)
802
724
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
803
725
cparent_tid = tt.get_tree_parent(cp_tid)
804
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
805
tt.adjust_path(osutils.basename(self.conflict_path),
726
tt.adjust_path(base_path, cparent_tid, cp_tid)
727
tt.adjust_path(conflict_base_path, parent_tid, p_tid)
810
733
class UnversionedParent(HandledConflict):