~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

Add a NEWS entry and prepare submission.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2007, 2009 Canonical Ltd
2
2
#
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
18
18
# point down
19
19
 
20
20
import os
 
21
import re
21
22
 
22
23
from bzrlib.lazy_import import lazy_import
23
24
lazy_import(globals(), """
24
25
import errno
25
26
 
26
27
from bzrlib import (
27
 
    cleanup,
 
28
    builtins,
 
29
    commands,
28
30
    errors,
29
31
    osutils,
30
32
    rio,
31
33
    trace,
32
 
    transform,
33
34
    workingtree,
34
35
    )
35
 
from bzrlib.i18n import gettext, ngettext
36
36
""")
37
37
from bzrlib import (
38
 
    commands,
39
38
    option,
40
39
    registry,
41
40
    )
45
44
 
46
45
 
47
46
class cmd_conflicts(commands.Command):
48
 
    __doc__ = """List files with conflicts.
 
47
    """List files with conflicts.
49
48
 
50
49
    Merge will do its best to combine the changes in two branches, but there
51
50
    are some kinds of problems only a human can fix.  When it encounters those,
59
58
    Use bzr resolve when you have fixed a problem.
60
59
    """
61
60
    takes_options = [
62
 
            'directory',
63
61
            option.Option('text',
64
62
                          help='List paths of files with text conflicts.'),
65
63
        ]
66
 
    _see_also = ['resolve', 'conflict-types']
 
64
    _see_also = ['resolve']
67
65
 
68
 
    def run(self, text=False, directory=u'.'):
69
 
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
66
    def run(self, text=False):
 
67
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
70
68
        for conflict in wt.conflicts():
71
69
            if text:
72
70
                if conflict.typestring != 'text conflict':
73
71
                    continue
74
72
                self.outf.write(conflict.path + '\n')
75
73
            else:
76
 
                self.outf.write(unicode(conflict) + '\n')
 
74
                self.outf.write(str(conflict) + '\n')
77
75
 
78
76
 
79
77
resolve_action_registry = registry.Registry()
82
80
resolve_action_registry.register(
83
81
    'done', 'done', 'Marks the conflict as resolved' )
84
82
resolve_action_registry.register(
85
 
    'take-this', 'take_this',
 
83
    'keep-mine', 'keep_mine',
86
84
    'Resolve the conflict preserving the version in the working tree' )
87
85
resolve_action_registry.register(
88
 
    'take-other', 'take_other',
 
86
    'take-their', 'take_their',
89
87
    'Resolve the conflict taking the merged version into account' )
90
88
resolve_action_registry.default_key = 'done'
91
89
 
99
97
 
100
98
 
101
99
class cmd_resolve(commands.Command):
102
 
    __doc__ = """Mark a conflict as resolved.
 
