1
# Copyright (C) 2005, 2006, 2007, 2009, 2010 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
50
52
Merge will do its best to combine the changes in two branches, but there
51
53
are some kinds of problems only a human can fix. When it encounters those,
52
54
it will mark a conflict. A conflict means that you need to fix something,
53
before you should commit.
55
before you can commit.
55
57
Conflicts normally are listed as short, human-readable messages. If --text
56
58
is supplied, the pathnames of files with text conflicts are listed,
59
61
Use bzr resolve when you have fixed a problem.
62
65
option.Option('text',
63
66
help='List paths of files with text conflicts.'),
65
68
_see_also = ['resolve', 'conflict-types']
67
def run(self, text=False):
68
wt = workingtree.WorkingTree.open_containing(u'.')[0]
70
def run(self, text=False, directory=u'.'):
71
wt = workingtree.WorkingTree.open_containing(directory)[0]
69
72
for conflict in wt.conflicts():
71
74
if conflict.typestring != 'text conflict':
73
76
self.outf.write(conflict.path + '\n')
75
self.outf.write(str(conflict) + '\n')
78
self.outf.write(unicode(conflict) + '\n')
78
81
resolve_action_registry = registry.Registry()
81
84
resolve_action_registry.register(
82
'done', 'done', 'Marks the conflict as resolved' )
85
'done', 'done', 'Marks the conflict as resolved.')
83
86
resolve_action_registry.register(
84
87
'take-this', 'take_this',
85
'Resolve the conflict preserving the version in the working tree' )
88
'Resolve the conflict preserving the version in the working tree.')
86
89
resolve_action_registry.register(
87
90
'take-other', 'take_other',
88
'Resolve the conflict taking the merged version into account' )
91
'Resolve the conflict taking the merged version into account.')
89
92
resolve_action_registry.default_key = 'done'
91
94
class ResolveActionOption(option.RegistryOption):
103
106
Merge will do its best to combine the changes in two branches, but there
104
107
are some kinds of problems only a human can fix. When it encounters those,
105
108
it will mark a conflict. A conflict means that you need to fix something,
106
before you should commit.
109
before you can commit.
108
111
Once you have fixed a problem, use "bzr resolve" to automatically mark
109
112
text conflicts as fixed, "bzr resolve FILE" to mark a specific conflict as
112
115
aliases = ['resolved']
113
116
takes_args = ['file*']
114
117
takes_options = [
115
119
option.Option('all', help='Resolve all conflicts in this tree.'),
116
120
ResolveActionOption(),
118
122
_see_also = ['conflicts']
119
def run(self, file_list=None, all=False, action=None):
123
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]
126
raise errors.BzrCommandError(gettext("If --all is specified,"
127
" no FILE may be provided"))
128
if directory is None:
130
tree = workingtree.WorkingTree.open_containing(directory)[0]
125
131
if action is None:
128
tree, file_list = builtins.tree_files(file_list)
134
tree, file_list = workingtree.WorkingTree.open_containing_paths(
135
file_list, directory)
129
136
if file_list is None:
130
137
if action is None:
131
138
# FIXME: There is a special case here related to the option
141
148
if file_list is None:
142
149
un_resolved, resolved = tree.auto_resolve()
143
150
if len(un_resolved) > 0:
144
trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
trace.note('Remaining conflicts:')
151
trace.note(ngettext('%d conflict auto-resolved.',
152
'%d conflicts auto-resolved.', len(resolved)),
154
trace.note(gettext('Remaining conflicts:'))
146
155
for conflict in un_resolved:
156
trace.note(unicode(conflict))
150
trace.note('All conflicts resolved.')
159
trace.note(gettext('All conflicts resolved.'))
153
162
# FIXME: This can never occur but the block above needs some
155
164
# conflict.auto(tree) --vila 091242
158
resolve(tree, file_list, action=action)
167
before, after = resolve(tree, file_list, action=action)
168
trace.note(ngettext('{0} conflict resolved, {1} remaining',
169
'{0} conflicts resolved, {1} remaining',
170
before-after).format(before - after, after))
161
173
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
174
186
:param action: How the conflict should be resolved,
176
188
tree.lock_tree_write()
189
nb_conflicts_after = None
178
191
tree_conflicts = tree.conflicts()
192
nb_conflicts_before = len(tree_conflicts)
179
193
if paths is None:
180
194
new_conflicts = ConflictList()
181
195
to_process = tree_conflicts
189
203
except NotImplementedError:
190
204
new_conflicts.append(conflict)
206
nb_conflicts_after = len(new_conflicts)
192
207
tree.set_conflicts(new_conflicts)
193
208
except errors.UnsupportedOperation:
212
if nb_conflicts_after is None:
213
nb_conflicts_after = nb_conflicts_before
214
return nb_conflicts_before, nb_conflicts_after
199
217
def restore(filename):
495
513
if path_to_create is not None:
496
514
tid = tt.trans_id_tree_path(path_to_create)
497
515
transform.create_from_tree(
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
516
tt, tid, self._revision_tree(tt._tree, revid), file_id)
500
517
tt.version_file(file_id, tid)
519
tid = tt.trans_id_file_id(file_id)
502
520
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
504
521
parent_tid = tt.get_tree_parent(tid)
505
tt.adjust_path(path, parent_tid, tid)
522
tt.adjust_path(osutils.basename(path), parent_tid, tid)
508
525
def _revision_tree(self, tree, revid):
571
588
:param tt: The TreeTransform where the conflict is resolved.
572
589
:param suffix_to_remove: Either 'THIS' or 'OTHER'
574
The resolution is symmetric, when taking THIS, OTHER is deleted and
591
The resolution is symmetric: when taking THIS, OTHER is deleted and
575
592
item.THIS is renamed into item and vice-versa.
584
601
# never existed or was already deleted (including the case
585
602
# 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)
605
this_path = tt._tree.id2path(self.file_id)
606
except errors.NoSuchId:
607
# The file is not present anymore. This may happen if the user
608
# deleted the file either manually or when resolving a conflict on
609
# the parent. We may raise some exception to indicate that the
610
# conflict doesn't exist anymore and as such doesn't need to be
611
# resolved ? -- vila 20110615
614
this_tid = tt.trans_id_tree_path(this_path)
615
if this_tid is not None:
616
# Rename 'item.suffix_to_remove' (note that if
617
# 'item.suffix_to_remove' has been deleted, this is a no-op)
618
parent_tid = tt.get_tree_parent(this_tid)
619
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
594
622
def action_take_this(self, tree):
595
623
self._resolve_with_cleanups(tree, 'OTHER')
598
626
self._resolve_with_cleanups(tree, 'THIS')
601
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
602
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
604
629
# TODO: There should be a base revid attribute to better inform the user about
605
630
# how the conflicts were generated.
606
class TextConflict(PathConflict):
631
class TextConflict(Conflict):
607
632
"""The merge algorithm could not resolve all differences encountered."""
613
638
format = 'Text conflict in %(path)s'
640
rformat = '%(class)s(%(path)r, %(file_id)r)'
615
642
def associated_filenames(self):
616
643
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
645
def _resolve(self, tt, winner_suffix):
646
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
648
:param tt: The TreeTransform where the conflict is resolved.
649
:param winner_suffix: Either 'THIS' or 'OTHER'
651
The resolution is symmetric, when taking THIS, item.THIS is renamed
652
into item and vice-versa. This takes one of the files as a whole
653
ignoring every difference that could have been merged cleanly.
655
# To avoid useless copies, we switch item and item.winner_suffix, only
656
# item will exist after the conflict has been resolved anyway.
657
item_tid = tt.trans_id_file_id(self.file_id)
658
item_parent_tid = tt.get_tree_parent(item_tid)
659
winner_path = self.path + '.' + winner_suffix
660
winner_tid = tt.trans_id_tree_path(winner_path)
661
winner_parent_tid = tt.get_tree_parent(winner_tid)
662
# Switch the paths to preserve the content
663
tt.adjust_path(osutils.basename(self.path),
664
winner_parent_tid, winner_tid)
665
tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
666
# Associate the file_id to the right content
667
tt.unversion_file(item_tid)
668
tt.version_file(self.file_id, winner_tid)
671
def action_take_this(self, tree):
672
self._resolve_with_cleanups(tree, 'THIS')
674
def action_take_other(self, tree):
675
self._resolve_with_cleanups(tree, 'OTHER')
619
678
class HandledConflict(Conflict):
620
679
"""A path problem that has been provisionally resolved.
715
774
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
775
tt = transform.TreeTransform(tree)
722
777
p_tid = tt.trans_id_file_id(self.file_id)
723
778
parent_tid = tt.get_tree_parent(p_tid)
724
779
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
780
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)
781
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
782
tt.adjust_path(osutils.basename(self.conflict_path),