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