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
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
20
from __future__ import absolute_import
22
from bzrlib.lazy_import import lazy_import
24
from .lazy_import import lazy_import
23
25
lazy_import(globals(), """
50
54
Merge will do its best to combine the changes in two branches, but there
51
55
are some kinds of problems only a human can fix. When it encounters those,
52
56
it will mark a conflict. A conflict means that you need to fix something,
53
before you should commit.
57
before you can commit.
55
59
Conflicts normally are listed as short, human-readable messages. If --text
56
60
is supplied, the pathnames of files with text conflicts are listed,
57
61
instead. (This is useful for editing all files with text conflicts.)
59
Use bzr resolve when you have fixed a problem.
63
Use brz resolve when you have fixed a problem.
63
help='List paths of files with text conflicts.'),
68
help='List paths of files with text conflicts.'),
65
70
_see_also = ['resolve', 'conflict-types']
67
def run(self, text=False):
68
wt = workingtree.WorkingTree.open_containing(u'.')[0]
72
def run(self, text=False, directory=u'.'):
73
wt = workingtree.WorkingTree.open_containing(directory)[0]
69
74
for conflict in wt.conflicts():
71
76
if conflict.typestring != 'text conflict':
73
78
self.outf.write(conflict.path + '\n')
75
self.outf.write(str(conflict) + '\n')
80
self.outf.write(text_type(conflict) + '\n')
78
83
resolve_action_registry = registry.Registry()
81
86
resolve_action_registry.register(
82
'done', 'done', 'Marks the conflict as resolved' )
87
'done', 'done', 'Marks the conflict as resolved.')
83
88
resolve_action_registry.register(
84
89
'take-this', 'take_this',
85
'Resolve the conflict preserving the version in the working tree' )
90
'Resolve the conflict preserving the version in the working tree.')
86
91
resolve_action_registry.register(
87
92
'take-other', 'take_other',
88
'Resolve the conflict taking the merged version into account' )
93
'Resolve the conflict taking the merged version into account.')
89
94
resolve_action_registry.default_key = 'done'
91
97
class ResolveActionOption(option.RegistryOption):
93
99
def __init__(self):
103
109
Merge will do its best to combine the changes in two branches, but there
104
110
are some kinds of problems only a human can fix. When it encounters those,
105
111
it will mark a conflict. A conflict means that you need to fix something,
106
before you should commit.
112
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.
114
Once you have fixed a problem, use "brz resolve" to automatically mark
115
text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
116
resolved, or "brz resolve --all" to mark all conflicts as resolved.
112
118
aliases = ['resolved']
113
119
takes_args = ['file*']
114
120
takes_options = [
115
option.Option('all', help='Resolve all conflicts in this tree.'),
116
ResolveActionOption(),
122
option.Option('all', help='Resolve all conflicts in this tree.'),
123
ResolveActionOption(),
118
125
_see_also = ['conflicts']
119
def run(self, file_list=None, all=False, action=None):
127
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]
130
raise errors.BzrCommandError(gettext("If --all is specified,"
131
" no FILE may be provided"))
132
if directory is None:
134
tree = workingtree.WorkingTree.open_containing(directory)[0]
125
135
if action is None:
128
tree, file_list = builtins.tree_files(file_list)
138
tree, file_list = workingtree.WorkingTree.open_containing_paths(
139
file_list, directory)
129
140
if file_list is None:
130
141
if action is None:
131
142
# FIXME: There is a special case here related to the option
141
152
if file_list is None:
142
153
un_resolved, resolved = tree.auto_resolve()
143
154
if len(un_resolved) > 0:
144
trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
trace.note('Remaining conflicts:')
155
trace.note(ngettext('%d conflict auto-resolved.',
156
'%d conflicts auto-resolved.', len(resolved)),
158
trace.note(gettext('Remaining conflicts:'))
146
159
for conflict in un_resolved:
160
trace.note(text_type(conflict))
150
trace.note('All conflicts resolved.')
163
trace.note(gettext('All conflicts resolved.'))
153
166
# FIXME: This can never occur but the block above needs some
155
168
# conflict.auto(tree) --vila 091242
158
resolve(tree, file_list, action=action)
171
before, after = resolve(tree, file_list, action=action)
172
trace.note(ngettext('{0} conflict resolved, {1} remaining',
173
'{0} conflicts resolved, {1} remaining',
174
before - after).format(before - after, after))
161
177
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
173
189
paths do not have conflicts.
174
190
:param action: How the conflict should be resolved,
176
tree.lock_tree_write()
192
nb_conflicts_after = None
193
with tree.lock_tree_write():
178
194
tree_conflicts = tree.conflicts()
195
nb_conflicts_before = len(tree_conflicts)
179
196
if paths is None:
180
197
new_conflicts = ConflictList()
181
198
to_process = tree_conflicts
189
206
except NotImplementedError:
190
207
new_conflicts.append(conflict)
209
nb_conflicts_after = len(new_conflicts)
192
210
tree.set_conflicts(new_conflicts)
193
211
except errors.UnsupportedOperation:
213
if nb_conflicts_after is None:
214
nb_conflicts_after = nb_conflicts_before
215
return nb_conflicts_before, nb_conflicts_after
199
218
def restore(filename):
206
225
osutils.rename(filename + ".THIS", filename)
207
226
conflicted = True
209
228
if e.errno != errno.ENOENT:
212
231
os.unlink(filename + ".BASE")
213
232
conflicted = True
215
234
if e.errno != errno.ENOENT:
218
237
os.unlink(filename + ".OTHER")
219
238
conflicted = True
221
240
if e.errno != errno.ENOENT:
223
242
if not conflicted:
336
355
if ignore_misses is not True:
337
356
for path in [p for p in paths if p not in selected_paths]:
338
357
if not os.path.exists(tree.abspath(path)):
339
print "%s does not exist" % path
358
print("%s does not exist" % path)
341
print "%s is not conflicted" % path
360
print("%s is not conflicted" % path)
342
361
return new_conflicts, selected_conflicts
351
370
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)
372
# the factory blindly transfers the Stanza values to __init__ and
373
# Stanza is purely a Unicode api.
374
if isinstance(file_id, text_type):
375
file_id = cache_utf8.encode(file_id)
376
self.file_id = osutils.safe_file_id(file_id)
357
378
def as_stanza(self):
358
379
s = rio.Stanza(type=self.typestring, path=self.path)
367
388
def __cmp__(self, other):
368
389
if getattr(other, "_cmp_list", None) is None:
370
return cmp(self._cmp_list(), other._cmp_list())
392
y = other._cmp_list()
393
return (x > y) - (x < y)
372
395
def __hash__(self):
373
396
return hash((type(self), self.path, self.file_id))
494
523
raise AssertionError('bad winner: %r' % (winner,))
495
524
if path_to_create is not None:
496
525
tid = tt.trans_id_tree_path(path_to_create)
526
tree = self._revision_tree(tt._tree, revid)
497
527
transform.create_from_tree(
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
528
tt, tid, tree, tree.id2path(file_id), file_id=file_id)
500
529
tt.version_file(file_id, tid)
531
tid = tt.trans_id_file_id(file_id)
502
532
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
504
533
parent_tid = tt.get_tree_parent(tid)
505
tt.adjust_path(path, parent_tid, tid)
534
tt.adjust_path(osutils.basename(path), parent_tid, tid)
508
537
def _revision_tree(self, tree, revid):
571
600
:param tt: The TreeTransform where the conflict is resolved.
572
601
:param suffix_to_remove: Either 'THIS' or 'OTHER'
574
The resolution is symmetric, when taking THIS, OTHER is deleted and
603
The resolution is symmetric: when taking THIS, OTHER is deleted and
575
604
item.THIS is renamed into item and vice-versa.
584
613
# never existed or was already deleted (including the case
585
614
# 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)
617
this_path = tt._tree.id2path(self.file_id)
618
except errors.NoSuchId:
619
# The file is not present anymore. This may happen if the user
620
# deleted the file either manually or when resolving a conflict on
621
# the parent. We may raise some exception to indicate that the
622
# conflict doesn't exist anymore and as such doesn't need to be
623
# resolved ? -- vila 20110615
626
this_tid = tt.trans_id_tree_path(this_path)
627
if this_tid is not None:
628
# Rename 'item.suffix_to_remove' (note that if
629
# 'item.suffix_to_remove' has been deleted, this is a no-op)
630
parent_tid = tt.get_tree_parent(this_tid)
631
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
594
634
def action_take_this(self, tree):
595
635
self._resolve_with_cleanups(tree, 'OTHER')
598
638
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
641
# TODO: There should be a base revid attribute to better inform the user about
605
642
# how the conflicts were generated.
606
class TextConflict(PathConflict):
643
class TextConflict(Conflict):
607
644
"""The merge algorithm could not resolve all differences encountered."""
613
650
format = 'Text conflict in %(path)s'
652
rformat = '%(class)s(%(path)r, %(file_id)r)'
615
654
def associated_filenames(self):
616
655
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
657
def _resolve(self, tt, winner_suffix):
658
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
660
:param tt: The TreeTransform where the conflict is resolved.
661
:param winner_suffix: Either 'THIS' or 'OTHER'
663
The resolution is symmetric, when taking THIS, item.THIS is renamed
664
into item and vice-versa. This takes one of the files as a whole
665
ignoring every difference that could have been merged cleanly.
667
# To avoid useless copies, we switch item and item.winner_suffix, only
668
# item will exist after the conflict has been resolved anyway.
669
item_tid = tt.trans_id_file_id(self.file_id)
670
item_parent_tid = tt.get_tree_parent(item_tid)
671
winner_path = self.path + '.' + winner_suffix
672
winner_tid = tt.trans_id_tree_path(winner_path)
673
winner_parent_tid = tt.get_tree_parent(winner_tid)
674
# Switch the paths to preserve the content
675
tt.adjust_path(osutils.basename(self.path),
676
winner_parent_tid, winner_tid)
677
tt.adjust_path(osutils.basename(winner_path),
678
item_parent_tid, item_tid)
679
# Associate the file_id to the right content
680
tt.unversion_file(item_tid)
681
tt.version_file(self.file_id, winner_tid)
684
def action_take_this(self, tree):
685
self._resolve_with_cleanups(tree, 'THIS')
687
def action_take_other(self, tree):
688
self._resolve_with_cleanups(tree, 'OTHER')
619
691
class HandledConflict(Conflict):
620
692
"""A path problem that has been provisionally resolved.
652
724
conflict_file_id=None):
653
725
HandledConflict.__init__(self, action, path, file_id)
654
726
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,
727
# the factory blindly transfers the Stanza values to __init__,
728
# so they can be unicode.
729
if isinstance(conflict_file_id, text_type):
730
conflict_file_id = cache_utf8.encode(conflict_file_id)
731
self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
660
733
def _cmp_list(self):
661
734
return HandledConflict._cmp_list(self) + [self.conflict_path,
709
782
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
711
784
def action_take_this(self, tree):
712
# just acccept bzr proposal
785
# just acccept brz proposal
715
788
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
789
tt = transform.TreeTransform(tree)
722
791
p_tid = tt.trans_id_file_id(self.file_id)
723
792
parent_tid = tt.get_tree_parent(p_tid)
724
793
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
794
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)
795
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
796
tt.adjust_path(osutils.basename(self.conflict_path),
785
855
# MissingParent from the *user* pov.
787
857
def action_take_this(self, tree):
788
# just acccept bzr proposal
858
# just acccept brz proposal
791
861
def action_take_other(self, tree):
832
902
for conflict_type in conflict_types:
833
903
ctype[conflict_type.typestring] = conflict_type
835
906
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
836
907
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
837
908
DeletingParent, NonDirectoryParent)