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
55
Merge will do its best to combine the changes in two branches, but there
51
56
are some kinds of problems only a human can fix. When it encounters those,
52
57
it will mark a conflict. A conflict means that you need to fix something,
53
before you should commit.
58
before you can commit.
55
60
Conflicts normally are listed as short, human-readable messages. If --text
56
61
is supplied, the pathnames of files with text conflicts are listed,
57
62
instead. (This is useful for editing all files with text conflicts.)
59
Use bzr resolve when you have fixed a problem.
64
Use brz resolve when you have fixed a problem.
63
help='List paths of files with text conflicts.'),
69
help='List paths of files with text conflicts.'),
65
71
_see_also = ['resolve', 'conflict-types']
67
def run(self, text=False):
68
wt = workingtree.WorkingTree.open_containing(u'.')[0]
73
def run(self, text=False, directory=u'.'):
74
wt = workingtree.WorkingTree.open_containing(directory)[0]
69
75
for conflict in wt.conflicts():
71
77
if conflict.typestring != 'text conflict':
73
79
self.outf.write(conflict.path + '\n')
75
self.outf.write(str(conflict) + '\n')
81
self.outf.write(text_type(conflict) + '\n')
78
84
resolve_action_registry = registry.Registry()
81
87
resolve_action_registry.register(
82
'done', 'done', 'Marks the conflict as resolved' )
88
'auto', 'auto', 'Detect whether conflict has been resolved by user.')
89
resolve_action_registry.register(
90
'done', 'done', 'Marks the conflict as resolved.')
83
91
resolve_action_registry.register(
84
92
'take-this', 'take_this',
85
'Resolve the conflict preserving the version in the working tree' )
93
'Resolve the conflict preserving the version in the working tree.')
86
94
resolve_action_registry.register(
87
95
'take-other', 'take_other',
88
'Resolve the conflict taking the merged version into account' )
96
'Resolve the conflict taking the merged version into account.')
89
97
resolve_action_registry.default_key = 'done'
91
100
class ResolveActionOption(option.RegistryOption):
93
102
def __init__(self):
103
112
Merge will do its best to combine the changes in two branches, but there
104
113
are some kinds of problems only a human can fix. When it encounters those,
105
114
it will mark a conflict. A conflict means that you need to fix something,
106
before you should commit.
115
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.
117
Once you have fixed a problem, use "brz resolve" to automatically mark
118
text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
119
resolved, or "brz resolve --all" to mark all conflicts as resolved.
112
121
aliases = ['resolved']
113
122
takes_args = ['file*']
114
123
takes_options = [
115
option.Option('all', help='Resolve all conflicts in this tree.'),
116
ResolveActionOption(),
125
option.Option('all', help='Resolve all conflicts in this tree.'),
126
ResolveActionOption(),
118
128
_see_also = ['conflicts']
119
def run(self, file_list=None, all=False, action=None):
130
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]
133
raise errors.BzrCommandError(gettext("If --all is specified,"
134
" no FILE may be provided"))
135
if directory is None:
137
tree = workingtree.WorkingTree.open_containing(directory)[0]
125
138
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
141
tree, file_list = workingtree.WorkingTree.open_containing_paths(
142
file_list, directory)
144
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.')
148
before, after = resolve(tree, file_list, action=action)
149
# GZ 2012-07-27: Should unify UI below now that auto is less magical.
150
if action == 'auto' and file_list is None:
153
ngettext('%d conflict auto-resolved.',
154
'%d conflicts auto-resolved.', before - after),
156
trace.note(gettext('Remaining conflicts:'))
157
for conflict in tree.conflicts():
158
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
161
trace.note(gettext('All conflicts resolved.'))
158
resolve(tree, file_list, action=action)
164
trace.note(ngettext('{0} conflict resolved, {1} remaining',
165
'{0} conflicts resolved, {1} remaining',
166
before - after).format(before - after, after))
161
169
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
351
362
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)
364
# the factory blindly transfers the Stanza values to __init__ and
365
# Stanza is purely a Unicode api.
366
if isinstance(file_id, text_type):
367
file_id = cache_utf8.encode(file_id)
368
self.file_id = osutils.safe_file_id(file_id)
357
370
def as_stanza(self):
358
371
s = rio.Stanza(type=self.typestring, path=self.path)
494
518
raise AssertionError('bad winner: %r' % (winner,))
495
519
if path_to_create is not None:
496
520
tid = tt.trans_id_tree_path(path_to_create)
521
tree = self._revision_tree(tt._tree, revid)
497
522
transform.create_from_tree(
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
523
tt, tid, tree, tree.id2path(file_id), file_id=file_id)
500
524
tt.version_file(file_id, tid)
526
tid = tt.trans_id_file_id(file_id)
502
527
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
504
528
parent_tid = tt.get_tree_parent(tid)
505
tt.adjust_path(path, parent_tid, tid)
529
tt.adjust_path(osutils.basename(path), parent_tid, tid)
508
532
def _revision_tree(self, tree, revid):
584
608
# never existed or was already deleted (including the case
585
609
# 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)
612
this_path = tt._tree.id2path(self.file_id)
613
except errors.NoSuchId:
614
# The file is not present anymore. This may happen if the user
615
# deleted the file either manually or when resolving a conflict on
616
# the parent. We may raise some exception to indicate that the
617
# conflict doesn't exist anymore and as such doesn't need to be
618
# resolved ? -- vila 20110615
621
this_tid = tt.trans_id_tree_path(this_path)
622
if this_tid is not None:
623
# Rename 'item.suffix_to_remove' (note that if
624
# 'item.suffix_to_remove' has been deleted, this is a no-op)
625
parent_tid = tt.get_tree_parent(this_tid)
626
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
594
629
def action_take_this(self, tree):
595
630
self._resolve_with_cleanups(tree, 'OTHER')
613
645
format = 'Text conflict in %(path)s'
647
rformat = '%(class)s(%(path)r, %(file_id)r)'
649
_conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
615
651
def associated_filenames(self):
616
652
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
654
def _resolve(self, tt, winner_suffix):
655
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
657
:param tt: The TreeTransform where the conflict is resolved.
658
:param winner_suffix: Either 'THIS' or 'OTHER'
660
The resolution is symmetric, when taking THIS, item.THIS is renamed
661
into item and vice-versa. This takes one of the files as a whole
662
ignoring every difference that could have been merged cleanly.
664
# To avoid useless copies, we switch item and item.winner_suffix, only
665
# item will exist after the conflict has been resolved anyway.
666
item_tid = tt.trans_id_file_id(self.file_id)
667
item_parent_tid = tt.get_tree_parent(item_tid)
668
winner_path = self.path + '.' + winner_suffix
669
winner_tid = tt.trans_id_tree_path(winner_path)
670
winner_parent_tid = tt.get_tree_parent(winner_tid)
671
# Switch the paths to preserve the content
672
tt.adjust_path(osutils.basename(self.path),
673
winner_parent_tid, winner_tid)
674
tt.adjust_path(osutils.basename(winner_path),
675
item_parent_tid, item_tid)
676
# Associate the file_id to the right content
677
tt.unversion_file(item_tid)
678
tt.version_file(self.file_id, winner_tid)
681
def action_auto(self, tree):
682
# GZ 2012-07-27: Using NotImplementedError to signal that a conflict
683
# can't be auto resolved does not seem ideal.
685
kind = tree.kind(self.path)
686
except errors.NoSuchFile:
689
raise NotImplementedError("Conflict is not a file")
690
conflict_markers_in_line = self._conflict_re.search
691
# GZ 2012-07-27: What if not tree.has_id(self.file_id) due to removal?
692
with tree.get_file(self.path) as f:
694
if conflict_markers_in_line(line):
695
raise NotImplementedError("Conflict markers present")
697
def action_take_this(self, tree):
698
self._resolve_with_cleanups(tree, 'THIS')
700
def action_take_other(self, tree):
701
self._resolve_with_cleanups(tree, 'OTHER')
619
704
class HandledConflict(Conflict):
620
705
"""A path problem that has been provisionally resolved.
652
737
conflict_file_id=None):
653
738
HandledConflict.__init__(self, action, path, file_id)
654
739
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,
740
# the factory blindly transfers the Stanza values to __init__,
741
# so they can be unicode.
742
if isinstance(conflict_file_id, text_type):
743
conflict_file_id = cache_utf8.encode(conflict_file_id)
744
self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
660
746
def _cmp_list(self):
661
747
return HandledConflict._cmp_list(self) + [self.conflict_path,
709
795
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
711
797
def action_take_this(self, tree):
712
# just acccept bzr proposal
798
# just acccept brz proposal
715
801
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
802
tt = transform.TreeTransform(tree)
722
804
p_tid = tt.trans_id_file_id(self.file_id)
723
805
parent_tid = tt.get_tree_parent(p_tid)
724
806
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
807
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)
808
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
809
tt.adjust_path(osutils.basename(self.conflict_path),