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
22
from bzrlib.lazy_import import lazy_import
23
from .lazy_import import lazy_import
23
24
lazy_import(globals(), """
34
from breezy.i18n import gettext, ngettext
50
51
Merge will do its best to combine the changes in two branches, but there
51
52
are some kinds of problems only a human can fix. When it encounters those,
52
53
it will mark a conflict. A conflict means that you need to fix something,
53
before you should commit.
54
before you can commit.
55
56
Conflicts normally are listed as short, human-readable messages. If --text
56
57
is supplied, the pathnames of files with text conflicts are listed,
57
58
instead. (This is useful for editing all files with text conflicts.)
59
Use bzr resolve when you have fixed a problem.
60
Use brz resolve when you have fixed a problem.
63
help='List paths of files with text conflicts.'),
65
help='List paths of files with text conflicts.'),
65
67
_see_also = ['resolve', 'conflict-types']
67
def run(self, text=False):
68
wt = workingtree.WorkingTree.open_containing(u'.')[0]
69
def run(self, text=False, directory=u'.'):
70
wt = workingtree.WorkingTree.open_containing(directory)[0]
69
71
for conflict in wt.conflicts():
71
73
if conflict.typestring != 'text conflict':
81
83
resolve_action_registry.register(
82
'done', 'done', 'Marks the conflict as resolved' )
84
'auto', 'auto', 'Detect whether conflict has been resolved by user.')
85
resolve_action_registry.register(
86
'done', 'done', 'Marks the conflict as resolved.')
83
87
resolve_action_registry.register(
84
88
'take-this', 'take_this',
85
'Resolve the conflict preserving the version in the working tree' )
89
'Resolve the conflict preserving the version in the working tree.')
86
90
resolve_action_registry.register(
87
91
'take-other', 'take_other',
88
'Resolve the conflict taking the merged version into account' )
92
'Resolve the conflict taking the merged version into account.')
89
93
resolve_action_registry.default_key = 'done'
91
96
class ResolveActionOption(option.RegistryOption):
93
98
def __init__(self):
103
108
Merge will do its best to combine the changes in two branches, but there
104
109
are some kinds of problems only a human can fix. When it encounters those,
105
110
it will mark a conflict. A conflict means that you need to fix something,
106
before you should commit.
111
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.
113
Once you have fixed a problem, use "brz resolve" to automatically mark
114
text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
115
resolved, or "brz resolve --all" to mark all conflicts as resolved.
112
117
aliases = ['resolved']
113
118
takes_args = ['file*']
114
119
takes_options = [
115
option.Option('all', help='Resolve all conflicts in this tree.'),
116
ResolveActionOption(),
121
option.Option('all', help='Resolve all conflicts in this tree.'),
122
ResolveActionOption(),
118
124
_see_also = ['conflicts']
119
def run(self, file_list=None, all=False, action=None):
126
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]
129
raise errors.BzrCommandError(gettext("If --all is specified,"
130
" no FILE may be provided"))
131
if directory is None:
133
tree = workingtree.WorkingTree.open_containing(directory)[0]
125
134
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
137
tree, file_list = workingtree.WorkingTree.open_containing_paths(
138
file_list, directory)
140
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.')
144
before, after = resolve(tree, file_list, action=action)
145
# GZ 2012-07-27: Should unify UI below now that auto is less magical.
146
if action == 'auto' and file_list is None:
149
ngettext('%d conflict auto-resolved.',
150
'%d conflicts auto-resolved.', before - after),
152
trace.note(gettext('Remaining conflicts:'))
153
for conflict in tree.conflicts():
154
trace.note(str(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
157
trace.note(gettext('All conflicts resolved.'))
158
resolve(tree, file_list, action=action)
160
trace.note(ngettext('{0} conflict resolved, {1} remaining',
161
'{0} conflicts resolved, {1} remaining',
162
before - after).format(before - after, after))
161
165
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
351
358
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)
360
# the factory blindly transfers the Stanza values to __init__ and
361
# Stanza is purely a Unicode api.
362
if isinstance(file_id, str):
363
file_id = cache_utf8.encode(file_id)
364
self.file_id = osutils.safe_file_id(file_id)
357
366
def as_stanza(self):
358
367
s = rio.Stanza(type=self.typestring, path=self.path)
436
456
raise NotImplementedError(self.action_take_other)
438
458
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)
459
with tree.get_transform() as tt:
460
self._resolve(tt, *args, **kwargs)
445
463
class PathConflict(Conflict):
494
512
raise AssertionError('bad winner: %r' % (winner,))
495
513
if path_to_create is not None:
496
514
tid = tt.trans_id_tree_path(path_to_create)
515
tree = self._revision_tree(tt._tree, revid)
497
516
transform.create_from_tree(
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
517
tt, tid, tree, tree.id2path(file_id))
500
518
tt.version_file(file_id, tid)
520
tid = tt.trans_id_file_id(file_id)
502
521
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
504
522
parent_tid = tt.get_tree_parent(tid)
505
tt.adjust_path(path, parent_tid, tid)
523
tt.adjust_path(osutils.basename(path), parent_tid, tid)
508
526
def _revision_tree(self, tree, revid):
584
602
# never existed or was already deleted (including the case
585
603
# 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)
606
this_path = tt._tree.id2path(self.file_id)
607
except errors.NoSuchId:
608
# The file is not present anymore. This may happen if the user
609
# deleted the file either manually or when resolving a conflict on
610
# the parent. We may raise some exception to indicate that the
611
# conflict doesn't exist anymore and as such doesn't need to be
612
# resolved ? -- vila 20110615
615
this_tid = tt.trans_id_tree_path(this_path)
616
if this_tid is not None:
617
# Rename 'item.suffix_to_remove' (note that if
618
# 'item.suffix_to_remove' has been deleted, this is a no-op)
619
parent_tid = tt.get_tree_parent(this_tid)
620
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
594
623
def action_take_this(self, tree):
595
624
self._resolve_with_cleanups(tree, 'OTHER')
613
639
format = 'Text conflict in %(path)s'
641
rformat = '%(class)s(%(path)r, %(file_id)r)'
643
_conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
615
645
def associated_filenames(self):
616
646
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
648
def _resolve(self, tt, winner_suffix):
649
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
651
:param tt: The TreeTransform where the conflict is resolved.
652
:param winner_suffix: Either 'THIS' or 'OTHER'
654
The resolution is symmetric, when taking THIS, item.THIS is renamed
655
into item and vice-versa. This takes one of the files as a whole
656
ignoring every difference that could have been merged cleanly.
658
# To avoid useless copies, we switch item and item.winner_suffix, only
659
# item will exist after the conflict has been resolved anyway.
660
item_tid = tt.trans_id_file_id(self.file_id)
661
item_parent_tid = tt.get_tree_parent(item_tid)
662
winner_path = self.path + '.' + winner_suffix
663
winner_tid = tt.trans_id_tree_path(winner_path)
664
winner_parent_tid = tt.get_tree_parent(winner_tid)
665
# Switch the paths to preserve the content
666
tt.adjust_path(osutils.basename(self.path),
667
winner_parent_tid, winner_tid)
668
tt.adjust_path(osutils.basename(winner_path),
669
item_parent_tid, item_tid)
670
# Associate the file_id to the right content
671
tt.unversion_file(item_tid)
672
tt.version_file(self.file_id, winner_tid)
675
def action_auto(self, tree):
676
# GZ 2012-07-27: Using NotImplementedError to signal that a conflict
677
# can't be auto resolved does not seem ideal.
679
kind = tree.kind(self.path)
680
except errors.NoSuchFile:
683
raise NotImplementedError("Conflict is not a file")
684
conflict_markers_in_line = self._conflict_re.search
685
# GZ 2012-07-27: What if not tree.has_id(self.file_id) due to removal?
686
with tree.get_file(self.path) as f:
688
if conflict_markers_in_line(line):
689
raise NotImplementedError("Conflict markers present")
691
def action_take_this(self, tree):
692
self._resolve_with_cleanups(tree, 'THIS')
694
def action_take_other(self, tree):
695
self._resolve_with_cleanups(tree, 'OTHER')
619
698
class HandledConflict(Conflict):
620
699
"""A path problem that has been provisionally resolved.
652
731
conflict_file_id=None):
653
732
HandledConflict.__init__(self, action, path, file_id)
654
733
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,
734
# the factory blindly transfers the Stanza values to __init__,
735
# so they can be unicode.
736
if isinstance(conflict_file_id, str):
737
conflict_file_id = cache_utf8.encode(conflict_file_id)
738
self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
660
740
def _cmp_list(self):
661
741
return HandledConflict._cmp_list(self) + [self.conflict_path,
709
789
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
711
791
def action_take_this(self, tree):
712
# just acccept bzr proposal
792
# just acccept brz proposal
715
795
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)
796
with tree.get_transform() as tt:
722
797
p_tid = tt.trans_id_file_id(self.file_id)
723
798
parent_tid = tt.get_tree_parent(p_tid)
724
799
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
800
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)
801
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
802
tt.adjust_path(osutils.basename(self.conflict_path),
733
807
class UnversionedParent(HandledConflict):