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.
62
67
option.Option('text',
63
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
96
class ResolveActionOption(option.RegistryOption):
103
108
Merge will do its best to combine the changes in two branches, but there
104
109
are some kinds of problems only a human can fix. When it encounters those,
105
110
it will mark a conflict. A conflict means that you need to fix something,
106
before you should commit.
111
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.
113
Once you have fixed a problem, use "brz resolve" to automatically mark
114
text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
115
resolved, or "brz resolve --all" to mark all conflicts as resolved.
112
117
aliases = ['resolved']
113
118
takes_args = ['file*']
114
119
takes_options = [
115
121
option.Option('all', help='Resolve all conflicts in this tree.'),
116
122
ResolveActionOption(),
118
124
_see_also = ['conflicts']
119
def run(self, file_list=None, all=False, action=None):
125
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]
128
raise errors.BzrCommandError(gettext("If --all is specified,"
129
" no FILE may be provided"))
130
if directory is None:
132
tree = workingtree.WorkingTree.open_containing(directory)[0]
125
133
if action is None:
128
tree, file_list = builtins.tree_files(file_list)
136
tree, file_list = workingtree.WorkingTree.open_containing_paths(
137
file_list, directory)
129
138
if file_list is None:
130
139
if action is None:
131
140
# FIXME: There is a special case here related to the option
141
150
if file_list is None:
142
151
un_resolved, resolved = tree.auto_resolve()
143
152
if len(un_resolved) > 0:
144
trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
trace.note('Remaining conflicts:')
153
trace.note(ngettext('%d conflict auto-resolved.',
154
'%d conflicts auto-resolved.', len(resolved)),
156
trace.note(gettext('Remaining conflicts:'))
146
157
for conflict in un_resolved:
158
trace.note(text_type(conflict))
150
trace.note('All conflicts resolved.')
161
trace.note(gettext('All conflicts resolved.'))
153
164
# FIXME: This can never occur but the block above needs some
155
166
# conflict.auto(tree) --vila 091242
158
resolve(tree, file_list, action=action)
169
before, after = resolve(tree, file_list, action=action)
170
trace.note(ngettext('{0} conflict resolved, {1} remaining',
171
'{0} conflicts resolved, {1} remaining',
172
before-after).format(before - after, after))
161
175
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
173
187
paths do not have conflicts.
174
188
:param action: How the conflict should be resolved,
176
tree.lock_tree_write()
190
nb_conflicts_after = None
191
with tree.lock_tree_write():
178
192
tree_conflicts = tree.conflicts()
193
nb_conflicts_before = len(tree_conflicts)
179
194
if paths is None:
180
195
new_conflicts = ConflictList()
181
196
to_process = tree_conflicts
189
204
except NotImplementedError:
190
205
new_conflicts.append(conflict)
207
nb_conflicts_after = len(new_conflicts)
192
208
tree.set_conflicts(new_conflicts)
193
209
except errors.UnsupportedOperation:
211
if nb_conflicts_after is None:
212
nb_conflicts_after = nb_conflicts_before
213
return nb_conflicts_before, nb_conflicts_after
199
216
def restore(filename):
206
223
osutils.rename(filename + ".THIS", filename)
207
224
conflicted = True
209
226
if e.errno != errno.ENOENT:
212
229
os.unlink(filename + ".BASE")
213
230
conflicted = True
215
232
if e.errno != errno.ENOENT:
218
235
os.unlink(filename + ".OTHER")
219
236
conflicted = True
221
238
if e.errno != errno.ENOENT:
223
240
if not conflicted:
336
353
if ignore_misses is not True:
337
354
for path in [p for p in paths if p not in selected_paths]:
338
355
if not os.path.exists(tree.abspath(path)):
339
print "%s does not exist" % path
356
print("%s does not exist" % path)
341
print "%s is not conflicted" % path
358
print("%s is not conflicted" % path)
342
359
return new_conflicts, selected_conflicts
351
368
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)
370
# the factory blindly transfers the Stanza values to __init__ and
371
# Stanza is purely a Unicode api.
372
if isinstance(file_id, text_type):
373
file_id = cache_utf8.encode(file_id)
374
self.file_id = osutils.safe_file_id(file_id)
357
376
def as_stanza(self):
358
377
s = rio.Stanza(type=self.typestring, path=self.path)
367
386
def __cmp__(self, other):
368
387
if getattr(other, "_cmp_list", None) is None:
370
return cmp(self._cmp_list(), other._cmp_list())
390
y = other._cmp_list()
391
return (x > y) - (x < y)
372
393
def __hash__(self):
373
394
return hash((type(self), self.path, self.file_id))
494
521
raise AssertionError('bad winner: %r' % (winner,))
495
522
if path_to_create is not None:
496
523
tid = tt.trans_id_tree_path(path_to_create)
524
tree = self._revision_tree(tt._tree, revid)
497
525
transform.create_from_tree(
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
526
tt, tid, tree, tree.id2path(file_id), file_id=file_id)
500
527
tt.version_file(file_id, tid)
529
tid = tt.trans_id_file_id(file_id)
502
530
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
504
531
parent_tid = tt.get_tree_parent(tid)
505
tt.adjust_path(path, parent_tid, tid)
532
tt.adjust_path(osutils.basename(path), parent_tid, tid)
508
535
def _revision_tree(self, tree, revid):
571
598
:param tt: The TreeTransform where the conflict is resolved.
572
599
:param suffix_to_remove: Either 'THIS' or 'OTHER'
574
The resolution is symmetric, when taking THIS, OTHER is deleted and
601
The resolution is symmetric: when taking THIS, OTHER is deleted and
575
602
item.THIS is renamed into item and vice-versa.
584
611
# never existed or was already deleted (including the case
585
612
# 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)
615
this_path = tt._tree.id2path(self.file_id)
616
except errors.NoSuchId:
617
# The file is not present anymore. This may happen if the user
618
# deleted the file either manually or when resolving a conflict on
619
# the parent. We may raise some exception to indicate that the
620
# conflict doesn't exist anymore and as such doesn't need to be
621
# resolved ? -- vila 20110615
624
this_tid = tt.trans_id_tree_path(this_path)
625
if this_tid is not None:
626
# Rename 'item.suffix_to_remove' (note that if
627
# 'item.suffix_to_remove' has been deleted, this is a no-op)
628
parent_tid = tt.get_tree_parent(this_tid)
629
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
594
632
def action_take_this(self, tree):
595
633
self._resolve_with_cleanups(tree, 'OTHER')
598
636
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
639
# TODO: There should be a base revid attribute to better inform the user about
605
640
# how the conflicts were generated.
606
class TextConflict(PathConflict):
641
class TextConflict(Conflict):
607
642
"""The merge algorithm could not resolve all differences encountered."""
613
648
format = 'Text conflict in %(path)s'
650
rformat = '%(class)s(%(path)r, %(file_id)r)'
615
652
def associated_filenames(self):
616
653
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
655
def _resolve(self, tt, winner_suffix):
656
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
658
:param tt: The TreeTransform where the conflict is resolved.
659
:param winner_suffix: Either 'THIS' or 'OTHER'
661
The resolution is symmetric, when taking THIS, item.THIS is renamed
662
into item and vice-versa. This takes one of the files as a whole
663
ignoring every difference that could have been merged cleanly.
665
# To avoid useless copies, we switch item and item.winner_suffix, only
666
# item will exist after the conflict has been resolved anyway.
667
item_tid = tt.trans_id_file_id(self.file_id)
668
item_parent_tid = tt.get_tree_parent(item_tid)
669
winner_path = self.path + '.' + winner_suffix
670
winner_tid = tt.trans_id_tree_path(winner_path)
671
winner_parent_tid = tt.get_tree_parent(winner_tid)
672
# Switch the paths to preserve the content
673
tt.adjust_path(osutils.basename(self.path),
674
winner_parent_tid, winner_tid)
675
tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
676
# Associate the file_id to the right content
677
tt.unversion_file(item_tid)
678
tt.version_file(self.file_id, winner_tid)
681
def action_take_this(self, tree):
682
self._resolve_with_cleanups(tree, 'THIS')
684
def action_take_other(self, tree):
685
self._resolve_with_cleanups(tree, 'OTHER')
619
688
class HandledConflict(Conflict):
620
689
"""A path problem that has been provisionally resolved.
652
721
conflict_file_id=None):
653
722
HandledConflict.__init__(self, action, path, file_id)
654
723
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,
724
# the factory blindly transfers the Stanza values to __init__,
725
# so they can be unicode.
726
if isinstance(conflict_file_id, text_type):
727
conflict_file_id = cache_utf8.encode(conflict_file_id)
728
self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
660
730
def _cmp_list(self):
661
731
return HandledConflict._cmp_list(self) + [self.conflict_path,
709
779
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
711
781
def action_take_this(self, tree):
712
# just acccept bzr proposal
782
# just acccept brz proposal
715
785
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
786
tt = transform.TreeTransform(tree)
722
788
p_tid = tt.trans_id_file_id(self.file_id)
723
789
parent_tid = tt.get_tree_parent(p_tid)
724
790
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
791
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)
792
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
793
tt.adjust_path(osutils.basename(self.conflict_path),
785
852
# MissingParent from the *user* pov.
787
854
def action_take_this(self, tree):
788
# just acccept bzr proposal
855
# just acccept brz proposal
791
858
def action_take_other(self, tree):