/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/conflicts.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# TODO: 'brz resolve' should accept a directory name and work from that
18
18
# point down
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
20
import os
 
21
import re
23
22
 
24
23
from .lazy_import import lazy_import
25
24
lazy_import(globals(), """
26
25
import errno
27
26
 
28
27
from breezy import (
29
 
    cleanup,
30
 
    errors,
31
28
    osutils,
32
29
    rio,
33
30
    trace,
38
35
""")
39
36
from . import (
40
37
    cache_utf8,
 
38
    errors,
41
39
    commands,
42
40
    option,
43
41
    registry,
44
42
    )
45
 
from .sixish import text_type
46
43
 
47
44
 
48
45
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
63
60
    Use brz resolve when you have fixed a problem.
64
61
    """
65
62
    takes_options = [
66
 
            'directory',
67
 
            option.Option('text',
68
 
                          help='List paths of files with text conflicts.'),
 
63
        'directory',
 
64
        option.Option('text',
 
65
                      help='List paths of files with text conflicts.'),
69
66
        ]
70
67
    _see_also = ['resolve', 'conflict-types']
71
68
 
77
74
                    continue
78
75
                self.outf.write(conflict.path + '\n')
79
76
            else:
80
 
                self.outf.write(text_type(conflict) + '\n')
 
77
                self.outf.write(str(conflict) + '\n')
81
78
 
82
79
 
83
80
resolve_action_registry = registry.Registry()
84
81
 
85
82
 
86
83
resolve_action_registry.register(
 
84
    'auto', 'auto', 'Detect whether conflict has been resolved by user.')
 
85
resolve_action_registry.register(
87
86
    'done', 'done', 'Marks the conflict as resolved.')
88
87
resolve_action_registry.register(
89
88
    'take-this', 'take_this',
93
92
    'Resolve the conflict taking the merged version into account.')
94
93
resolve_action_registry.default_key = 'done'
95
94
 
 
95
 
96
96
class ResolveActionOption(option.RegistryOption):
97
97
 
98
98
    def __init__(self):
117
117
    aliases = ['resolved']
118
118
    takes_args = ['file*']
119
119
    takes_options = [
120
 
            'directory',
121
 
            option.Option('all', help='Resolve all conflicts in this tree.'),
122
 
            ResolveActionOption(),
123
 
            ]
 
120
        'directory',
 
121
        option.Option('all', help='Resolve all conflicts in this tree.'),
 
122
        ResolveActionOption(),
 
123
        ]
124
124
    _see_also = ['conflicts']
 
125
 
125
126
    def run(self, file_list=None, all=False, action=None, directory=None):
126
127
        if all:
127
128
            if file_list:
128
129
                raise errors.BzrCommandError(gettext("If --all is specified,"
129
 
                                             " no FILE may be provided"))
 
130
                                                     " no FILE may be provided"))
130
131
            if directory is None:
131
132
                directory = u'.'
132
133
            tree = workingtree.WorkingTree.open_containing(directory)[0]
135
136
        else:
136
137
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
137
138
                file_list, directory)
138
 
            if file_list is None:
139
 
                if action is None:
140
 
                    # FIXME: There is a special case here related to the option
141
 
                    # handling that could be clearer and easier to discover by
142
 
                    # providing an --auto action (bug #344013 and #383396) and
143
 
                    # make it mandatory instead of implicit and active only
144
 
                    # when no file_list is provided -- vila 091229
 
139
            if action is None:
 
140
                if file_list is None:
145
141
                    action = 'auto'
146
 
            else:
147
 
                if action is None:
 
142
                else:
148
143
                    action = 'done'
149
 
        if action == 'auto':
150
 
            if file_list is None:
151
 
                un_resolved, resolved = tree.auto_resolve()
152
 
                if len(un_resolved) > 0:
153
 
                    trace.note(ngettext('%d conflict auto-resolved.',
154
 
                        '%d conflicts auto-resolved.', len(resolved)),
155
 
                        len(resolved))
156
 
                    trace.note(gettext('Remaining conflicts:'))
157
 
                    for conflict in un_resolved:
158
 
                        trace.note(text_type(conflict))
159
 
                    return 1
160
 
                else:
161
 
                    trace.note(gettext('All conflicts resolved.'))
162
 
                    return 0
 
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:
 
147
            if after > 0:
 
148
                trace.note(
 
149
                    ngettext('%d conflict auto-resolved.',
 
150
                             '%d conflicts auto-resolved.', before - after),
 
151
                    before - after)
 
152
                trace.note(gettext('Remaining conflicts:'))
 
153
                for conflict in tree.conflicts():
 
154
                    trace.note(str(conflict))
 
155
                return 1
163
156
            else:
164
 
                # FIXME: This can never occur but the block above needs some
165
 
                # refactoring to transfer tree.auto_resolve() to
166
 
                # conflict.auto(tree) --vila 091242
167
 
                pass
 
157
                trace.note(gettext('All conflicts resolved.'))
 
158
                return 0
168
159
        else:
169
 
            before, after = resolve(tree, file_list, action=action)
170
160
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
171
161
                                '{0} conflicts resolved, {1} remaining',
172
 
                                before-after).format(before - after, after))
 
162
                                before - after).format(before - after, after))
173
163
 
174
164
 
175
165
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
296
286
    def to_strings(self):
297
287
        """Generate strings for the provided conflicts"""
298
288
        for conflict in self:
299
 
            yield text_type(conflict)
 
289
            yield str(conflict)
300
290
 
301
291
    def remove_files(self, tree):
302
292
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
369
359
        self.path = path
370
360
        # the factory blindly transfers the Stanza values to __init__ and
371
361
        # Stanza is purely a Unicode api.
372
 
        if isinstance(file_id, text_type):
 
362
        if isinstance(file_id, str):
373
363
            file_id = cache_utf8.encode(file_id)
374
364
        self.file_id = osutils.safe_file_id(file_id)
375
365
 
386
376
    def __cmp__(self, other):
387
377
        if getattr(other, "_cmp_list", None) is None:
388
378
            return -1
389
 
        return cmp(self._cmp_list(), other._cmp_list())
 
379
        x = self._cmp_list()
 
380
        y = other._cmp_list()
 
381
        return (x > y) - (x < y)
390
382
 
391
383
    def __hash__(self):
392
384
        return hash((type(self), self.path, self.file_id))
398
390
        return not self.__eq__(other)
399
391
 
400
392
    def __unicode__(self):
 
393
        return self.describe()
 
394
 
 
395
    def __str__(self):
 
396
        return self.describe()
 
397
 
 
398
    def describe(self):
401
399
        return self.format % self.__dict__
402
400
 
403
401
    def __repr__(self):
443
441
                if e.errno != errno.ENOENT:
444
442
                    raise
445
443
 
 
444
    def action_auto(self, tree):
 
445
        raise NotImplementedError(self.action_auto)
 
446
 
446
447
    def action_done(self, tree):
447
448
        """Mark the conflict as solved once it has been handled."""
448
449
        # This method does nothing but simplifies the design of upper levels.
455
456
        raise NotImplementedError(self.action_take_other)
456
457
 
457
458
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
458
 
        tt = transform.TreeTransform(tree)
459
 
        op = cleanup.OperationWithCleanups(self._resolve)
460
 
        op.add_cleanup(tt.finalize)
461
 
        op.run_simple(tt, *args, **kwargs)
 
459
        with tree.get_transform() as tt:
 
460
            self._resolve(tt, *args, **kwargs)
462
461
 
463
462
 
464
463
class PathConflict(Conflict):
495
494
        path_to_create = None
496
495
        if winner == 'this':
497
496
            if self.path == '<deleted>':
498
 
                return # Nothing to do
 
497
                return  # Nothing to do
499
498
            if self.conflict_path == '<deleted>':
500
499
                path_to_create = self.path
501
500
                revid = tt._tree.get_parent_ids()[0]
515
514
            tid = tt.trans_id_tree_path(path_to_create)
516
515
            tree = self._revision_tree(tt._tree, revid)
517
516
            transform.create_from_tree(
518
 
                tt, tid, tree, tree.id2path(file_id), file_id=file_id)
 
517
                tt, tid, tree, tree.id2path(file_id))
519
518
            tt.version_file(file_id, tid)
520
519
        else:
521
520
            tid = tt.trans_id_file_id(file_id)
534
533
        possible_paths = []
535
534
        for p in (self.path, self.conflict_path):
536
535
            if p == '<deleted>':
537
 
                # special hard-coded path 
 
536
                # special hard-coded path
538
537
                continue
539
538
            if p is not None:
540
539
                possible_paths.append(p)
610
609
            # deleted the file either manually or when resolving a conflict on
611
610
            # the parent.  We may raise some exception to indicate that the
612
611
            # conflict doesn't exist anymore and as such doesn't need to be
613
 
            # resolved ? -- vila 20110615 
 
612
            # resolved ? -- vila 20110615
614
613
            this_tid = None
615
614
        else:
616
615
            this_tid = tt.trans_id_tree_path(this_path)
641
640
 
642
641
    rformat = '%(class)s(%(path)r, %(file_id)r)'
643
642
 
 
643
    _conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
 
644
 
644
645
    def associated_filenames(self):
645
646
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
646
647
 
664
665
        # Switch the paths to preserve the content
665
666
        tt.adjust_path(osutils.basename(self.path),
666
667
                       winner_parent_tid, winner_tid)
667
 
        tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
 
668
        tt.adjust_path(osutils.basename(winner_path),
 
669
                       item_parent_tid, item_tid)
668
670
        # Associate the file_id to the right content
669
671
        tt.unversion_file(item_tid)
670
672
        tt.version_file(self.file_id, winner_tid)
671
673
        tt.apply()
672
674
 
 
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.
 
678
        try:
 
679
            kind = tree.kind(self.path)
 
680
        except errors.NoSuchFile:
 
681
            return
 
682
        if kind != 'file':
 
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:
 
687
            for line in f:
 
688
                if conflict_markers_in_line(line):
 
689
                    raise NotImplementedError("Conflict markers present")
 
690
 
673
691
    def action_take_this(self, tree):
674
692
        self._resolve_with_cleanups(tree, 'THIS')
675
693
 
715
733
        self.conflict_path = conflict_path
716
734
        # the factory blindly transfers the Stanza values to __init__,
717
735
        # so they can be unicode.
718
 
        if isinstance(conflict_file_id, text_type):
 
736
        if isinstance(conflict_file_id, str):
719
737
            conflict_file_id = cache_utf8.encode(conflict_file_id)
720
738
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
721
739
 
775
793
        pass
776
794
 
777
795
    def action_take_other(self, tree):
778
 
        tt = transform.TreeTransform(tree)
779
 
        try:
 
796
        with tree.get_transform() as tt:
780
797
            p_tid = tt.trans_id_file_id(self.file_id)
781
798
            parent_tid = tt.get_tree_parent(p_tid)
782
799
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
785
802
            tt.adjust_path(osutils.basename(self.conflict_path),
786
803
                           parent_tid, p_tid)
787
804
            tt.apply()
788
 
        finally:
789
 
            tt.finalize()
790
805
 
791
806
 
792
807
class UnversionedParent(HandledConflict):
891
906
    for conflict_type in conflict_types:
892
907
        ctype[conflict_type.typestring] = conflict_type
893
908
 
 
909
 
894
910
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
895
911
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
896
912
               DeletingParent, NonDirectoryParent)