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))
518
494
raise AssertionError('bad winner: %r' % (winner,))
519
495
if path_to_create is not None:
520
496
tid = tt.trans_id_tree_path(path_to_create)
521
tree = self._revision_tree(tt._tree, revid)
522
497
transform.create_from_tree(
523
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)
524
500
tt.version_file(file_id, tid)
526
tid = tt.trans_id_file_id(file_id)
527
502
# Adjust the path for the retained file id
503
tid = tt.trans_id_file_id(file_id)
528
504
parent_tid = tt.get_tree_parent(tid)
529
tt.adjust_path(osutils.basename(path), parent_tid, tid)
505
tt.adjust_path(path, parent_tid, tid)
532
508
def _revision_tree(self, tree, revid):
595
571
:param tt: The TreeTransform where the conflict is resolved.
596
572
:param suffix_to_remove: Either 'THIS' or 'OTHER'
598
The resolution is symmetric: when taking THIS, OTHER is deleted and
574
The resolution is symmetric, when taking THIS, OTHER is deleted and
599
575
item.THIS is renamed into item and vice-versa.
608
584
# never existed or was already deleted (including the case
609
585
# where the user deleted it)
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)
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)
629
594
def action_take_this(self, tree):
630
595
self._resolve_with_cleanups(tree, 'OTHER')
633
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
636
604
# TODO: There should be a base revid attribute to better inform the user about
637
605
# how the conflicts were generated.
638
class TextConflict(Conflict):
606
class TextConflict(PathConflict):
639
607
"""The merge algorithm could not resolve all differences encountered."""
645
613
format = 'Text conflict in %(path)s'
647
rformat = '%(class)s(%(path)r, %(file_id)r)'
649
615
def associated_filenames(self):
650
616
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')
685
619
class HandledConflict(Conflict):
686
620
"""A path problem that has been provisionally resolved.
718
652
conflict_file_id=None):
719
653
HandledConflict.__init__(self, action, path, file_id)
720
654
self.conflict_path = conflict_path
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)
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,
727
660
def _cmp_list(self):
728
661
return HandledConflict._cmp_list(self) + [self.conflict_path,
776
709
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
778
711
def action_take_this(self, tree):
779
# just acccept brz proposal
712
# just acccept bzr proposal
782
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)
783
720
tt = transform.TreeTransform(tree)
785
722
p_tid = tt.trans_id_file_id(self.file_id)
786
723
parent_tid = tt.get_tree_parent(p_tid)
787
724
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
788
725
cparent_tid = tt.get_tree_parent(cp_tid)
789
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
790
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)
849
785
# MissingParent from the *user* pov.
851
787
def action_take_this(self, tree):
852
# just acccept brz proposal
788
# just acccept bzr proposal
855
791
def action_take_other(self, tree):