100
    """Mark a conflict as resolved.
103
101
 
104
102
    Merge will do its best to combine the changes in two branches, but there
105
103
    are some kinds of problems only a human can fix.  When it encounters those,
113
111
    aliases = ['resolved']
114
112
    takes_args = ['file*']
115
113
    takes_options = [
116
 
            'directory',
117
114
            option.Option('all', help='Resolve all conflicts in this tree.'),
118
115
            ResolveActionOption(),
119
116
            ]
120
117
    _see_also = ['conflicts']
121
 
    def run(self, file_list=None, all=False, action=None, directory=None):
 
118
    def run(self, file_list=None, all=False, action=None):
122
119
        if all:
123
120
            if file_list:
124
 
                raise errors.BzrCommandError(gettext("If --all is specified,"
125
 
                                             " no FILE may be provided"))
126
 
            if directory is None:
127
 
                directory = u'.'
128
 
            tree = workingtree.WorkingTree.open_containing(directory)[0]
 
121
                raise errors.BzrCommandError("If --all is specified,"
 
122
                                             " no FILE may be provided")
 
123
            tree = workingtree.WorkingTree.open_containing('.')[0]
129
124
            if action is None:
130
125
                action = 'done'
131
126
        else:
132
 
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
133
 
                file_list, directory)
 
127
            tree, file_list = builtins.tree_files(file_list)
134
128
            if file_list is None:
135
129
                if action is None:
136
130
                    # FIXME: There is a special case here related to the option
146
140
            if file_list is None:
147
141
                un_resolved, resolved = tree.auto_resolve()
148
142
                if len(un_resolved) > 0:
149
 
                    trace.note(ngettext('%d conflict auto-resolved.',
150
 
                        '%d conflicts auto-resolved.', len(resolved)),
151
 
                        len(resolved))
152
 
                    trace.note(gettext('Remaining conflicts:'))
 
143
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
 
144
                    trace.note('Remaining conflicts:')
153
145
                    for conflict in un_resolved:
154
 
                        trace.note(unicode(conflict))
 
146
                        trace.note(conflict)
155
147
                    return 1
156
148
                else:
157
 
                    trace.note(gettext('All conflicts resolved.'))
 
149
                    trace.note('All conflicts resolved.')
158
150
                    return 0
159
151
            else:
160
152
                # FIXME: This can never occur but the block above needs some
162
154
                # conflict.auto(tree) --vila 091242
163
155
                pass
164
156
        else:
165
 
            before, after = resolve(tree, file_list, action=action)
166
 
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
167
 
                                '{0} conflicts resolved, {1} remaining',
168
 
                                before-after).format(before - after, after))
 
157
            resolve(tree, file_list, action=action)
169
158
 
170
159
 
171
160
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
184
173
    :param action: How the conflict should be resolved,
185
174
    """
186
175
    tree.lock_tree_write()
187
 
    nb_conflicts_after = None
188
176
    try:
189
177
        tree_conflicts = tree.conflicts()
190
 
        nb_conflicts_before = len(tree_conflicts)
191
178
        if paths is None:
192
179
            new_conflicts = ConflictList()
193
180
            to_process = tree_conflicts
201
188
            except NotImplementedError:
202
189
                new_conflicts.append(conflict)
203
190
        try:
204
 
            nb_conflicts_after = len(new_conflicts)
205
191
            tree.set_conflicts(new_conflicts)
206
192
        except errors.UnsupportedOperation:
207
193
            pass
208
194
    finally:
209
195
        tree.unlock()
210
 
    if nb_conflicts_after is None:
211
 
        nb_conflicts_after = nb_conflicts_before
212
 
    return nb_conflicts_before, nb_conflicts_after
213
196
 
214
197
 
215
198
def restore(filename):
295
278
    def to_strings(self):
296
279
        """Generate strings for the provided conflicts"""
297
280
        for conflict in self:
298
 
            yield unicode(conflict)
 
281
            yield str(conflict)
299
282
 
300
283
    def remove_files(self, tree):
301
284
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
394
377
    def __ne__(self, other):
395
378
        return not self.__eq__(other)
396
379
 
397
 
    def __unicode__(self):
 
380
    def __str__(self):
398
381
        return self.format % self.__dict__
399
382
 
400
383
    def __repr__(self):
423
406
 
424
407
        :param tree: The tree passed as a parameter to the method.
