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
53
Merge will do its best to combine the changes in two branches, but there
51
54
are some kinds of problems only a human can fix. When it encounters those,
52
55
it will mark a conflict. A conflict means that you need to fix something,
53
before you should commit.
56
before you can commit.
55
58
Conflicts normally are listed as short, human-readable messages. If --text
56
59
is supplied, the pathnames of files with text conflicts are listed,
57
60
instead. (This is useful for editing all files with text conflicts.)
59
Use bzr resolve when you have fixed a problem.
62
Use brz resolve when you have fixed a problem.
62
66
option.Option('text',
63
67
help='List paths of files with text conflicts.'),
65
69
_see_also = ['resolve', 'conflict-types']
67
def run(self, text=False):
68
wt = workingtree.WorkingTree.open_containing(u'.')[0]
71
def run(self, text=False, directory=u'.'):
72
wt = workingtree.WorkingTree.open_containing(directory)[0]
69
73
for conflict in wt.conflicts():
71
75
if conflict.typestring != 'text conflict':
73
77
self.outf.write(conflict.path + '\n')
75
self.outf.write(str(conflict) + '\n')
79
self.outf.write(unicode(conflict) + '\n')
78
82
resolve_action_registry = registry.Registry()
81
85
resolve_action_registry.register(
82
'done', 'done', 'Marks the conflict as resolved' )
86
'done', 'done', 'Marks the conflict as resolved.')
83
87
resolve_action_registry.register(
84
88
'take-this', 'take_this',
85
'Resolve the conflict preserving the version in the working tree' )
89
'Resolve the conflict preserving the version in the working tree.')
86
90
resolve_action_registry.register(
87
91
'take-other', 'take_other',
88
'Resolve the conflict taking the merged version into account' )
92
'Resolve the conflict taking the merged version into account.')
89
93
resolve_action_registry.default_key = 'done'
91
95
class ResolveActionOption(option.RegistryOption):
103
107
Merge will do its best to combine the changes in two branches, but there
104
108
are some kinds of problems only a human can fix. When it encounters those,
105
109
it will mark a conflict. A conflict means that you need to fix something,
106
before you should commit.
110
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.
112
Once you have fixed a problem, use "brz resolve" to automatically mark
113
text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
114
resolved, or "brz resolve --all" to mark all conflicts as resolved.
112
116
aliases = ['resolved']
113
117
takes_args = ['file*']
114
118
takes_options = [
115
120
option.Option('all', help='Resolve all conflicts in this tree.'),
116
121
ResolveActionOption(),
118
123
_see_also = ['conflicts']
119
def run(self, file_list=None, all=False, action=None):
124
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]
127
raise errors.BzrCommandError(gettext("If --all is specified,"
128
" no FILE may be provided"))
129
if directory is None:
131
tree = workingtree.WorkingTree.open_containing(directory)[0]
125
132
if action is None:
128
tree, file_list = builtins.tree_files(file_list)
135
tree, file_list = workingtree.WorkingTree.open_containing_paths(
136
file_list, directory)
129
137
if file_list is None:
130
138
if action is None:
131
139
# FIXME: There is a special case here related to the option
141
149
if file_list is None:
142
150
un_resolved, resolved = tree.auto_resolve()
143
151
if len(un_resolved) > 0:
144
trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
trace.note('Remaining conflicts:')
152
trace.note(ngettext('%d conflict auto-resolved.',
153
'%d conflicts auto-resolved.', len(resolved)),
155
trace.note(gettext('Remaining conflicts:'))
146
156
for conflict in un_resolved:
157
trace.note(unicode(conflict))
150
trace.note('All conflicts resolved.')
160
trace.note(gettext('All conflicts resolved.'))
153
163
# FIXME: This can never occur but the block above needs some
155
165
# conflict.auto(tree) --vila 091242
158
resolve(tree, file_list, action=action)
168
before, after = resolve(tree, file_list, action=action)
169
trace.note(ngettext('{0} conflict resolved, {1} remaining',
170
'{0} conflicts resolved, {1} remaining',
171
before-after).format(before - after, after))
161
174
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
174
187
:param action: How the conflict should be resolved,
176
189
tree.lock_tree_write()
190
nb_conflicts_after = None
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:
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, unicode):
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)
495
516
if path_to_create is not None:
496
517
tid = tt.trans_id_tree_path(path_to_create)
497
518
transform.create_from_tree(
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
519
tt, tid, self._revision_tree(tt._tree, revid), file_id)
500
520
tt.version_file(file_id, tid)
522
tid = tt.trans_id_file_id(file_id)
502
523
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
504
524
parent_tid = tt.get_tree_parent(tid)
505
tt.adjust_path(path, parent_tid, tid)
525
tt.adjust_path(osutils.basename(path), parent_tid, tid)
508
528
def _revision_tree(self, tree, revid):
571
591
:param tt: The TreeTransform where the conflict is resolved.
572
592
:param suffix_to_remove: Either 'THIS' or 'OTHER'
574
The resolution is symmetric, when taking THIS, OTHER is deleted and
594
The resolution is symmetric: when taking THIS, OTHER is deleted and
575
595
item.THIS is renamed into item and vice-versa.
584
604
# never existed or was already deleted (including the case
585
605
# 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)
608
this_path = tt._tree.id2path(self.file_id)
609
except errors.NoSuchId:
610
# The file is not present anymore. This may happen if the user
611
# deleted the file either manually or when resolving a conflict on
612
# the parent. We may raise some exception to indicate that the
613
# conflict doesn't exist anymore and as such doesn't need to be
614
# resolved ? -- vila 20110615
617
this_tid = tt.trans_id_tree_path(this_path)
618
if this_tid is not None:
619
# Rename 'item.suffix_to_remove' (note that if
620
# 'item.suffix_to_remove' has been deleted, this is a no-op)
621
parent_tid = tt.get_tree_parent(this_tid)
622
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
594
625
def action_take_this(self, tree):
595
626
self._resolve_with_cleanups(tree, 'OTHER')
598
629
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
632
# TODO: There should be a base revid attribute to better inform the user about
605
633
# how the conflicts were generated.
606
class TextConflict(PathConflict):
634
class TextConflict(Conflict):
607
635
"""The merge algorithm could not resolve all differences encountered."""
613
641
format = 'Text conflict in %(path)s'
643
rformat = '%(class)s(%(path)r, %(file_id)r)'
615
645
def associated_filenames(self):
616
646
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
648
def _resolve(self, tt, winner_suffix):
649
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
651
:param tt: The TreeTransform where the conflict is resolved.
652
:param winner_suffix: Either 'THIS' or 'OTHER'
654
The resolution is symmetric, when taking THIS, item.THIS is renamed
655
into item and vice-versa. This takes one of the files as a whole
656
ignoring every difference that could have been merged cleanly.
658
# To avoid useless copies, we switch item and item.winner_suffix, only
659
# item will exist after the conflict has been resolved anyway.
660
item_tid = tt.trans_id_file_id(self.file_id)
661
item_parent_tid = tt.get_tree_parent(item_tid)
662
winner_path = self.path + '.' + winner_suffix
663
winner_tid = tt.trans_id_tree_path(winner_path)
664
winner_parent_tid = tt.get_tree_parent(winner_tid)
665
# Switch the paths to preserve the content
666
tt.adjust_path(osutils.basename(self.path),
667
winner_parent_tid, winner_tid)
668
tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
669
# Associate the file_id to the right content
670
tt.unversion_file(item_tid)
671
tt.version_file(self.file_id, winner_tid)
674
def action_take_this(self, tree):
675
self._resolve_with_cleanups(tree, 'THIS')
677
def action_take_other(self, tree):
678
self._resolve_with_cleanups(tree, 'OTHER')
619
681
class HandledConflict(Conflict):
620
682
"""A path problem that has been provisionally resolved.
652
714
conflict_file_id=None):
653
715
HandledConflict.__init__(self, action, path, file_id)
654
716
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,
717
# the factory blindly transfers the Stanza values to __init__,
718
# so they can be unicode.
719
if isinstance(conflict_file_id, unicode):
720
conflict_file_id = cache_utf8.encode(conflict_file_id)
721
self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
660
723
def _cmp_list(self):
661
724
return HandledConflict._cmp_list(self) + [self.conflict_path,
709
772
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
711
774
def action_take_this(self, tree):
712
# just acccept bzr proposal
775
# just acccept brz proposal
715
778
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
779
tt = transform.TreeTransform(tree)
722
781
p_tid = tt.trans_id_file_id(self.file_id)
723
782
parent_tid = tt.get_tree_parent(p_tid)
724
783
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
784
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)
785
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
786
tt.adjust_path(osutils.basename(self.conflict_path),
785
845
# MissingParent from the *user* pov.
787
847
def action_take_this(self, tree):
788
# just acccept bzr proposal
848
# just acccept brz proposal
791
851
def action_take_other(self, tree):