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
49
52
Merge will do its best to combine the changes in two branches, but there
50
53
are some kinds of problems only a human can fix. When it encounters those,
51
54
it will mark a conflict. A conflict means that you need to fix something,
52
before you should commit.
55
before you can commit.
54
57
Conflicts normally are listed as short, human-readable messages. If --text
55
58
is supplied, the pathnames of files with text conflicts are listed,
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
117
120
ResolveActionOption(),
119
122
_see_also = ['conflicts']
120
def run(self, file_list=None, all=False, action=None, directory=u'.'):
123
def run(self, file_list=None, all=False, action=None, directory=None):
123
raise errors.BzrCommandError("If --all is specified,"
124
" no FILE may be provided")
126
raise errors.BzrCommandError(gettext("If --all is specified,"
127
" no FILE may be provided"))
128
if directory is None:
125
130
tree = workingtree.WorkingTree.open_containing(directory)[0]
126
131
if action is None:
129
134
tree, file_list = workingtree.WorkingTree.open_containing_paths(
135
file_list, directory)
131
136
if file_list is None:
132
137
if action is None:
133
138
# FIXME: There is a special case here related to the option
143
148
if file_list is None:
144
149
un_resolved, resolved = tree.auto_resolve()
145
150
if len(un_resolved) > 0:
146
trace.note('%d conflict(s) auto-resolved.', len(resolved))
147
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:'))
148
155
for conflict in un_resolved:
156
trace.note(unicode(conflict))
152
trace.note('All conflicts resolved.')
159
trace.note(gettext('All conflicts resolved.'))
155
162
# FIXME: This can never occur but the block above needs some
157
164
# conflict.auto(tree) --vila 091242
160
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))
163
173
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
176
186
:param action: How the conflict should be resolved,
178
188
tree.lock_tree_write()
189
nb_conflicts_after = None
180
191
tree_conflicts = tree.conflicts()
192
nb_conflicts_before = len(tree_conflicts)
181
193
if paths is None:
182
194
new_conflicts = ConflictList()
183
195
to_process = tree_conflicts
191
203
except NotImplementedError:
192
204
new_conflicts.append(conflict)
206
nb_conflicts_after = len(new_conflicts)
194
207
tree.set_conflicts(new_conflicts)
195
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
201
217
def restore(filename):
497
513
if path_to_create is not None:
498
514
tid = tt.trans_id_tree_path(path_to_create)
499
515
transform.create_from_tree(
500
tt, tt.trans_id_tree_path(path_to_create),
501
self._revision_tree(tt._tree, revid), file_id)
516
tt, tid, self._revision_tree(tt._tree, revid), file_id)
502
517
tt.version_file(file_id, tid)
519
tid = tt.trans_id_file_id(file_id)
504
520
# Adjust the path for the retained file id
505
tid = tt.trans_id_file_id(file_id)
506
521
parent_tid = tt.get_tree_parent(tid)
507
tt.adjust_path(path, parent_tid, tid)
522
tt.adjust_path(osutils.basename(path), parent_tid, tid)
510
525
def _revision_tree(self, tree, revid):
573
588
:param tt: The TreeTransform where the conflict is resolved.
574
589
:param suffix_to_remove: Either 'THIS' or 'OTHER'
576
The resolution is symmetric, when taking THIS, OTHER is deleted and
591
The resolution is symmetric: when taking THIS, OTHER is deleted and
577
592
item.THIS is renamed into item and vice-versa.
586
601
# never existed or was already deleted (including the case
587
602
# where the user deleted it)
589
# Rename 'item.suffix_to_remove' (note that if
590
# 'item.suffix_to_remove' has been deleted, this is a no-op)
591
this_tid = tt.trans_id_file_id(self.file_id)
592
parent_tid = tt.get_tree_parent(this_tid)
593
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)
596
622
def action_take_this(self, tree):
597
623
self._resolve_with_cleanups(tree, 'OTHER')
600
626
self._resolve_with_cleanups(tree, 'THIS')
603
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
604
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
606
629
# TODO: There should be a base revid attribute to better inform the user about
607
630
# how the conflicts were generated.
608
class TextConflict(PathConflict):
631
class TextConflict(Conflict):
609
632
"""The merge algorithm could not resolve all differences encountered."""
615
638
format = 'Text conflict in %(path)s'
640
rformat = '%(class)s(%(path)r, %(file_id)r)'
617
642
def associated_filenames(self):
618
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')
621
678
class HandledConflict(Conflict):
622
679
"""A path problem that has been provisionally resolved.
717
774
def action_take_other(self, tree):
718
# FIXME: We shouldn't have to manipulate so many paths here (and there
719
# is probably a bug or two...)
720
base_path = osutils.basename(self.path)
721
conflict_base_path = osutils.basename(self.conflict_path)
722
775
tt = transform.TreeTransform(tree)
724
777
p_tid = tt.trans_id_file_id(self.file_id)
725
778
parent_tid = tt.get_tree_parent(p_tid)
726
779
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
727
780
cparent_tid = tt.get_tree_parent(cp_tid)
728
tt.adjust_path(base_path, cparent_tid, cp_tid)
729
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),