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
46
48
class cmd_conflicts(commands.Command):
47
__doc__ = """List files with conflicts.
49
"""List files with conflicts.
49
51
Merge will do its best to combine the changes in two branches, but there
50
52
are some kinds of problems only a human can fix. When it encounters those,
58
60
Use bzr resolve when you have fixed a problem.
62
63
option.Option('text',
63
64
help='List paths of files with text conflicts.'),
65
66
_see_also = ['resolve', 'conflict-types']
67
def run(self, text=False, directory=u'.'):
68
wt = workingtree.WorkingTree.open_containing(directory)[0]
68
def run(self, text=False):
69
wt = workingtree.WorkingTree.open_containing(u'.')[0]
69
70
for conflict in wt.conflicts():
71
72
if conflict.typestring != 'text conflict':
73
74
self.outf.write(conflict.path + '\n')
75
self.outf.write(unicode(conflict) + '\n')
76
self.outf.write(str(conflict) + '\n')
78
79
resolve_action_registry = registry.Registry()
100
101
class cmd_resolve(commands.Command):
101
__doc__ = """Mark a conflict as resolved.
102
"""Mark a conflict as resolved.
103
104
Merge will do its best to combine the changes in two branches, but there
104
105
are some kinds of problems only a human can fix. When it encounters those,
112
113
aliases = ['resolved']
113
114
takes_args = ['file*']
114
115
takes_options = [
116
116
option.Option('all', help='Resolve all conflicts in this tree.'),
117
117
ResolveActionOption(),
119
119
_see_also = ['conflicts']
120
def run(self, file_list=None, all=False, action=None, directory=None):
120
def run(self, file_list=None, all=False, action=None):
123
123
raise errors.BzrCommandError("If --all is specified,"
124
124
" no FILE may be provided")
125
if directory is None:
127
tree = workingtree.WorkingTree.open_containing(directory)[0]
125
tree = workingtree.WorkingTree.open_containing('.')[0]
128
126
if action is None:
131
tree, file_list = workingtree.WorkingTree.open_containing_paths(
132
file_list, directory)
129
tree, file_list = builtins.tree_files(file_list)
133
130
if file_list is None:
134
131
if action is None:
135
132
# FIXME: There is a special case here related to the option
148
145
trace.note('%d conflict(s) auto-resolved.', len(resolved))
149
146
trace.note('Remaining conflicts:')
150
147
for conflict in un_resolved:
151
trace.note(unicode(conflict))
154
151
trace.note('All conflicts resolved.')
159
156
# conflict.auto(tree) --vila 091242
162
before, after = resolve(tree, file_list, action=action)
163
trace.note('%d conflict(s) resolved, %d remaining'
164
% (before - after, after))
159
resolve(tree, file_list, action=action)
167
162
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
180
175
:param action: How the conflict should be resolved,
182
177
tree.lock_tree_write()
183
nb_conflicts_after = None
185
179
tree_conflicts = tree.conflicts()
186
nb_conflicts_before = len(tree_conflicts)
187
180
if paths is None:
188
181
new_conflicts = ConflictList()
189
182
to_process = tree_conflicts
197
190
except NotImplementedError:
198
191
new_conflicts.append(conflict)
200
nb_conflicts_after = len(new_conflicts)
201
193
tree.set_conflicts(new_conflicts)
202
194
except errors.UnsupportedOperation:
206
if nb_conflicts_after is None:
207
nb_conflicts_after = nb_conflicts_before
208
return nb_conflicts_before, nb_conflicts_after
211
200
def restore(filename):
507
496
if path_to_create is not None:
508
497
tid = tt.trans_id_tree_path(path_to_create)
509
498
transform.create_from_tree(
510
tt, tid, self._revision_tree(tt._tree, revid), file_id)
499
tt, tt.trans_id_tree_path(path_to_create),
500
self._revision_tree(tt._tree, revid), file_id)
511
501
tt.version_file(file_id, tid)
513
tid = tt.trans_id_file_id(file_id)
514
503
# Adjust the path for the retained file id
504
tid = tt.trans_id_file_id(file_id)
515
505
parent_tid = tt.get_tree_parent(tid)
516
tt.adjust_path(osutils.basename(path), parent_tid, tid)
506
tt.adjust_path(path, parent_tid, tid)
519
509
def _revision_tree(self, tree, revid):
582
572
:param tt: The TreeTransform where the conflict is resolved.
583
573
:param suffix_to_remove: Either 'THIS' or 'OTHER'
585
The resolution is symmetric: when taking THIS, OTHER is deleted and
575
The resolution is symmetric, when taking THIS, OTHER is deleted and
586
576
item.THIS is renamed into item and vice-versa.
595
585
# never existed or was already deleted (including the case
596
586
# where the user deleted it)
599
this_path = tt._tree.id2path(self.file_id)
600
except errors.NoSuchId:
601
# The file is not present anymore. This may happen if the user
602
# deleted the file either manually or when resolving a conflict on
603
# the parent. We may raise some exception to indicate that the
604
# conflict doesn't exist anymore and as such doesn't need to be
605
# resolved ? -- vila 20110615
608
this_tid = tt.trans_id_tree_path(this_path)
609
if this_tid is not None:
610
# Rename 'item.suffix_to_remove' (note that if
611
# 'item.suffix_to_remove' has been deleted, this is a no-op)
612
parent_tid = tt.get_tree_parent(this_tid)
613
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
588
# Rename 'item.suffix_to_remove' (note that if
589
# 'item.suffix_to_remove' has been deleted, this is a no-op)
590
this_tid = tt.trans_id_file_id(self.file_id)
591
parent_tid = tt.get_tree_parent(this_tid)
592
tt.adjust_path(self.path, parent_tid, this_tid)
616
595
def action_take_this(self, tree):
617
596
self._resolve_with_cleanups(tree, 'OTHER')
620
599
self._resolve_with_cleanups(tree, 'THIS')
602
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
603
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
623
605
# TODO: There should be a base revid attribute to better inform the user about
624
606
# how the conflicts were generated.
625
class TextConflict(Conflict):
607
class TextConflict(PathConflict):
626
608
"""The merge algorithm could not resolve all differences encountered."""
632
614
format = 'Text conflict in %(path)s'
634
rformat = '%(class)s(%(path)r, %(file_id)r)'
636
616
def associated_filenames(self):
637
617
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
639
def _resolve(self, tt, winner_suffix):
640
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
642
:param tt: The TreeTransform where the conflict is resolved.
643
:param winner_suffix: Either 'THIS' or 'OTHER'
645
The resolution is symmetric, when taking THIS, item.THIS is renamed
646
into item and vice-versa. This takes one of the files as a whole
647
ignoring every difference that could have been merged cleanly.
649
# To avoid useless copies, we switch item and item.winner_suffix, only
650
# item will exist after the conflict has been resolved anyway.
651
item_tid = tt.trans_id_file_id(self.file_id)
652
item_parent_tid = tt.get_tree_parent(item_tid)
653
winner_path = self.path + '.' + winner_suffix
654
winner_tid = tt.trans_id_tree_path(winner_path)
655
winner_parent_tid = tt.get_tree_parent(winner_tid)
656
# Switch the paths to preserve the content
657
tt.adjust_path(osutils.basename(self.path),
658
winner_parent_tid, winner_tid)
659
tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
660
# Associate the file_id to the right content
661
tt.unversion_file(item_tid)
662
tt.version_file(self.file_id, winner_tid)
665
def action_take_this(self, tree):
666
self._resolve_with_cleanups(tree, 'THIS')
668
def action_take_other(self, tree):
669
self._resolve_with_cleanups(tree, 'OTHER')
672
620
class HandledConflict(Conflict):
673
621
"""A path problem that has been provisionally resolved.
768
716
def action_take_other(self, tree):
717
# FIXME: We shouldn't have to manipulate so many paths here (and there
718
# is probably a bug or two...)
719
base_path = osutils.basename(self.path)
720
conflict_base_path = osutils.basename(self.conflict_path)
769
721
tt = transform.TreeTransform(tree)
771
723
p_tid = tt.trans_id_file_id(self.file_id)
772
724
parent_tid = tt.get_tree_parent(p_tid)
773
725
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
774
726
cparent_tid = tt.get_tree_parent(cp_tid)
775
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
776
tt.adjust_path(osutils.basename(self.conflict_path),
727
tt.adjust_path(base_path, cparent_tid, cp_tid)
728
tt.adjust_path(conflict_base_path, parent_tid, p_tid)