425
408
        """
426
 
        meth = getattr(self, 'action_%s' % action, None)
 
409
        meth = getattr(self, action, None)
427
410
        if meth is None:
428
411
            raise NotImplementedError(self.__class__.__name__ + '.' + action)
429
412
        meth(tree)
430
413
 
431
 
    def associated_filenames(self):
432
 
        """The names of the files generated to help resolve the conflict."""
433
 
        raise NotImplementedError(self.associated_filenames)
434
 
 
435
414
    def cleanup(self, tree):
436
 
        for fname in self.associated_filenames():
437
 
            try:
438
 
                osutils.delete_any(tree.abspath(fname))
439
 
            except OSError, e:
440
 
                if e.errno != errno.ENOENT:
441
 
                    raise
 
415
        raise NotImplementedError(self.cleanup)
442
416
 
443
 
    def action_done(self, tree):
 
417
    def done(self, tree):
444
418
        """Mark the conflict as solved once it has been handled."""
445
419
        # This method does nothing but simplifies the design of upper levels.
446
420
        pass
447
421
 
448
 
    def action_take_this(self, tree):
449
 
        raise NotImplementedError(self.action_take_this)
450
 
 
451
 
    def action_take_other(self, tree):
452
 
        raise NotImplementedError(self.action_take_other)
453
 
 
454
 
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
455
 
        tt = transform.TreeTransform(tree)
456
 
        op = cleanup.OperationWithCleanups(self._resolve)
457
 
        op.add_cleanup(tt.finalize)
458
 
        op.run_simple(tt, *args, **kwargs)
 
422
    def keep_mine(self, tree):
 
423
        raise NotImplementedError(self.keep_mine)
 
424
 
 
425
    def take_their(self, tree):
 
426
        raise NotImplementedError(self.take_their)
459
427
 
460
428
 
461
429
class PathConflict(Conflict):
477
445
            s.add('conflict_path', self.conflict_path)
478
446
        return s
479
447
 
480
 
    def associated_filenames(self):
 
448
    def cleanup(self, tree):
481
449
        # No additional files have been generated here
482
 
        return []
483
 
 
484
 
    def _resolve(self, tt, file_id, path, winner):
485
 
        """Resolve the conflict.
486
 
 
487
 
        :param tt: The TreeTransform where the conflict is resolved.
488
 
        :param file_id: The retained file id.
489
 
        :param path: The retained path.
490
 
        :param winner: 'this' or 'other' indicates which side is the winner.
491
 
        """
492
 
        path_to_create = None
493
 
        if winner == 'this':
494
 
            if self.path == '<deleted>':
495
 
                return # Nothing to do
496
 
            if self.conflict_path == '<deleted>':
497
 
                path_to_create = self.path
498
 
                revid = tt._tree.get_parent_ids()[0]
499
 
        elif winner == 'other':
500
 
            if self.conflict_path == '<deleted>':
501
 
                return  # Nothing to do
502
 
            if self.path == '<deleted>':
503
 
                path_to_create = self.conflict_path
504
 
                # FIXME: If there are more than two parents we may need to
505
 
                # iterate. Taking the last parent is the safer bet in the mean
506
 
                # time. -- vila 20100309
507
 
                revid = tt._tree.get_parent_ids()[-1]
508
 
        else:
509
 
            # Programmer error
510
 
            raise AssertionError('bad winner: %r' % (winner,))
511
 
        if path_to_create is not None:
512
 
            tid = tt.trans_id_tree_path(path_to_create)
513
 
            transform.create_from_tree(
514
 
                tt, tid, self._revision_tree(tt._tree, revid), file_id)
515
 
            tt.version_file(file_id, tid)
516
 
        else:
517
 
            tid = tt.trans_id_file_id(file_id)
518
 
        # Adjust the path for the retained file id
519
 
        parent_tid = tt.get_tree_parent(tid)
520
 
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
521
 
        tt.apply()
522
 
 
523
 
    def _revision_tree(self, tree, revid):
524
 
        return tree.branch.repository.revision_tree(revid)
525
 
 
526
 
    def _infer_file_id(self, tree):
527
 
        # Prior to bug #531967, file_id wasn't always set, there may still be
528
 
        # conflict files in the wild so we need to cope with them
529
 
        # Establish which path we should use to find back the file-id
530
 
        possible_paths = []
531
 
        for p in (self.path, self.conflict_path):
532
 
            if p == '<deleted>':
533
 
                # special hard-coded path 
534
 
                continue
535
 
            if p is not None:
536
 
                possible_paths.append(p)
537
 
        # Search the file-id in the parents with any path available
538
 
        file_id = None
539
 
        for revid in tree.get_parent_ids():
540
 
            revtree = self._revision_tree(tree, revid)
541
 
            for p in possible_paths:
542
 
                file_id = revtree.path2id(p)
543
 
                if file_id is not None:
544
 
                    return revtree, file_id
545
 
        return None, None
546
 
 
547
 
    def action_take_this(self, tree):
548
 
        if self.file_id is not None:
549
 
            self._resolve_with_cleanups(tree, self.file_id, self.path,
550
 
                                        winner='this')
551
 
        else:
552
 
            # Prior to bug #531967 we need to find back the file_id and restore
553
 
            # the content from there
554
 
            revtree, file_id = self._infer_file_id(tree)
555
 
            tree.revert([revtree.id2path(file_id)],
556
 
                        old_tree=revtree, backups=False)
557
 
 
558
 
    def action_take_other(self, tree):
559
 
        if self.file_id is not None:
560
 
            self._resolve_with_cleanups(tree, self.file_id,
561
 
                                        self.conflict_path,
562
 
                                        winner='other')
563
 
        else:
564
 
            # Prior to bug #531967 we need to find back the file_id and restore
565
 
            # the content from there
566
 
            revtree, file_id = self._infer_file_id(tree)
567
 
            tree.revert([revtree.id2path(file_id)],
568
 
                        old_tree=revtree, backups=False)
 
450
        pass
 
451
 
 
452
    def keep_mine(self, tree):
 
453
        tree.rename_one(self.conflict_path, self.path)
 
454
 
 
455
    def take_their(self, tree):
 
456
        # just acccept bzr proposal
 
457
        pass
569
458
 
570
459
 
571
460
class ContentsConflict(PathConflict):
572
 
    """The files are of different types (or both binary), or not present"""
 
461
    """The files are of different types, or not present"""
573
462
 
574
463
    has_files = True
575
464
 
577
466
 
578
467
    format = 'Contents conflict in %(path)s'
579
468
 
580
 
    def associated_filenames(self):
581
 
        return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
582
 
 
583
 
    def _resolve(self, tt, suffix_to_remove):
584
 
        """Resolve the conflict.
