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.
67
62
option.Option('text',
68
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'
96
91
class ResolveActionOption(option.RegistryOption):
108
103
Merge will do its best to combine the changes in two branches, but there
109
104
are some kinds of problems only a human can fix. When it encounters those,
110
105
it will mark a conflict. A conflict means that you need to fix something,
111
before you can commit.
106
before you should commit.
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.
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.
117
112
aliases = ['resolved']
118
113
takes_args = ['file*']
119
114
takes_options = [
121
115
option.Option('all', help='Resolve all conflicts in this tree.'),
122
116
ResolveActionOption(),
124
118
_see_also = ['conflicts']
125
def run(self, file_list=None, all=False, action=None, directory=None):
119
def run(self, file_list=None, all=False, action=None):
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]
122
raise errors.BzrCommandError("If --all is specified,"
123
" no FILE may be provided")
124
tree = workingtree.WorkingTree.open_containing('.')[0]
133
125
if action is None:
136
tree, file_list = workingtree.WorkingTree.open_containing_paths(
137
file_list, directory)
128
tree, file_list = builtins.tree_files(file_list)
138
129
if file_list is None:
139
130
if action is None:
140
131
# FIXME: There is a special case here related to the option
150
141
if file_list is None:
151
142
un_resolved, resolved = tree.auto_resolve()
152
143
if len(un_resolved) > 0:
153
trace.note(ngettext('%d conflict auto-resolved.',
154
'%d conflicts auto-resolved.', len(resolved)),
156
trace.note(gettext('Remaining conflicts:'))
144
trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
trace.note('Remaining conflicts:')
157
146
for conflict in un_resolved:
158
trace.note(text_type(conflict))
161
trace.note(gettext('All conflicts resolved.'))
150
trace.note('All conflicts resolved.')
164
153
# FIXME: This can never occur but the block above needs some
166
155
# conflict.auto(tree) --vila 091242
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))
158
resolve(tree, file_list, action=action)
175
161
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
187
173
paths do not have conflicts.
188
174
:param action: How the conflict should be resolved,
190
nb_conflicts_after = None
191
with tree.lock_tree_write():
176
tree.lock_tree_write()
192
178
tree_conflicts = tree.conflicts()
193
nb_conflicts_before = len(tree_conflicts)
194
179
if paths is None:
195
180
new_conflicts = ConflictList()
196
181
to_process = tree_conflicts
204
189
except NotImplementedError:
205
190
new_conflicts.append(conflict)
207
nb_conflicts_after = len(new_conflicts)
208
192
tree.set_conflicts(new_conflicts)
209
193
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
216
199
def restore(filename):
223
206
osutils.rename(filename + ".THIS", filename)
224
207
conflicted = True
226
209
if e.errno != errno.ENOENT:
229
212
os.unlink(filename + ".BASE")
230
213
conflicted = True
232
215
if e.errno != errno.ENOENT:
235
218
os.unlink(filename + ".OTHER")
236
219
conflicted = True
238
221
if e.errno != errno.ENOENT:
240
223
if not conflicted:
353
336
if ignore_misses is not True:
354
337
for path in [p for p in paths if p not in selected_paths]:
355
338
if not os.path.exists(tree.abspath(path)):
356
print("%s does not exist" % path)
339
print "%s does not exist" % path
358
print("%s is not conflicted" % path)
341
print "%s is not conflicted" % path
359
342
return new_conflicts, selected_conflicts
368
351
def __init__(self, path, file_id=None):
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)
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)
376
357
def as_stanza(self):
377
358
s = rio.Stanza(type=self.typestring, path=self.path)
386
367
def __cmp__(self, other):
387
368
if getattr(other, "_cmp_list", None) is None:
390
y = other._cmp_list()
391
return (x > y) - (x < y)
370
return cmp(self._cmp_list(), other._cmp_list())
393
372
def __hash__(self):
394
373
return hash((type(self), self.path, self.file_id))
521
494
raise AssertionError('bad winner: %r' % (winner,))
522
495
if path_to_create is not None:
523
496
tid = tt.trans_id_tree_path(path_to_create)
524
tree = self._revision_tree(tt._tree, revid)
525
497
transform.create_from_tree(
526
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)
527
500
tt.version_file(file_id, tid)
529
tid = tt.trans_id_file_id(file_id)
530
502
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
531
504
parent_tid = tt.get_tree_parent(tid)
532
tt.adjust_path(osutils.basename(path), parent_tid, tid)
505
tt.adjust_path(path, parent_tid, tid)
535
508
def _revision_tree(self, tree, revid):
598
571
:param tt: The TreeTransform where the conflict is resolved.
599
572
:param suffix_to_remove: Either 'THIS' or 'OTHER'
601
The resolution is symmetric: when taking THIS, OTHER is deleted and
574
The resolution is symmetric, when taking THIS, OTHER is deleted and
602
575
item.THIS is renamed into item and vice-versa.
611
584
# never existed or was already deleted (including the case
612
585
# where the user deleted it)
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)
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)
632
594
def action_take_this(self, tree):
633
595
self._resolve_with_cleanups(tree, 'OTHER')
636
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
639
604
# TODO: There should be a base revid attribute to better inform the user about
640
605
# how the conflicts were generated.
641
class TextConflict(Conflict):
606
class TextConflict(PathConflict):
642
607
"""The merge algorithm could not resolve all differences encountered."""
648
613
format = 'Text conflict in %(path)s'
650
rformat = '%(class)s(%(path)r, %(file_id)r)'
652
615
def associated_filenames(self):
653
616
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')
688
619
class HandledConflict(Conflict):
689
620
"""A path problem that has been provisionally resolved.
721
652
conflict_file_id=None):
722
653
HandledConflict.__init__(self, action, path, file_id)
723
654
self.conflict_path = conflict_path
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)
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,
730
660
def _cmp_list(self):
731
661
return HandledConflict._cmp_list(self) + [self.conflict_path,
779
709
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
781
711
def action_take_this(self, tree):
782
# just acccept brz proposal
712
# just acccept bzr proposal
785
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)
786
720
tt = transform.TreeTransform(tree)
788
722
p_tid = tt.trans_id_file_id(self.file_id)
789
723
parent_tid = tt.get_tree_parent(p_tid)
790
724
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
791
725
cparent_tid = tt.get_tree_parent(cp_tid)
792
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
793
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)
852
785
# MissingParent from the *user* pov.
854
787
def action_take_this(self, tree):
855
# just acccept brz proposal
788
# just acccept bzr proposal
858
791
def action_take_other(self, tree):