1
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011 Canonical Ltd
1
# Copyright (C) 2005 by Aaron Bentley
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: Move this into builtins
19
# TODO: 'bzr resolve' should accept a directory name and work from that
20
from __future__ import absolute_import
25
from .lazy_import import lazy_import
26
lazy_import(globals(), """
36
from breezy.i18n import gettext, ngettext
45
from .sixish import text_type
26
from bzrlib.commands import register_command
27
from bzrlib.errors import BzrCommandError, NotConflicted, UnsupportedOperation
28
from bzrlib.option import Option
29
from bzrlib.osutils import rename, delete_any
30
from bzrlib.rio import Stanza
48
33
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
51
class cmd_conflicts(commands.Command):
52
__doc__ = """List files with conflicts.
36
class cmd_conflicts(bzrlib.commands.Command):
37
"""List files with conflicts.
54
39
Merge will do its best to combine the changes in two branches, but there
55
40
are some kinds of problems only a human can fix. When it encounters those,
56
41
it will mark a conflict. A conflict means that you need to fix something,
57
before you can commit.
59
Conflicts normally are listed as short, human-readable messages. If --text
60
is supplied, the pathnames of files with text conflicts are listed,
61
instead. (This is useful for editing all files with text conflicts.)
63
Use brz resolve when you have fixed a problem.
42
before you should commit.
44
Use bzr resolve when you have fixed a problem.
46
(conflicts are determined by the presence of .BASE .TREE, and .OTHER
68
help='List paths of files with text conflicts.'),
70
_see_also = ['resolve', 'conflict-types']
72
def run(self, text=False, directory=u'.'):
73
wt = workingtree.WorkingTree.open_containing(directory)[0]
52
from bzrlib.workingtree import WorkingTree
53
wt = WorkingTree.open_containing(u'.')[0]
74
54
for conflict in wt.conflicts():
76
if conflict.typestring != 'text conflict':
78
self.outf.write(conflict.path + '\n')
80
self.outf.write(text_type(conflict) + '\n')
83
resolve_action_registry = registry.Registry()
86
resolve_action_registry.register(
87
'auto', 'auto', 'Detect whether conflict has been resolved by user.')
88
resolve_action_registry.register(
89
'done', 'done', 'Marks the conflict as resolved.')
90
resolve_action_registry.register(
91
'take-this', 'take_this',
92
'Resolve the conflict preserving the version in the working tree.')
93
resolve_action_registry.register(
94
'take-other', 'take_other',
95
'Resolve the conflict taking the merged version into account.')
96
resolve_action_registry.default_key = 'done'
99
class ResolveActionOption(option.RegistryOption):
102
super(ResolveActionOption, self).__init__(
103
'action', 'How to resolve the conflict.',
105
registry=resolve_action_registry)
108
class cmd_resolve(commands.Command):
109
__doc__ = """Mark a conflict as resolved.
58
class cmd_resolve(bzrlib.commands.Command):
59
"""Mark a conflict as resolved.
111
61
Merge will do its best to combine the changes in two branches, but there
112
62
are some kinds of problems only a human can fix. When it encounters those,
113
63
it will mark a conflict. A conflict means that you need to fix something,
114
before you can commit.
116
Once you have fixed a problem, use "brz resolve" to automatically mark
117
text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
118
resolved, or "brz resolve --all" to mark all conflicts as resolved.
64
before you should commit.
66
Once you have fixed a problem, use "bzr resolve FILE.." to mark
67
individual files as fixed, or "bzr resolve --all" to mark all conflicts as
70
See also bzr conflicts.
120
72
aliases = ['resolved']
121
73
takes_args = ['file*']
124
option.Option('all', help='Resolve all conflicts in this tree.'),
125
ResolveActionOption(),
127
_see_also = ['conflicts']
129
def run(self, file_list=None, all=False, action=None, directory=None):
74
takes_options = [Option('all', help='Resolve all conflicts in this tree')]
75
def run(self, file_list=None, all=False):
76
from bzrlib.workingtree import WorkingTree
132
raise errors.BzrCommandError(gettext("If --all is specified,"
133
" no FILE may be provided"))
134
if directory is None:
136
tree = workingtree.WorkingTree.open_containing(directory)[0]
140
tree, file_list = workingtree.WorkingTree.open_containing_paths(
141
file_list, directory)
143
if file_list is None:
147
before, after = resolve(tree, file_list, action=action)
148
# GZ 2012-07-27: Should unify UI below now that auto is less magical.
149
if action == 'auto' and file_list is None:
152
ngettext('%d conflict auto-resolved.',
153
'%d conflicts auto-resolved.', before - after),
155
trace.note(gettext('Remaining conflicts:'))
156
for conflict in tree.conflicts():
157
trace.note(text_type(conflict))
160
trace.note(gettext('All conflicts resolved.'))
163
trace.note(ngettext('{0} conflict resolved, {1} remaining',
164
'{0} conflicts resolved, {1} remaining',
165
before - after).format(before - after, after))
168
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
170
"""Resolve some or all of the conflicts in a working tree.
172
:param paths: If None, resolve all conflicts. Otherwise, select only
174
:param recursive: If True, then elements of paths which are directories
175
have all their children resolved, etc. When invoked as part of
176
recursive commands like revert, this should be True. For commands
177
or applications wishing finer-grained control, like the resolve
178
command, this should be False.
179
:param ignore_misses: If False, warnings will be printed if the supplied
180
paths do not have conflicts.
181
:param action: How the conflict should be resolved,
183
nb_conflicts_after = None
184
with tree.lock_tree_write():
79
raise BzrCommandError("If --all is specified, no FILE may be provided")
80
tree = WorkingTree.open_containing('.')[0]
84
raise BzrCommandError("command 'resolve' needs one or more FILE, or --all")
85
tree = WorkingTree.open_containing(file_list[0])[0]
86
to_resolve = [tree.relpath(p) for p in file_list]
87
resolve(tree, to_resolve)
90
def resolve(tree, paths=None, ignore_misses=False):
185
93
tree_conflicts = tree.conflicts()
186
nb_conflicts_before = len(tree_conflicts)
188
95
new_conflicts = ConflictList()
189
to_process = tree_conflicts
96
selected_conflicts = tree_conflicts
191
new_conflicts, to_process = tree_conflicts.select_conflicts(
192
tree, paths, ignore_misses, recursive)
193
for conflict in to_process:
195
conflict._do(action, tree)
196
conflict.cleanup(tree)
197
except NotImplementedError:
198
new_conflicts.append(conflict)
98
new_conflicts, selected_conflicts = \
99
tree_conflicts.select_conflicts(tree, paths, ignore_misses)
200
nb_conflicts_after = len(new_conflicts)
201
101
tree.set_conflicts(new_conflicts)
202
except errors.UnsupportedOperation:
102
except UnsupportedOperation:
204
if nb_conflicts_after is None:
205
nb_conflicts_after = nb_conflicts_before
206
return nb_conflicts_before, nb_conflicts_after
104
selected_conflicts.remove_files(tree)
209
109
def restore(filename):
210
"""Restore a conflicted file to the state it was in before merging.
212
Only text restoration is supported at present.
111
Restore a conflicted file to the state it was in before merging.
112
Only text restoration supported at present.
214
114
conflicted = False
216
osutils.rename(filename + ".THIS", filename)
116
rename(filename + ".THIS", filename)
217
117
conflicted = True
219
119
if e.errno != errno.ENOENT:
222
122
os.unlink(filename + ".BASE")
223
123
conflicted = True
225
125
if e.errno != errno.ENOENT:
228
128
os.unlink(filename + ".OTHER")
229
129
conflicted = True
231
131
if e.errno != errno.ENOENT:
233
133
if not conflicted:
234
raise errors.NotConflicted(filename)
134
raise NotConflicted(filename)
237
137
class ConflictList(object):
482
320
s.add('conflict_path', self.conflict_path)
485
def associated_filenames(self):
486
# No additional files have been generated here
489
def _resolve(self, tt, file_id, path, winner):
490
"""Resolve the conflict.
492
:param tt: The TreeTransform where the conflict is resolved.
493
:param file_id: The retained file id.
494
:param path: The retained path.
495
:param winner: 'this' or 'other' indicates which side is the winner.
497
path_to_create = None
499
if self.path == '<deleted>':
500
return # Nothing to do
501
if self.conflict_path == '<deleted>':
502
path_to_create = self.path
503
revid = tt._tree.get_parent_ids()[0]
504
elif winner == 'other':
505
if self.conflict_path == '<deleted>':
506
return # Nothing to do
507
if self.path == '<deleted>':
508
path_to_create = self.conflict_path
509
# FIXME: If there are more than two parents we may need to
510
# iterate. Taking the last parent is the safer bet in the mean
511
# time. -- vila 20100309
512
revid = tt._tree.get_parent_ids()[-1]
515
raise AssertionError('bad winner: %r' % (winner,))
516
if path_to_create is not None:
517
tid = tt.trans_id_tree_path(path_to_create)
518
tree = self._revision_tree(tt._tree, revid)
519
transform.create_from_tree(
520
tt, tid, tree, tree.id2path(file_id))
521
tt.version_file(file_id, tid)
523
tid = tt.trans_id_file_id(file_id)
524
# Adjust the path for the retained file id
525
parent_tid = tt.get_tree_parent(tid)
526
tt.adjust_path(osutils.basename(path), parent_tid, tid)
529
def _revision_tree(self, tree, revid):
530
return tree.branch.repository.revision_tree(revid)
532
def _infer_file_id(self, tree):
533
# Prior to bug #531967, file_id wasn't always set, there may still be
534
# conflict files in the wild so we need to cope with them
535
# Establish which path we should use to find back the file-id
537
for p in (self.path, self.conflict_path):
539
# special hard-coded path
542
possible_paths.append(p)
543
# Search the file-id in the parents with any path available
545
for revid in tree.get_parent_ids():
546
revtree = self._revision_tree(tree, revid)
547
for p in possible_paths:
548
file_id = revtree.path2id(p)
549
if file_id is not None:
550
return revtree, file_id
553
def action_take_this(self, tree):
554
if self.file_id is not None:
555
self._resolve_with_cleanups(tree, self.file_id, self.path,
558
# Prior to bug #531967 we need to find back the file_id and restore
559
# the content from there
560
revtree, file_id = self._infer_file_id(tree)
561
tree.revert([revtree.id2path(file_id)],
562
old_tree=revtree, backups=False)
564
def action_take_other(self, tree):
565
if self.file_id is not None:
566
self._resolve_with_cleanups(tree, self.file_id,
570
# Prior to bug #531967 we need to find back the file_id and restore
571
# the content from there
572
revtree, file_id = self._infer_file_id(tree)
573
tree.revert([revtree.id2path(file_id)],
574
old_tree=revtree, backups=False)
577
324
class ContentsConflict(PathConflict):
578
"""The files are of different types (or both binary), or not present"""
325
"""The files are of different types, or not present"""
584
331
format = 'Contents conflict in %(path)s'
586
def associated_filenames(self):
587
return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
589
def _resolve(self, tt, suffix_to_remove):
590
"""Resolve the conflict.
592
:param tt: The TreeTransform where the conflict is resolved.
593
:param suffix_to_remove: Either 'THIS' or 'OTHER'
595
The resolution is symmetric: when taking THIS, OTHER is deleted and
596
item.THIS is renamed into item and vice-versa.
599
# Delete 'item.THIS' or 'item.OTHER' depending on
602
tt.trans_id_tree_path(self.path + '.' + suffix_to_remove))
603
except errors.NoSuchFile:
604
# There are valid cases where 'item.suffix_to_remove' either
605
# never existed or was already deleted (including the case
606
# where the user deleted it)
609
this_path = tt._tree.id2path(self.file_id)
610
except errors.NoSuchId:
611
# The file is not present anymore. This may happen if the user
612
# deleted the file either manually or when resolving a conflict on
613
# the parent. We may raise some exception to indicate that the
614
# conflict doesn't exist anymore and as such doesn't need to be
615
# resolved ? -- vila 20110615
618
this_tid = tt.trans_id_tree_path(this_path)
619
if this_tid is not None:
620
# Rename 'item.suffix_to_remove' (note that if
621
# 'item.suffix_to_remove' has been deleted, this is a no-op)
622
parent_tid = tt.get_tree_parent(this_tid)
623
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
626
def action_take_this(self, tree):
627
self._resolve_with_cleanups(tree, 'OTHER')
629
def action_take_other(self, tree):
630
self._resolve_with_cleanups(tree, 'THIS')
633
# TODO: There should be a base revid attribute to better inform the user about
634
# how the conflicts were generated.
635
class TextConflict(Conflict):
334
class TextConflict(PathConflict):
636
335
"""The merge algorithm could not resolve all differences encountered."""
642
341
format = 'Text conflict in %(path)s'
644
rformat = '%(class)s(%(path)r, %(file_id)r)'
646
_conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
648
def associated_filenames(self):
649
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
651
def _resolve(self, tt, winner_suffix):
652
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
654
:param tt: The TreeTransform where the conflict is resolved.
655
:param winner_suffix: Either 'THIS' or 'OTHER'
657
The resolution is symmetric, when taking THIS, item.THIS is renamed
658
into item and vice-versa. This takes one of the files as a whole
659
ignoring every difference that could have been merged cleanly.
661
# To avoid useless copies, we switch item and item.winner_suffix, only
662
# item will exist after the conflict has been resolved anyway.
663
item_tid = tt.trans_id_file_id(self.file_id)
664
item_parent_tid = tt.get_tree_parent(item_tid)
665
winner_path = self.path + '.' + winner_suffix
666
winner_tid = tt.trans_id_tree_path(winner_path)
667
winner_parent_tid = tt.get_tree_parent(winner_tid)
668
# Switch the paths to preserve the content
669
tt.adjust_path(osutils.basename(self.path),
670
winner_parent_tid, winner_tid)
671
tt.adjust_path(osutils.basename(winner_path),
672
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_auto(self, tree):
679
# GZ 2012-07-27: Using NotImplementedError to signal that a conflict
680
# can't be auto resolved does not seem ideal.
682
kind = tree.kind(self.path)
683
except errors.NoSuchFile:
686
raise NotImplementedError("Conflict is not a file")
687
conflict_markers_in_line = self._conflict_re.search
688
# GZ 2012-07-27: What if not tree.has_id(self.file_id) due to removal?
689
with tree.get_file(self.path) as f:
691
if conflict_markers_in_line(line):
692
raise NotImplementedError("Conflict markers present")
694
def action_take_this(self, tree):
695
self._resolve_with_cleanups(tree, 'THIS')
697
def action_take_other(self, tree):
698
self._resolve_with_cleanups(tree, 'OTHER')
701
344
class HandledConflict(Conflict):
702
345
"""A path problem that has been provisionally resolved.
769
404
format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
771
def action_take_this(self, tree):
772
tree.remove([self.conflict_path], force=True, keep_files=False)
773
tree.rename_one(self.path, self.conflict_path)
775
def action_take_other(self, tree):
776
tree.remove([self.path], force=True, keep_files=False)
779
407
class ParentLoop(HandledPathConflict):
780
408
"""An attempt to create an infinitely-looping directory structure.
781
409
This is rare, but can be produced like so:
790
418
typestring = 'parent loop'
792
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
794
def action_take_this(self, tree):
795
# just acccept brz proposal
798
def action_take_other(self, tree):
799
with tree.get_transform() as tt:
800
p_tid = tt.trans_id_file_id(self.file_id)
801
parent_tid = tt.get_tree_parent(p_tid)
802
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
803
cparent_tid = tt.get_tree_parent(cp_tid)
804
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
805
tt.adjust_path(osutils.basename(self.conflict_path),
420
format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
810
423
class UnversionedParent(HandledConflict):
811
"""An attempt to version a file whose parent directory is not versioned.
424
"""An attempt to version an file whose parent directory is not versioned.
812
425
Typically, the result of a merge where one tree unversioned the directory
813
426
and the other added a versioned file to it.
816
429
typestring = 'unversioned parent'
818
format = 'Conflict because %(path)s is not versioned, but has versioned'\
819
' children. %(action)s.'
821
# FIXME: We silently do nothing to make tests pass, but most probably the
822
# conflict shouldn't exist (the long story is that the conflict is
823
# generated with another one that can be resolved properly) -- vila 091224
824
def action_take_this(self, tree):
827
def action_take_other(self, tree):
431
format = 'Conflict adding versioned files to %(path)s. %(action)s.'
831
434
class MissingParent(HandledConflict):
832
435
"""An attempt to add files to a directory that is not present.
833
Typically, the result of a merge where THIS deleted the directory and
834
the OTHER added a file to it.
835
See also: DeletingParent (same situation, THIS and OTHER reversed)
436
Typically, the result of a merge where one tree deleted the directory and
437
the other added a file to it.
838
440
typestring = 'missing parent'
840
442
format = 'Conflict adding files to %(path)s. %(action)s.'
842
def action_take_this(self, tree):
843
tree.remove([self.path], force=True, keep_files=False)
845
def action_take_other(self, tree):
846
# just acccept brz proposal
850
class DeletingParent(HandledConflict):
851
"""An attempt to add files to a directory that is not present.
852
Typically, the result of a merge where one OTHER deleted the directory and
853
the THIS added a file to it.
856
typestring = 'deleting parent'
858
format = "Conflict: can't delete %(path)s because it is not empty. "\
861
# FIXME: It's a bit strange that the default action is not coherent with
862
# MissingParent from the *user* pov.
864
def action_take_this(self, tree):
865
# just acccept brz proposal
868
def action_take_other(self, tree):
869
tree.remove([self.path], force=True, keep_files=False)
872
class NonDirectoryParent(HandledConflict):
873
"""An attempt to add files to a directory that is not a directory or
874
an attempt to change the kind of a directory with files.
877
typestring = 'non-directory parent'
879
format = "Conflict: %(path)s is not a directory, but has files in it."\
882
# FIXME: .OTHER should be used instead of .new when the conflict is created
884
def action_take_this(self, tree):
885
# FIXME: we should preserve that path when the conflict is generated !
886
if self.path.endswith('.new'):
887
conflict_path = self.path[:-(len('.new'))]
888
tree.remove([self.path], force=True, keep_files=False)
889
tree.add(conflict_path)
891
raise NotImplementedError(self.action_take_this)
893
def action_take_other(self, tree):
894
# FIXME: we should preserve that path when the conflict is generated !
895
if self.path.endswith('.new'):
896
conflict_path = self.path[:-(len('.new'))]
897
tree.remove([conflict_path], force=True, keep_files=False)
898
tree.rename_one(self.path, conflict_path)
900
raise NotImplementedError(self.action_take_other)