585
 
 
586
 
        :param tt: The TreeTransform where the conflict is resolved.
587
 
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
588
 
 
589
 
        The resolution is symmetric: when taking THIS, OTHER is deleted and
590
 
        item.THIS is renamed into item and vice-versa.
591
 
        """
592
 
        try:
593
 
            # Delete 'item.THIS' or 'item.OTHER' depending on
594
 
            # suffix_to_remove
595
 
            tt.delete_contents(
596
 
                tt.trans_id_tree_path(self.path + '.' + suffix_to_remove))
597
 
        except errors.NoSuchFile:
598
 
            # There are valid cases where 'item.suffix_to_remove' either
599
 
            # never existed or was already deleted (including the case
600
 
            # where the user deleted it)
601
 
            pass
602
 
        try:
603
 
            this_path = tt._tree.id2path(self.file_id)
604
 
        except errors.NoSuchId:
605
 
            # The file is not present anymore. This may happen if the user
606
 
            # deleted the file either manually or when resolving a conflict on
607
 
            # the parent.  We may raise some exception to indicate that the
608
 
            # conflict doesn't exist anymore and as such doesn't need to be
609
 
            # resolved ? -- vila 20110615 
610
 
            this_tid = None
611
 
        else:
612
 
            this_tid = tt.trans_id_tree_path(this_path)
613
 
        if this_tid is not None:
614
 
            # Rename 'item.suffix_to_remove' (note that if
615
 
            # 'item.suffix_to_remove' has been deleted, this is a no-op)
616
 
            parent_tid = tt.get_tree_parent(this_tid)
617
 
            tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
618
 
            tt.apply()
619
 
 
620
 
    def action_take_this(self, tree):
621
 
        self._resolve_with_cleanups(tree, 'OTHER')
622
 
 
623
 
    def action_take_other(self, tree):
624
 
        self._resolve_with_cleanups(tree, 'THIS')
625
 
 
 
469
    def cleanup(self, tree):
 
470
        for suffix in ('.BASE', '.OTHER'):
 
471
            try:
 
472
                osutils.delete_any(tree.abspath(self.path + suffix))
 
473
            except OSError, e:
 
474
                if e.errno != errno.ENOENT:
 
475
                    raise
 
476
 
 
477
    # FIXME: I smell something weird here and it seems we should be able to be
 
478
    # more coherent with some other conflict ? bzr *did* a choice there but
 
479
    # neither keep_mine nor take_their reflect that... -- vila 091224
 
480
    def keep_mine(self, tree):
 
481
        tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
 
482
 
 
483
    def take_their(self, tree):
 
484
        tree.remove([self.path], force=True, keep_files=False)
 
485
 
 
486
 
 
487
 
 
488
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
 
489
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
626
490
 
627
491
# TODO: There should be a base revid attribute to better inform the user about
628
492
# how the conflicts were generated.
629
 
class TextConflict(Conflict):
 
493
class TextConflict(PathConflict):
630
494
    """The merge algorithm could not resolve all differences encountered."""
631
495
 
632
496
    has_files = True
635
499
 
636
500
    format = 'Text conflict in %(path)s'
637
501
 
638
 
    rformat = '%(class)s(%(path)r, %(file_id)r)'
639
 
 
640
 
    def associated_filenames(self):
641
 
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
642
 
 
643
 
    def _resolve(self, tt, winner_suffix):
644
 
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
645
 
 
646
 
        :param tt: The TreeTransform where the conflict is resolved.
647
 
        :param winner_suffix: Either 'THIS' or 'OTHER'
648
 
 
649
 
        The resolution is symmetric, when taking THIS, item.THIS is renamed
650
 
        into item and vice-versa. This takes one of the files as a whole
651
 
        ignoring every difference that could have been merged cleanly.
652
 
        """
