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
22
# TODO: bzr revert should resolve; even when reverting the whole tree
23
# or particular directories
23
from .lazy_import import lazy_import
24
lazy_import(globals(), """
34
from breezy.i18n import gettext, ngettext
45
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
48
class cmd_conflicts(commands.Command):
49
__doc__ = """List files with conflicts.
51
Merge will do its best to combine the changes in two branches, but there
52
are some kinds of problems only a human can fix. When it encounters those,
53
it will mark a conflict. A conflict means that you need to fix something,
54
before you can commit.
56
Conflicts normally are listed as short, human-readable messages. If --text
57
is supplied, the pathnames of files with text conflicts are listed,
58
instead. (This is useful for editing all files with text conflicts.)
60
Use brz resolve when you have fixed a problem.
29
from bzrlib.branch import Branch
30
from bzrlib.errors import BzrCommandError, NotConflicted
31
from bzrlib.commands import register_command
32
from bzrlib.workingtree import CONFLICT_SUFFIXES
34
class cmd_conflicts(bzrlib.commands.Command):
35
"""List files with conflicts.
36
(conflicts are determined by the presence of .BASE .TREE, and .OTHER
65
help='List paths of files with text conflicts.'),
67
_see_also = ['resolve', 'conflict-types']
69
def run(self, text=False, directory=u'.'):
70
wt = workingtree.WorkingTree.open_containing(directory)[0]
71
for conflict in wt.conflicts():
73
if conflict.typestring != 'text conflict':
75
self.outf.write(conflict.path + '\n')
77
self.outf.write(str(conflict) + '\n')
80
resolve_action_registry = registry.Registry()
83
resolve_action_registry.register(
84
'auto', 'auto', 'Detect whether conflict has been resolved by user.')
85
resolve_action_registry.register(
86
'done', 'done', 'Marks the conflict as resolved.')
87
resolve_action_registry.register(
88
'take-this', 'take_this',
89
'Resolve the conflict preserving the version in the working tree.')
90
resolve_action_registry.register(
91
'take-other', 'take_other',
92
'Resolve the conflict taking the merged version into account.')
93
resolve_action_registry.default_key = 'done'
96
class ResolveActionOption(option.RegistryOption):
99
super(ResolveActionOption, self).__init__(
100
'action', 'How to resolve the conflict.',
102
registry=resolve_action_registry)
105
class cmd_resolve(commands.Command):
106
__doc__ = """Mark a conflict as resolved.
108
Merge will do its best to combine the changes in two branches, but there
109
are some kinds of problems only a human can fix. When it encounters those,
110
it will mark a conflict. A conflict means that you need to fix something,
111
before you can 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.
40
for path in Branch.open_containing(u'.')[0].working_tree().iter_conflicts():
43
class cmd_resolve(bzrlib.commands.Command):
44
"""Mark a conflict as resolved.
117
46
aliases = ['resolved']
118
47
takes_args = ['file*']
121
option.Option('all', help='Resolve all conflicts in this tree.'),
122
ResolveActionOption(),
124
_see_also = ['conflicts']
126
def run(self, file_list=None, all=False, action=None, directory=None):
129
raise errors.CommandError(gettext("If --all is specified,"
130
" no FILE may be provided"))
131
if directory is None:
133
tree = workingtree.WorkingTree.open_containing(directory)[0]
48
takes_options = ['all']
49
def run(self, file_list=None, all=False):
52
raise BzrCommandError(
53
"command 'resolve' needs one or more FILE, or --all")
54
tree = Branch.open_containing(u'.')[0].working_tree()
55
file_list = list(tree.abspath(f) for f in tree.iter_conflicts())
137
tree, file_list = workingtree.WorkingTree.open_containing_paths(
138
file_list, directory)
140
if file_list is None:
58
raise BzrCommandError(
59
"If --all is specified, no FILE may be provided")
60
for filename in file_list:
62
for suffix in CONFLICT_SUFFIXES:
64
os.unlink(filename+suffix)
66
if e.errno != errno.ENOENT:
70
if failures == len(CONFLICT_SUFFIXES):
71
if not os.path.exists(filename):
72
print "%s does not exist" % filename
144
before, after = resolve(tree, file_list, action=action)
145
# GZ 2012-07-27: Should unify UI below now that auto is less magical.
146
if action == 'auto' and file_list is None:
149
ngettext('%d conflict auto-resolved.',
150
'%d conflicts auto-resolved.', before - after),
152
trace.note(gettext('Remaining conflicts:'))
153
for conflict in tree.conflicts():
154
trace.note(str(conflict))
157
trace.note(gettext('All conflicts resolved.'))
160
trace.note(ngettext('{0} conflict resolved, {1} remaining',
161
'{0} conflicts resolved, {1} remaining',
162
before - after).format(before - after, after))
165
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
167
"""Resolve some or all of the conflicts in a working tree.
169
:param paths: If None, resolve all conflicts. Otherwise, select only
171
:param recursive: If True, then elements of paths which are directories
172
have all their children resolved, etc. When invoked as part of
173
recursive commands like revert, this should be True. For commands
174
or applications wishing finer-grained control, like the resolve
175
command, this should be False.
176
:param ignore_misses: If False, warnings will be printed if the supplied
177
paths do not have conflicts.
178
:param action: How the conflict should be resolved,
180
nb_conflicts_after = None
181
with tree.lock_tree_write():
182
tree_conflicts = tree.conflicts()
183
nb_conflicts_before = len(tree_conflicts)
185
new_conflicts = ConflictList()
186
to_process = tree_conflicts
188
new_conflicts, to_process = tree_conflicts.select_conflicts(
189
tree, paths, ignore_misses, recursive)
190
for conflict in to_process:
192
conflict._do(action, tree)
193
conflict.cleanup(tree)
194
except NotImplementedError:
195
new_conflicts.append(conflict)
197
nb_conflicts_after = len(new_conflicts)
198
tree.set_conflicts(new_conflicts)
199
except errors.UnsupportedOperation:
201
if nb_conflicts_after is None:
202
nb_conflicts_after = nb_conflicts_before
203
return nb_conflicts_before, nb_conflicts_after
74
print "%s is not conflicted" % filename
206
76
def restore(filename):
207
"""Restore a conflicted file to the state it was in before merging.
209
Only text restoration is supported at present.
78
Restore a conflicted file to the state it was in before merging.
79
Only text restoration supported at present.
211
81
conflicted = False
213
osutils.rename(filename + ".THIS", filename)
83
os.rename(filename + ".THIS", filename)
216
86
if e.errno != errno.ENOENT:
219
89
os.unlink(filename + ".BASE")
222
92
if e.errno != errno.ENOENT:
225
95
os.unlink(filename + ".OTHER")
228
98
if e.errno != errno.ENOENT:
230
100
if not conflicted:
231
raise errors.NotConflicted(filename)
234
class ConflictList(object):
235
"""List of conflicts.
237
Typically obtained from WorkingTree.conflicts()
239
Can be instantiated from stanzas or from Conflict subclasses.
242
def __init__(self, conflicts=None):
243
object.__init__(self)
244
if conflicts is None:
247
self.__list = conflicts
250
return len(self.__list) == 0
253
return len(self.__list)
256
return iter(self.__list)
258
def __getitem__(self, key):
259
return self.__list[key]
261
def append(self, conflict):
262
return self.__list.append(conflict)
264
def __eq__(self, other_list):
265
return list(self) == list(other_list)
267
def __ne__(self, other_list):
268
return not (self == other_list)
271
return "ConflictList(%r)" % self.__list
274
def from_stanzas(stanzas):
275
"""Produce a new ConflictList from an iterable of stanzas"""
276
conflicts = ConflictList()
277
for stanza in stanzas:
278
conflicts.append(Conflict.factory(**stanza.as_dict()))
281
def to_stanzas(self):
282
"""Generator of stanzas"""
283
for conflict in self:
284
yield conflict.as_stanza()
286
def to_strings(self):
287
"""Generate strings for the provided conflicts"""
288
for conflict in self:
291
def remove_files(self, tree):
292
"""Remove the THIS, BASE and OTHER files for listed conflicts"""
293
for conflict in self:
294
if not conflict.has_files:
296
conflict.cleanup(tree)
298
def select_conflicts(self, tree, paths, ignore_misses=False,
300
"""Select the conflicts associated with paths in a tree.
302
File-ids are also used for this.
303
:return: a pair of ConflictLists: (not_selected, selected)
305
path_set = set(paths)
307
selected_paths = set()
308
new_conflicts = ConflictList()
309
selected_conflicts = ConflictList()
311
file_id = tree.path2id(path)
312
if file_id is not None:
315
for conflict in self:
317
for key in ('path', 'conflict_path'):
318
cpath = getattr(conflict, key, None)
321
if cpath in path_set:
323
selected_paths.add(cpath)
325
if osutils.is_inside_any(path_set, cpath):
327
selected_paths.add(cpath)
329
for key in ('file_id', 'conflict_file_id'):
330
cfile_id = getattr(conflict, key, None)
334
cpath = ids[cfile_id]
338
selected_paths.add(cpath)
340
selected_conflicts.append(conflict)
342
new_conflicts.append(conflict)
343
if ignore_misses is not True:
344
for path in [p for p in paths if p not in selected_paths]:
345
if not os.path.exists(tree.abspath(path)):
346
print("%s does not exist" % path)
348
print("%s is not conflicted" % path)
349
return new_conflicts, selected_conflicts
352
class Conflict(object):
353
"""Base class for all types of conflict"""
355
# FIXME: cleanup should take care of that ? -- vila 091229
358
def __init__(self, path, file_id=None):
360
# the factory blindly transfers the Stanza values to __init__ and
361
# Stanza is purely a Unicode api.
362
if isinstance(file_id, str):
363
file_id = cache_utf8.encode(file_id)
364
self.file_id = file_id
367
s = rio.Stanza(type=self.typestring, path=self.path)
368
if self.file_id is not None:
369
# Stanza requires Unicode apis
370
s.add('file_id', self.file_id.decode('utf8'))
374
return [type(self), self.path, self.file_id]
376
def __cmp__(self, other):
377
if getattr(other, "_cmp_list", None) is None:
380
y = other._cmp_list()
381
return (x > y) - (x < y)
384
return hash((type(self), self.path, self.file_id))
386
def __eq__(self, other):
387
return self.__cmp__(other) == 0
389
def __ne__(self, other):
390
return not self.__eq__(other)
392
def __unicode__(self):
393
return self.describe()
396
return self.describe()
399
return self.format % self.__dict__
402
rdict = dict(self.__dict__)
403
rdict['class'] = self.__class__.__name__
404
return self.rformat % rdict
407
def factory(type, **kwargs):
409
return ctype[type](**kwargs)
412
def sort_key(conflict):
413
if conflict.path is not None:
414
return conflict.path, conflict.typestring
415
elif getattr(conflict, "conflict_path", None) is not None:
416
return conflict.conflict_path, conflict.typestring
418
return None, conflict.typestring
420
def _do(self, action, tree):
421
"""Apply the specified action to the conflict.
423
:param action: The method name to call.
425
:param tree: The tree passed as a parameter to the method.
427
meth = getattr(self, 'action_%s' % action, None)
429
raise NotImplementedError(self.__class__.__name__ + '.' + action)
432
def associated_filenames(self):
433
"""The names of the files generated to help resolve the conflict."""
434
raise NotImplementedError(self.associated_filenames)
436
def cleanup(self, tree):
437
for fname in self.associated_filenames():
439
osutils.delete_any(tree.abspath(fname))
441
if e.errno != errno.ENOENT:
444
def action_auto(self, tree):
445
raise NotImplementedError(self.action_auto)
447
def action_done(self, tree):
448
"""Mark the conflict as solved once it has been handled."""
449
# This method does nothing but simplifies the design of upper levels.
452
def action_take_this(self, tree):
453
raise NotImplementedError(self.action_take_this)
455
def action_take_other(self, tree):
456
raise NotImplementedError(self.action_take_other)
458
def _resolve_with_cleanups(self, tree, *args, **kwargs):
459
with tree.transform() as tt:
460
self._resolve(tt, *args, **kwargs)
463
class PathConflict(Conflict):
464
"""A conflict was encountered merging file paths"""
466
typestring = 'path conflict'
468
format = 'Path conflict: %(path)s / %(conflict_path)s'
470
rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
472
def __init__(self, path, conflict_path=None, file_id=None):
473
Conflict.__init__(self, path, file_id)
474
self.conflict_path = conflict_path
477
s = Conflict.as_stanza(self)
478
if self.conflict_path is not None:
479
s.add('conflict_path', self.conflict_path)
482
def associated_filenames(self):
483
# 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
tree = self._revision_tree(tt._tree, revid)
516
transform.create_from_tree(
517
tt, tid, tree, tree.id2path(file_id))
518
tt.version_file(tid, file_id=file_id)
520
tid = tt.trans_id_file_id(file_id)
521
# Adjust the path for the retained file id
522
parent_tid = tt.get_tree_parent(tid)
523
tt.adjust_path(osutils.basename(path), parent_tid, tid)
526
def _revision_tree(self, tree, revid):
527
return tree.branch.repository.revision_tree(revid)
529
def _infer_file_id(self, tree):
530
# Prior to bug #531967, file_id wasn't always set, there may still be
531
# conflict files in the wild so we need to cope with them
532
# Establish which path we should use to find back the file-id
534
for p in (self.path, self.conflict_path):
536
# special hard-coded path
539
possible_paths.append(p)
540
# Search the file-id in the parents with any path available
542
for revid in tree.get_parent_ids():
543
revtree = self._revision_tree(tree, revid)
544
for p in possible_paths:
545
file_id = revtree.path2id(p)
546
if file_id is not None:
547
return revtree, file_id
550
def action_take_this(self, tree):
551
if self.file_id is not None:
552
self._resolve_with_cleanups(tree, self.file_id, self.path,
555
# Prior to bug #531967 we need to find back the file_id and restore
556
# the content from there
557
revtree, file_id = self._infer_file_id(tree)
558
tree.revert([revtree.id2path(file_id)],
559
old_tree=revtree, backups=False)
561
def action_take_other(self, tree):
562
if self.file_id is not None:
563
self._resolve_with_cleanups(tree, self.file_id,
567
# Prior to bug #531967 we need to find back the file_id and restore
568
# the content from there
569
revtree, file_id = self._infer_file_id(tree)
570
tree.revert([revtree.id2path(file_id)],
571
old_tree=revtree, backups=False)
574
class ContentsConflict(PathConflict):
575
"""The files are of different types (or both binary), or not present"""
579
typestring = 'contents conflict'
581
format = 'Contents conflict in %(path)s'
583
def associated_filenames(self):
584
return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
586
def _resolve(self, tt, suffix_to_remove):
587
"""Resolve the conflict.
589
:param tt: The TreeTransform where the conflict is resolved.
590
:param suffix_to_remove: Either 'THIS' or 'OTHER'
592
The resolution is symmetric: when taking THIS, OTHER is deleted and
593
item.THIS is renamed into item and vice-versa.
596
# Delete 'item.THIS' or 'item.OTHER' depending on
599
tt.trans_id_tree_path(self.path + '.' + suffix_to_remove))
600
except errors.NoSuchFile:
601
# There are valid cases where 'item.suffix_to_remove' either
602
# never existed or was already deleted (including the case
603
# where the user deleted it)
606
this_path = tt._tree.id2path(self.file_id)
607
except errors.NoSuchId:
608
# The file is not present anymore. This may happen if the user
609
# deleted the file either manually or when resolving a conflict on
610
# the parent. We may raise some exception to indicate that the
611
# conflict doesn't exist anymore and as such doesn't need to be
612
# resolved ? -- vila 20110615
615
this_tid = tt.trans_id_tree_path(this_path)
616
if this_tid is not None:
617
# Rename 'item.suffix_to_remove' (note that if
618
# 'item.suffix_to_remove' has been deleted, this is a no-op)
619
parent_tid = tt.get_tree_parent(this_tid)
620
tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
623
def action_take_this(self, tree):
624
self._resolve_with_cleanups(tree, 'OTHER')
626
def action_take_other(self, tree):
627
self._resolve_with_cleanups(tree, 'THIS')
630
# TODO: There should be a base revid attribute to better inform the user about
631
# how the conflicts were generated.
632
class TextConflict(Conflict):
633
"""The merge algorithm could not resolve all differences encountered."""
637
typestring = 'text conflict'
639
format = 'Text conflict in %(path)s'
641
rformat = '%(class)s(%(path)r, %(file_id)r)'
643
_conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
645
def associated_filenames(self):
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),
669
item_parent_tid, item_tid)
670
# Associate the file_id to the right content
671
tt.unversion_file(item_tid)
672
tt.version_file(winner_tid, file_id=self.file_id)
675
def action_auto(self, tree):
676
# GZ 2012-07-27: Using NotImplementedError to signal that a conflict
677
# can't be auto resolved does not seem ideal.
679
kind = tree.kind(self.path)
680
except errors.NoSuchFile:
683
raise NotImplementedError("Conflict is not a file")
684
conflict_markers_in_line = self._conflict_re.search
685
# GZ 2012-07-27: What if not tree.has_id(self.file_id) due to removal?
686
with tree.get_file(self.path) as f:
688
if conflict_markers_in_line(line):
689
raise NotImplementedError("Conflict markers present")
691
def action_take_this(self, tree):
692
self._resolve_with_cleanups(tree, 'THIS')
694
def action_take_other(self, tree):
695
self._resolve_with_cleanups(tree, 'OTHER')
698
class HandledConflict(Conflict):
699
"""A path problem that has been provisionally resolved.
700
This is intended to be a base class.
703
rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
705
def __init__(self, action, path, file_id=None):
706
Conflict.__init__(self, path, file_id)
710
return Conflict._cmp_list(self) + [self.action]
713
s = Conflict.as_stanza(self)
714
s.add('action', self.action)
717
def associated_filenames(self):
718
# Nothing has been generated here
722
class HandledPathConflict(HandledConflict):
723
"""A provisionally-resolved path problem involving two paths.
724
This is intended to be a base class.
727
rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
728
" %(file_id)r, %(conflict_file_id)r)"
730
def __init__(self, action, path, conflict_path, file_id=None,
731
conflict_file_id=None):
732
HandledConflict.__init__(self, action, path, file_id)
733
self.conflict_path = conflict_path
734
# the factory blindly transfers the Stanza values to __init__,
735
# so they can be unicode.
736
if isinstance(conflict_file_id, str):
737
conflict_file_id = cache_utf8.encode(conflict_file_id)
738
self.conflict_file_id = conflict_file_id
741
return HandledConflict._cmp_list(self) + [self.conflict_path,
742
self.conflict_file_id]
745
s = HandledConflict.as_stanza(self)
746
s.add('conflict_path', self.conflict_path)
747
if self.conflict_file_id is not None:
748
s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
753
class DuplicateID(HandledPathConflict):
754
"""Two files want the same file_id."""
756
typestring = 'duplicate id'
758
format = 'Conflict adding id to %(conflict_path)s. %(action)s %(path)s.'
761
class DuplicateEntry(HandledPathConflict):
762
"""Two directory entries want to have the same name."""
764
typestring = 'duplicate'
766
format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
768
def action_take_this(self, tree):
769
tree.remove([self.conflict_path], force=True, keep_files=False)
770
tree.rename_one(self.path, self.conflict_path)
772
def action_take_other(self, tree):
773
tree.remove([self.path], force=True, keep_files=False)
776
class ParentLoop(HandledPathConflict):
777
"""An attempt to create an infinitely-looping directory structure.
778
This is rare, but can be produced like so:
787
typestring = 'parent loop'
789
format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
791
def action_take_this(self, tree):
792
# just acccept brz proposal
795
def action_take_other(self, tree):
796
with tree.transform() as tt:
797
p_tid = tt.trans_id_file_id(self.file_id)
798
parent_tid = tt.get_tree_parent(p_tid)
799
cp_tid = tt.trans_id_file_id(self.conflict_file_id)
800
cparent_tid = tt.get_tree_parent(cp_tid)
801
tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
802
tt.adjust_path(osutils.basename(self.conflict_path),
807
class UnversionedParent(HandledConflict):
808
"""An attempt to version a file whose parent directory is not versioned.
809
Typically, the result of a merge where one tree unversioned the directory
810
and the other added a versioned file to it.
813
typestring = 'unversioned parent'
815
format = 'Conflict because %(path)s is not versioned, but has versioned'\
816
' children. %(action)s.'
818
# FIXME: We silently do nothing to make tests pass, but most probably the
819
# conflict shouldn't exist (the long story is that the conflict is
820
# generated with another one that can be resolved properly) -- vila 091224
821
def action_take_this(self, tree):
824
def action_take_other(self, tree):
828
class MissingParent(HandledConflict):
829
"""An attempt to add files to a directory that is not present.
830
Typically, the result of a merge where THIS deleted the directory and
831
the OTHER added a file to it.
832
See also: DeletingParent (same situation, THIS and OTHER reversed)
835
typestring = 'missing parent'
837
format = 'Conflict adding files to %(path)s. %(action)s.'
839
def action_take_this(self, tree):
840
tree.remove([self.path], force=True, keep_files=False)
842
def action_take_other(self, tree):
843
# just acccept brz proposal
847
class DeletingParent(HandledConflict):
848
"""An attempt to add files to a directory that is not present.
849
Typically, the result of a merge where one OTHER deleted the directory and
850
the THIS added a file to it.
853
typestring = 'deleting parent'
855
format = "Conflict: can't delete %(path)s because it is not empty. "\
858
# FIXME: It's a bit strange that the default action is not coherent with
859
# MissingParent from the *user* pov.
861
def action_take_this(self, tree):
862
# just acccept brz proposal
865
def action_take_other(self, tree):
866
tree.remove([self.path], force=True, keep_files=False)
869
class NonDirectoryParent(HandledConflict):
870
"""An attempt to add files to a directory that is not a directory or
871
an attempt to change the kind of a directory with files.
874
typestring = 'non-directory parent'
876
format = "Conflict: %(path)s is not a directory, but has files in it."\
879
# FIXME: .OTHER should be used instead of .new when the conflict is created
881
def action_take_this(self, tree):
882
# FIXME: we should preserve that path when the conflict is generated !
883
if self.path.endswith('.new'):
884
conflict_path = self.path[:-(len('.new'))]
885
tree.remove([self.path], force=True, keep_files=False)
886
tree.add(conflict_path)
888
raise NotImplementedError(self.action_take_this)
890
def action_take_other(self, tree):
891
# FIXME: we should preserve that path when the conflict is generated !
892
if self.path.endswith('.new'):
893
conflict_path = self.path[:-(len('.new'))]
894
tree.remove([conflict_path], force=True, keep_files=False)
895
tree.rename_one(self.path, conflict_path)
897
raise NotImplementedError(self.action_take_other)
903
def register_types(*conflict_types):
904
"""Register a Conflict subclass for serialization purposes"""
906
for conflict_type in conflict_types:
907
ctype[conflict_type.typestring] = conflict_type
910
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
911
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
912
DeletingParent, NonDirectoryParent)
101
raise NotConflicted(filename)