1
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007, 2009, 2010 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
52
49
Merge will do its best to combine the changes in two branches, but there
53
50
are some kinds of problems only a human can fix. When it encounters those,
54
51
it will mark a conflict. A conflict means that you need to fix something,
55
before you can commit.
52
before you should commit.
57
54
Conflicts normally are listed as short, human-readable messages. If --text
58
55
is supplied, the pathnames of files with text conflicts are listed,
76
73
self.outf.write(conflict.path + '\n')
78
self.outf.write(unicode(conflict) + '\n')
75
self.outf.write(str(conflict) + '\n')
81
78
resolve_action_registry = registry.Registry()
84
81
resolve_action_registry.register(
85
'done', 'done', 'Marks the conflict as resolved.')
82
'done', 'done', 'Marks the conflict as resolved' )
86
83
resolve_action_registry.register(
87
84
'take-this', 'take_this',
88
'Resolve the conflict preserving the version in the working tree.')
85
'Resolve the conflict preserving the version in the working tree' )
89
86
resolve_action_registry.register(
90
87
'take-other', 'take_other',
91
'Resolve the conflict taking the merged version into account.')
88
'Resolve the conflict taking the merged version into account' )
92
89
resolve_action_registry.default_key = 'done'
94
91
class ResolveActionOption(option.RegistryOption):
106
103
Merge will do its best to combine the changes in two branches, but there
107
104
are some kinds of problems only a human can fix. When it encounters those,
108
105
it will mark a conflict. A conflict means that you need to fix something,
109
before you can commit.
106
before you should commit.
111
108
Once you have fixed a problem, use "bzr resolve" to automatically mark
112
109
text conflicts as fixed, "bzr resolve FILE" to mark a specific conflict as
120
117
ResolveActionOption(),
122
119
_see_also = ['conflicts']
123
def run(self, file_list=None, all=False, action=None, directory=None):
120
def run(self, file_list=None, all=False, action=None, directory=u'.'):
126
raise errors.BzrCommandError(gettext("If --all is specified,"
127
" no FILE may be provided"))
128
if directory is None:
123
raise errors.BzrCommandError("If --all is specified,"
124
" no FILE may be provided")
130
125
tree = workingtree.WorkingTree.open_containing(directory)[0]
131
126
if action is None:
134
129
tree, file_list = workingtree.WorkingTree.open_containing_paths(
135
file_list, directory)
136
131
if file_list is None:
137
132
if action is None:
138
133
# FIXME: There is a special case here related to the option
148
143
if file_list is None:
149
144
un_resolved, resolved = tree.auto_resolve()
150
145
if len(un_resolved) > 0:
151
trace.note(ngettext('%d conflict auto-resolved.',
152
'%d conflicts auto-resolved.', len(resolved)),
154
trace.note(gettext('Remaining conflicts:'))
146
trace.note('%d conflict(s) auto-resolved.', len(resolved))
147
trace.note('Remaining conflicts:')
155
148
for conflict in un_resolved:
156
trace.note(unicode(conflict))
159
trace.note(gettext('All conflicts resolved.'))
152
trace.note('All conflicts resolved.')
162
155
# FIXME: This can never occur but the block above needs some
164
157
# conflict.auto(tree) --vila 091242
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))
160
resolve(tree, file_list, action=action)
173
163
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
186
176
:param action: How the conflict should be resolved,
188
178
tree.lock_tree_write()
189
nb_conflicts_after = None
191
180
tree_conflicts = tree.conflicts()
192
nb_conflicts_before = len(tree_conflicts)
193
181
if paths is None:
194
182
new_conflicts = ConflictList()
195
183
to_process = tree_conflicts
203
191
except NotImplementedError:
204
192
new_conflicts.append(conflict)
206
nb_conflicts_after = len(new_conflicts)
207
194
tree.set_conflicts(new_conflicts)
208
195
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
217
201
def restore(filename):
513
497
if path_to_create is not None:
514
498
tid = tt.trans_id_tree_path(path_to_create)
515
499
transform.create_from_tree(
516
tt, tid, self._revision_tree(tt._tree, revid), file_id)
500
tt, tt.trans_id_tree_path(path_to_create),
501
self._revision_tree(tt._tree, revid), file_id)
517
502
tt.version_file(file_id, tid)
519
tid = tt.trans_id_file_id(file_id)
520
504
# Adjust the path for the retained file id
505
tid = tt.trans_id_file_id(file_id)
521
506
parent_tid = tt.get_tree_parent(tid)
522
tt.adjust_path(osutils.basename(path), parent_tid, tid)
507
tt.adjust_path(path, parent_tid, tid)
525
510
def _revision_tree(self, tree, revid):
588
573
:param tt: The TreeTransform where the conflict is resolved.
589
574
:param suffix_to_remove: Either 'THIS' or 'OTHER'
591
The resolution is symmetric: when taking THIS, OTHER is deleted and
576
The resolution is symmetric, when taking THIS, OTHER is deleted and
592
577
item.THIS is renamed into item and vice-versa.
601
586
# never existed or was already deleted (including the case
602
587
# where the user deleted it)
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)
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)
622
596
def action_take_this(self, tree):
623
597
self._resolve_with_cleanups(tree, 'OTHER')
626
600
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
629
606
# TODO: There should be a base revid attribute to better inform the user about
630
607
# how the conflicts were generated.
631
class TextConflict(Conflict):
608
class TextConflict(PathConflict):
632
609
"""The merge algorithm could not resolve all differences encountered."""
638
615
format = 'Text conflict in %(path)s'
640
rformat = '%(class)s(%(path)r, %(file_id)r)'
642
617
def associated_filenames(self):
643
618
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')
678
621
class HandledConflict(Conflict):
679
622
"""A path problem that has been provisionally resolved.
774
717
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)
775
722
tt = transform.TreeTransform(tree)
777
724
p_tid = tt.trans_id_file_id(self.file_id)
778
725
parent_tid = tt.get_tree_parent(p_tid)
779
726
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
780
727
cparent_tid = tt.get_tree_parent(cp_tid)
781
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
782
tt.adjust_path(osutils.basename(self.conflict_path),
728
tt.adjust_path(base_path, cparent_tid, cp_tid)
729
tt.adjust_path(conflict_base_path, parent_tid, p_tid)