653
 
        # To avoid useless copies, we switch item and item.winner_suffix, only
654
 
        # item will exist after the conflict has been resolved anyway.
655
 
        item_tid = tt.trans_id_file_id(self.file_id)
656
 
        item_parent_tid = tt.get_tree_parent(item_tid)
657
 
        winner_path = self.path + '.' + winner_suffix
658
 
        winner_tid = tt.trans_id_tree_path(winner_path)
659
 
        winner_parent_tid = tt.get_tree_parent(winner_tid)
660
 
        # Switch the paths to preserve the content
661
 
        tt.adjust_path(osutils.basename(self.path),
662
 
                       winner_parent_tid, winner_tid)
663
 
        tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
664
 
        # Associate the file_id to the right content
665
 
        tt.unversion_file(item_tid)
666
 
        tt.version_file(self.file_id, winner_tid)
667
 
        tt.apply()
668
 
 
669
 
    def action_take_this(self, tree):
670
 
        self._resolve_with_cleanups(tree, 'THIS')
671
 
 
672
 
    def action_take_other(self, tree):
673
 
        self._resolve_with_cleanups(tree, 'OTHER')
 
502
    def cleanup(self, tree):
 
503
        for suffix in CONFLICT_SUFFIXES:
 
504
            try:
 
505
                osutils.delete_any(tree.abspath(self.path+suffix))
 
506
            except OSError, e:
 
507
                if e.errno != errno.ENOENT:
 
508
                    raise
674
509
 
675
510
 
676
511
class HandledConflict(Conflict):
692
527
        s.add('action', self.action)
693
528
        return s
694
529
 
695
 
    def associated_filenames(self):
696
 
        # Nothing has been generated here
697
 
        return []
 
530
    def cleanup(self, tree):
 
531
        """Nothing to cleanup."""
 
532
        pass
698
533
 
699
534
 
700
535
class HandledPathConflict(HandledConflict):
742
577
 
743
578
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
744
579
 
745
 
    def action_take_this(self, tree):
 
580
    def keep_mine(self, tree):
746
581
        tree.remove([self.conflict_path], force=True, keep_files=False)
747
582
        tree.rename_one(self.path, self.conflict_path)
748
583
 
749
 
    def action_take_other(self, tree):
 
584
    def take_their(self, tree):
750
585
        tree.remove([self.path], force=True, keep_files=False)
751
586
 
752
587
 
763
598
 
764
599
    typestring = 'parent loop'
765
600
 
766
 
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
 
601
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
767
602
 
768
 
    def action_take_this(self, tree):
 
