49
47
class cmd_conflicts(commands.Command):
50
__doc__ = """List files with conflicts.
48
"""List files with conflicts.
52
50
Merge will do its best to combine the changes in two branches, but there
53
51
are some kinds of problems only a human can fix. When it encounters those,
54
52
it will mark a conflict. A conflict means that you need to fix something,
55
before you can commit.
53
before you should commit.
57
55
Conflicts normally are listed as short, human-readable messages. If --text
58
56
is supplied, the pathnames of files with text conflicts are listed,
61
59
Use bzr resolve when you have fixed a problem.
65
62
option.Option('text',
66
63
help='List paths of files with text conflicts.'),
68
65
_see_also = ['resolve', 'conflict-types']
70
def run(self, text=False, directory=u'.'):
71
wt = workingtree.WorkingTree.open_containing(directory)[0]
67
def run(self, text=False):
68
wt = workingtree.WorkingTree.open_containing(u'.')[0]
72
69
for conflict in wt.conflicts():
74
71
if conflict.typestring != 'text conflict':
76
73
self.outf.write(conflict.path + '\n')
78
self.outf.write(unicode(conflict) + '\n')
75
self.outf.write(str(conflict) + '\n')
81
78
resolve_action_registry = registry.Registry()
84
81
resolve_action_registry.register(
85
'done', 'done', 'Marks the conflict as resolved.')
82
'done', 'done', 'Marks the conflict as resolved' )
86
83
resolve_action_registry.register(
87
84
'take-this', 'take_this',
88
'Resolve the conflict preserving the version in the working tree.')
85
'Resolve the conflict preserving the version in the working tree' )
89
86
resolve_action_registry.register(
90
87
'take-other', 'take_other',
91
'Resolve the conflict taking the merged version into account.')
88
'Resolve the conflict taking the merged version into account' )
92
89
resolve_action_registry.default_key = 'done'
94
91
class ResolveActionOption(option.RegistryOption):
103
100
class cmd_resolve(commands.Command):
104
__doc__ = """Mark a conflict as resolved.
101
"""Mark a conflict as resolved.
106
103
Merge will do its best to combine the changes in two branches, but there
107
104
are some kinds of problems only a human can fix. When it encounters those,
108
105
it will mark a conflict. A conflict means that you need to fix something,
109
before you can commit.
106
before you should commit.
111
108
Once you have fixed a problem, use "bzr resolve" to automatically mark
112
109
text conflicts as fixed, "bzr resolve FILE" to mark a specific conflict as
115
112
aliases = ['resolved']
116
113
takes_args = ['file*']
117
114
takes_options = [
119
115
option.Option('all', help='Resolve all conflicts in this tree.'),
120
116
ResolveActionOption(),
122
118
_see_also = ['conflicts']
123
def run(self, file_list=None, all=False, action=None, directory=None):
119
def run(self, file_list=None, all=False, action=None):
126
raise errors.BzrCommandError(gettext("If --all is specified,"
127
" no FILE may be provided"))
128
if directory is None:
130
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]
131
125
if action is None:
134
tree, file_list = workingtree.WorkingTree.open_containing_paths(
135
file_list, directory)
128
tree, file_list = builtins.tree_files(file_list)
136
129
if file_list is None:
137
130
if action is None:
138
131
# FIXME: There is a special case here related to the option
148
141
if file_list is None:
149
142
un_resolved, resolved = tree.auto_resolve()
150
143
if len(un_resolved) > 0:
151
trace.note(ngettext('%d conflict auto-resolved.',
152
'%d conflicts auto-resolved.', len(resolved)),
154
trace.note(gettext('Remaining conflicts:'))
144
trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
trace.note('Remaining conflicts:')
155
146
for conflict in un_resolved:
156
trace.note(unicode(conflict))
159
trace.note(gettext('All conflicts resolved.'))
150
trace.note('All conflicts resolved.')
162
153
# FIXME: This can never occur but the block above needs some
164
155
# conflict.auto(tree) --vila 091242
167
before, after = resolve(tree, file_list, action=action)
168
trace.note(ngettext('{0} conflict resolved, {1} remaining',
169
'{0} conflicts resolved, {1} remaining',
170
before-after).format(before - after, after))
158
resolve(tree, file_list, action=action)
173
161
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
453
435
def action_take_other(self, tree):
454
436
raise NotImplementedError(self.action_take_other)
456
def _resolve_with_cleanups(self, tree, *args, **kwargs):
457
tt = transform.TreeTransform(tree)
458
op = cleanup.OperationWithCleanups(self._resolve)
459
op.add_cleanup(tt.finalize)
460
op.run_simple(tt, *args, **kwargs)
463
439
class PathConflict(Conflict):
464
440
"""A conflict was encountered merging file paths"""
483
459
# No additional files have been generated here
486
def _resolve(self, tt, file_id, path, winner):
487
"""Resolve the conflict.
489
:param tt: The TreeTransform where the conflict is resolved.
490
:param file_id: The retained file id.
491
:param path: The retained path.
492
:param winner: 'this' or 'other' indicates which side is the winner.
494
path_to_create = None
496
if self.path == '<deleted>':
497
return # Nothing to do
498
if self.conflict_path == '<deleted>':
499
path_to_create = self.path
500
revid = tt._tree.get_parent_ids()[0]
501
elif winner == 'other':
502
if self.conflict_path == '<deleted>':
503
return # Nothing to do
504
if self.path == '<deleted>':
505
path_to_create = self.conflict_path
506
# FIXME: If there are more than two parents we may need to
507
# iterate. Taking the last parent is the safer bet in the mean
508
# time. -- vila 20100309
509
revid = tt._tree.get_parent_ids()[-1]
512
raise AssertionError('bad winner: %r' % (winner,))
513
if path_to_create is not None:
514
tid = tt.trans_id_tree_path(path_to_create)
515
transform.create_from_tree(
516
tt, tid, self._revision_tree(tt._tree, revid), file_id)
517
tt.version_file(file_id, tid)
519
tid = tt.trans_id_file_id(file_id)
520
# Adjust the path for the retained file id
521
parent_tid = tt.get_tree_parent(tid)
522
tt.adjust_path(osutils.basename(path), parent_tid, tid)
525
def _revision_tree(self, tree, revid):
526
return tree.branch.repository.revision_tree(revid)
528
def _infer_file_id(self, tree):
529
# Prior to bug #531967, file_id wasn't always set, there may still be
530
# conflict files in the wild so we need to cope with them
531
# Establish which path we should use to find back the file-id
533
for p in (self.path, self.conflict_path):
535
# special hard-coded path
538
possible_paths.append(p)
539
# Search the file-id in the parents with any path available
541
for revid in tree.get_parent_ids():
542
revtree = self._revision_tree(tree, revid)
543
for p in possible_paths:
544
file_id = revtree.path2id(p)
545
if file_id is not None:
546
return revtree, file_id
549
462
def action_take_this(self, tree):
550
if self.file_id is not None:
551
self._resolve_with_cleanups(tree, self.file_id, self.path,
554
# Prior to bug #531967 we need to find back the file_id and restore
555
# the content from there
556
revtree, file_id = self._infer_file_id(tree)
557
tree.revert([revtree.id2path(file_id)],
558
old_tree=revtree, backups=False)
463
tree.rename_one(self.conflict_path, self.path)
560
465
def action_take_other(self, tree):
561
if self.file_id is not None:
562
self._resolve_with_cleanups(tree, self.file_id,
566
# Prior to bug #531967 we need to find back the file_id and restore
567
# the content from there
568
revtree, file_id = self._infer_file_id(tree)
569
tree.revert([revtree.id2path(file_id)],
570
old_tree=revtree, backups=False)
466
# just acccept bzr proposal
573
470
class ContentsConflict(PathConflict):
574
"""The files are of different types (or both binary), or not present"""
471
"""The files are of different types, or not present"""
582
479
def associated_filenames(self):
583
480
return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
585
def _resolve(self, tt, suffix_to_remove):
586
"""Resolve the conflict.
588
:param tt: The TreeTransform where the conflict is resolved.
589
:param suffix_to_remove: Either 'THIS' or 'OTHER'
591
The resolution is symmetric: when taking THIS, OTHER is deleted and
592
item.THIS is renamed into item and vice-versa.
595
# Delete 'item.THIS' or 'item.OTHER' depending on
598
tt.trans_id_tree_path(self.path + '.' + suffix_to_remove))
599
except errors.NoSuchFile:
600
# There are valid cases where 'item.suffix_to_remove' either
601
# never existed or was already deleted (including the case
602
# where the user deleted it)
605
this_path = tt._tree.id2path(self.file_id)
606
except errors.NoSuchId:
607
# The file is not present anymore. This may happen if the user
608
# deleted the file either manually or when resolving a conflict on
609
# the parent. We may raise some exception to indicate that the
610
# conflict doesn't exist anymore and as such doesn't need to be
611
# resolved ? -- vila 20110615
614
this_tid = tt.trans_id_tree_path(this_path)
615
if this_tid is not None:
616
# Rename 'item.suffix_to_remove' (note that if
617
# 'item.suffix_to_remove' has been deleted, this is a no-op)
618
parent_tid = tt.get_tree_parent(this_tid)
619
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
482
# FIXME: I smell something weird here and it seems we should be able to be
483
# more coherent with some other conflict ? bzr *did* a choice there but
484
# neither action_take_this nor action_take_other reflect that...
622
486
def action_take_this(self, tree):
623
self._resolve_with_cleanups(tree, 'OTHER')
487
tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
625
489
def action_take_other(self, tree):
626
self._resolve_with_cleanups(tree, 'THIS')
490
tree.remove([self.path], force=True, keep_files=False)
494
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
495
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
629
497
# TODO: There should be a base revid attribute to better inform the user about
630
498
# how the conflicts were generated.
631
class TextConflict(Conflict):
499
class TextConflict(PathConflict):
632
500
"""The merge algorithm could not resolve all differences encountered."""
638
506
format = 'Text conflict in %(path)s'
640
rformat = '%(class)s(%(path)r, %(file_id)r)'
642
508
def associated_filenames(self):
643
509
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
645
def _resolve(self, tt, winner_suffix):
646
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
648
:param tt: The TreeTransform where the conflict is resolved.
649
:param winner_suffix: Either 'THIS' or 'OTHER'
651
The resolution is symmetric, when taking THIS, item.THIS is renamed
652
into item and vice-versa. This takes one of the files as a whole
653
ignoring every difference that could have been merged cleanly.
655
# To avoid useless copies, we switch item and item.winner_suffix, only
656
# item will exist after the conflict has been resolved anyway.
657
item_tid = tt.trans_id_file_id(self.file_id)
658
item_parent_tid = tt.get_tree_parent(item_tid)
659
winner_path = self.path + '.' + winner_suffix
660
winner_tid = tt.trans_id_tree_path(winner_path)
661
winner_parent_tid = tt.get_tree_parent(winner_tid)
662
# Switch the paths to preserve the content
663
tt.adjust_path(osutils.basename(self.path),
664
winner_parent_tid, winner_tid)
665
tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
666
# Associate the file_id to the right content
667
tt.unversion_file(item_tid)
668
tt.version_file(self.file_id, winner_tid)
671
def action_take_this(self, tree):
672
self._resolve_with_cleanups(tree, 'THIS')
674
def action_take_other(self, tree):
675
self._resolve_with_cleanups(tree, 'OTHER')
678
512
class HandledConflict(Conflict):
679
513
"""A path problem that has been provisionally resolved.
766
600
typestring = 'parent loop'
768
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
602
format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
770
604
def action_take_this(self, tree):
771
605
# just acccept bzr proposal
774
608
def action_take_other(self, tree):
609
# FIXME: We shouldn't have to manipulate so many paths here (and there
610
# is probably a bug or two...)
611
base_path = osutils.basename(self.path)
612
conflict_base_path = osutils.basename(self.conflict_path)
775
613
tt = transform.TreeTransform(tree)
777
615
p_tid = tt.trans_id_file_id(self.file_id)
778
616
parent_tid = tt.get_tree_parent(p_tid)
779
617
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
780
618
cparent_tid = tt.get_tree_parent(cp_tid)
781
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
782
tt.adjust_path(osutils.basename(self.conflict_path),
619
tt.adjust_path(base_path, cparent_tid, cp_tid)
620
tt.adjust_path(conflict_base_path, parent_tid, p_tid)