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
518
raise AssertionError('bad winner: %r' % (winner,))
495
519
if path_to_create is not None:
496
520
tid = tt.trans_id_tree_path(path_to_create)
521
tree = self._revision_tree(tt._tree, revid)
497
522
transform.create_from_tree(
498
tt, tt.trans_id_tree_path(path_to_create),
499
self._revision_tree(tt._tree, revid), file_id)
523
tt, tid, tree, tree.id2path(file_id), file_id=file_id)
500
524
tt.version_file(file_id, tid)
526
tid = tt.trans_id_file_id(file_id)
502
527
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
504
528
parent_tid = tt.get_tree_parent(tid)
505
tt.adjust_path(path, parent_tid, tid)
529
tt.adjust_path(osutils.basename(path), parent_tid, tid)
508
532
def _revision_tree(self, tree, revid):
571
595
:param tt: The TreeTransform where the conflict is resolved.
572
596
:param suffix_to_remove: Either 'THIS' or 'OTHER'
574
The resolution is symmetric, when taking THIS, OTHER is deleted and
598
The resolution is symmetric: when taking THIS, OTHER is deleted and
575
599
item.THIS is renamed into item and vice-versa.
584
608
# never existed or was already deleted (including the case
585
609
# 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)
612
this_path = tt._tree.id2path(self.file_id)
613
except errors.NoSuchId:
614
# The file is not present anymore. This may happen if the user
615
# deleted the file either manually or when resolving a conflict on
616
# the parent. We may raise some exception to indicate that the
617
# conflict doesn't exist anymore and as such doesn't need to be
618
# resolved ? -- vila 20110615
621
this_tid = tt.trans_id_tree_path(this_path)
622
if this_tid is not None:
623
# Rename 'item.suffix_to_remove' (note that if
624
# 'item.suffix_to_remove' has been deleted, this is a no-op)
625
parent_tid = tt.get_tree_parent(this_tid)
626
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
594
629
def action_take_this(self, tree):
595
630
self._resolve_with_cleanups(tree, 'OTHER')
598
633
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
636
# TODO: There should be a base revid attribute to better inform the user about
605
637
# how the conflicts were generated.
606
class TextConflict(PathConflict):
638
class TextConflict(Conflict):
607
639
"""The merge algorithm could not resolve all differences encountered."""
613
645
format = 'Text conflict in %(path)s'
647
rformat = '%(class)s(%(path)r, %(file_id)r)'
615
649
def associated_filenames(self):
616
650
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
652
def _resolve(self, tt, winner_suffix):
653
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
655
:param tt: The TreeTransform where the conflict is resolved.
656
:param winner_suffix: Either 'THIS' or 'OTHER'
658
The resolution is symmetric, when taking THIS, item.THIS is renamed
659
into item and vice-versa. This takes one of the files as a whole
660
ignoring every difference that could have been merged cleanly.
662
# To avoid useless copies, we switch item and item.winner_suffix, only
663
# item will exist after the conflict has been resolved anyway.
664
item_tid = tt.trans_id_file_id(self.file_id)
665
item_parent_tid = tt.get_tree_parent(item_tid)
666
winner_path = self.path + '.' + winner_suffix
667
winner_tid = tt.trans_id_tree_path(winner_path)
668
winner_parent_tid = tt.get_tree_parent(winner_tid)
669
# Switch the paths to preserve the content
670
tt.adjust_path(osutils.basename(self.path),
671
winner_parent_tid, winner_tid)
672
tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
673
# Associate the file_id to the right content
674
tt.unversion_file(item_tid)
675
tt.version_file(self.file_id, winner_tid)
678
def action_take_this(self, tree):
679
self._resolve_with_cleanups(tree, 'THIS')
681
def action_take_other(self, tree):
682
self._resolve_with_cleanups(tree, 'OTHER')
619
685
class HandledConflict(Conflict):
620
686
"""A path problem that has been provisionally resolved.
652
718
conflict_file_id=None):
653
719
HandledConflict.__init__(self, action, path, file_id)
654
720
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,
721
# the factory blindly transfers the Stanza values to __init__,
722
# so they can be unicode.
723
if isinstance(conflict_file_id, text_type):
724
conflict_file_id = cache_utf8.encode(conflict_file_id)
725
self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
660
727
def _cmp_list(self):
661
728
return HandledConflict._cmp_list(self) + [self.conflict_path,
709
776
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
711
778
def action_take_this(self, tree):
712
# just acccept bzr proposal
779
# just acccept brz proposal
715
782
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
783
tt = transform.TreeTransform(tree)
722
785
p_tid = tt.trans_id_file_id(self.file_id)
723
786
parent_tid = tt.get_tree_parent(p_tid)
724
787
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
788
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)
789
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
790
tt.adjust_path(osutils.basename(self.conflict_path),
785
849
# MissingParent from the *user* pov.
787
851
def action_take_this(self, tree):
788
# just acccept bzr proposal
852
# just acccept brz proposal
791
855
def action_take_other(self, tree):