603
    def keep_mine(self, tree):
769
604
        # just acccept bzr proposal
770
605
        pass
771
606
 
772
 
    def action_take_other(self, tree):
773
 
        tt = transform.TreeTransform(tree)
774
 
        try:
775
 
            p_tid = tt.trans_id_file_id(self.file_id)
776
 
            parent_tid = tt.get_tree_parent(p_tid)
777
 
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
778
 
            cparent_tid = tt.get_tree_parent(cp_tid)
779
 
            tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
780
 
            tt.adjust_path(osutils.basename(self.conflict_path),
781
 
                           parent_tid, p_tid)
782
 
            tt.apply()
783
 
        finally:
784
 
            tt.finalize()
 
607
    def take_their(self, tree):
 
608
        # FIXME: We shouldn't have to manipulate so many paths here (and there
 
609
        # is probably a bug or two...)
 
610
        conflict_base_path = osutils.basename(self.conflict_path)
 
611
        base_path = osutils.basename(self.path)
 
612
        tree.rename_one(self.conflict_path, conflict_base_path)
 
613
        tree.rename_one(self.path,
 
614
                        osutils.joinpath([conflict_base_path, base_path]))
785
615
 
786
616
 
787
617
class UnversionedParent(HandledConflict):
798
628
    # FIXME: We silently do nothing to make tests pass, but most probably the
799
629
    # conflict shouldn't exist (the long story is that the conflict is
800
630
    # generated with another one that can be resolved properly) -- vila 091224
801
 
    def action_take_this(self, tree):
 
631
    def keep_mine(self, tree):
802
632
        pass
803
633
 
804
 
    def action_take_other(self, tree):
 
634
    def take_their(self, tree):
805
635
        pass
806
636
 
807
637
 
816
646
 
817
647
    format = 'Conflict adding files to %(path)s.  %(action)s.'
818
648
 
819
 
    def action_take_this(self, tree):
 
649
    def keep_mine(self, tree):
820
650
        tree.remove([self.path], force=True, keep_files=False)
821
651
 
822
 
    def action_take_other(self, tree):
 
652
    def take_their(self, tree):
823
653
        # just acccept bzr proposal
824
654
        pass
825
655
 
838
668
    # FIXME: It's a bit strange that the default action is not coherent with
839
669
    # MissingParent from the *user* pov.
840
670
 
841
 
    def action_take_this(self, tree):
 
671
    def keep_mine(self, tree):
842
672
        # just acccept bzr proposal
843
673
        pass
844
674
 
845
 
    def action_take_other(self, tree):
 
675
    def take_their(self, tree):
846
676
        tree.remove([self.path], force=True, keep_files=False)
847
677
 
848
678
 
856
686
    format = "Conflict: %(path)s is not a directory, but has files in it."\
857
687
             "  %(action)s."
858
688
 
859
 
    # FIXME: .OTHER should be used instead of .new when the conflict is created
860
 
 
861
 
    def action_take_this(self, tree):
 
689
    def keep_mine(self, tree):
862
690
        # FIXME: we should preserve that path when the conflict is generated !
863
691
        if self.path.endswith('.new'):
864
692
            conflict_path = self.path[:-(len('.new'))]
865
693
            tree.remove([self.path], force=True, keep_files=False)
866
694
            tree.add(conflict_path)
867
695
        else:
868
 
            raise NotImplementedError(self.action_take_this)
 
696
            raise NotImplementedError(self.keep_mine)
869
697
 
870
 
    def action_take_other(self, tree):
 
698
    def take_their(self, tree):
871
699
        # FIXME: we should preserve that path when the conflict is generated !
872
700
        if self.path.endswith('.new'):
873
701
            conflict_path = self.path[:-(len('.new'))]
874
702
            tree.remove([conflict_path], force=True, keep_files=False)
875
703
            tree.rename_one(self.path, conflict_path)
876
704
        else:
877
 
            raise NotImplementedError(self.action_take_other)
 
705
            raise NotImplementedError(self.take_their)
878
706
 
879
707
 
880
708
ctype = {}