/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: Martin
  • Date: 2018-11-16 16:38:22 UTC
  • mto: This revision was merged to the branch mainline in revision 7172.
  • Revision ID: gzlist@googlemail.com-20181116163822-yg1h1cdng6w7w9kn
Make --profile-imports work on Python 3

Also tweak heading to line up correctly.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
# TODO: 'bzr resolve' should accept a directory name and work from that
 
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
 
20
22
import os
21
23
 
22
 
from bzrlib.lazy_import import lazy_import
 
24
from .lazy_import import lazy_import
23
25
lazy_import(globals(), """
24
26
import errno
25
27
 
26
 
from bzrlib import (
27
 
    builtins,
 
28
from breezy import (
28
29
    cleanup,
29
 
    commands,
30
30
    errors,
31
31
    osutils,
32
32
    rio,
34
34
    transform,
35
35
    workingtree,
36
36
    )
 
37
from breezy.i18n import gettext, ngettext
37
38
""")
38
 
from bzrlib import (
 
39
from . import (
 
40
    cache_utf8,
 
41
    commands,
39
42
    option,
40
43
    registry,
41
44
    )
 
45
from .sixish import text_type
42
46
 
43
47
 
44
48
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
50
54
    Merge will do its best to combine the changes in two branches, but there
51
55
    are some kinds of problems only a human can fix.  When it encounters those,
52
56
    it will mark a conflict.  A conflict means that you need to fix something,
53
 
    before you should commit.
 
57
    before you can commit.
54
58
 
55
59
    Conflicts normally are listed as short, human-readable messages.  If --text
56
60
    is supplied, the pathnames of files with text conflicts are listed,
57
61
    instead.  (This is useful for editing all files with text conflicts.)
58
62
 
59
 
    Use bzr resolve when you have fixed a problem.
 
63
    Use brz resolve when you have fixed a problem.
60
64
    """
61
65
    takes_options = [
 
66
            'directory',
62
67
            option.Option('text',
63
68
                          help='List paths of files with text conflicts.'),
64
69
        ]
65
70
    _see_also = ['resolve', 'conflict-types']
66
71
 
67
 
    def run(self, text=False):
68
 
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
 
72
    def run(self, text=False, directory=u'.'):
 
73
        wt = workingtree.WorkingTree.open_containing(directory)[0]
69
74
        for conflict in wt.conflicts():
70
75
            if text:
71
76
                if conflict.typestring != 'text conflict':
72
77
                    continue
73
78
                self.outf.write(conflict.path + '\n')
74
79
            else:
75
 
                self.outf.write(str(conflict) + '\n')
 
80
                self.outf.write(text_type(conflict) + '\n')
76
81
 
77
82
 
78
83
resolve_action_registry = registry.Registry()
79
84
 
80
85
 
81
86
resolve_action_registry.register(
82
 
    'done', 'done', 'Marks the conflict as resolved' )
 
87
    'done', 'done', 'Marks the conflict as resolved.')
83
88
resolve_action_registry.register(
84
89
    'take-this', 'take_this',
85
 
    'Resolve the conflict preserving the version in the working tree' )
 
90
    'Resolve the conflict preserving the version in the working tree.')
86
91
resolve_action_registry.register(
87
92
    'take-other', 'take_other',
88
 
    'Resolve the conflict taking the merged version into account' )
 
93
    'Resolve the conflict taking the merged version into account.')
89
94
resolve_action_registry.default_key = 'done'
90
95
 
91
96
class ResolveActionOption(option.RegistryOption):
103
108
    Merge will do its best to combine the changes in two branches, but there
104
109
    are some kinds of problems only a human can fix.  When it encounters those,
105
110
    it will mark a conflict.  A conflict means that you need to fix something,
106
 
    before you should commit.
 
111
    before you can commit.
107
112
 
108
 
    Once you have fixed a problem, use "bzr resolve" to automatically mark
109
 
    text conflicts as fixed, "bzr resolve FILE" to mark a specific conflict as
110
 
    resolved, or "bzr resolve --all" to mark all conflicts as resolved.
 
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.
111
116
    """
112
117
    aliases = ['resolved']
113
118
    takes_args = ['file*']
114
119
    takes_options = [
 
120
            'directory',
115
121
            option.Option('all', help='Resolve all conflicts in this tree.'),
116
122
            ResolveActionOption(),
117
123
            ]
118
124
    _see_also = ['conflicts']
119
 
    def run(self, file_list=None, all=False, action=None):
 
125
    def run(self, file_list=None, all=False, action=None, directory=None):
120
126
        if all:
121
127
            if file_list:
122
 
                raise errors.BzrCommandError("If --all is specified,"
123
 
                                             " no FILE may be provided")
124
 
            tree = workingtree.WorkingTree.open_containing('.')[0]
 
128
                raise errors.BzrCommandError(gettext("If --all is specified,"
 
129
                                             " no FILE may be provided"))
 
130
            if directory is None:
 
131
                directory = u'.'
 
132
            tree = workingtree.WorkingTree.open_containing(directory)[0]
125
133
            if action is None:
126
134
                action = 'done'
127
135
        else:
128
 
            tree, file_list = builtins.tree_files(file_list)
 
136
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
 
137
                file_list, directory)
129
138
            if file_list is None:
130
139
                if action is None:
131
140
                    # FIXME: There is a special case here related to the option
141
150
            if file_list is None:
142
151
                un_resolved, resolved = tree.auto_resolve()
143
152
                if len(un_resolved) > 0:
144
 
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
 
                    trace.note('Remaining conflicts:')
 
153
                    trace.note(ngettext('%d conflict auto-resolved.',
 
154
                        '%d conflicts auto-resolved.', len(resolved)),
 
155
                        len(resolved))
 
156
                    trace.note(gettext('Remaining conflicts:'))
146
157
                    for conflict in un_resolved:
147
 
                        trace.note(conflict)
 
158
                        trace.note(text_type(conflict))
148
159
                    return 1
149
160
                else:
150
 
                    trace.note('All conflicts resolved.')
 
161
                    trace.note(gettext('All conflicts resolved.'))
151
162
                    return 0
152
163
            else:
153
164
                # FIXME: This can never occur but the block above needs some
155
166
                # conflict.auto(tree) --vila 091242
156
167
                pass
157
168
        else:
158
 
            resolve(tree, file_list, action=action)
 
169
            before, after = resolve(tree, file_list, action=action)
 
170
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
 
171
                                '{0} conflicts resolved, {1} remaining',
 
172
                                before-after).format(before - after, after))
159
173
 
160
174
 
161
175
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
173
187
        paths do not have conflicts.
174
188
    :param action: How the conflict should be resolved,
175
189
    """
176
 
    tree.lock_tree_write()
177
 
    try:
 
190
    nb_conflicts_after = None
 
191
    with tree.lock_tree_write():
178
192
        tree_conflicts = tree.conflicts()
 
193
        nb_conflicts_before = len(tree_conflicts)
179
194
        if paths is None:
180
195
            new_conflicts = ConflictList()
181
196
            to_process = tree_conflicts
189
204
            except NotImplementedError:
190
205
                new_conflicts.append(conflict)
191
206
        try:
 
207
            nb_conflicts_after = len(new_conflicts)
192
208
            tree.set_conflicts(new_conflicts)
193
209
        except errors.UnsupportedOperation:
194
210
            pass
195
 
    finally:
196
 
        tree.unlock()
 
211
    if nb_conflicts_after is None:
 
212
        nb_conflicts_after = nb_conflicts_before
 
213
    return nb_conflicts_before, nb_conflicts_after
197
214
 
198
215
 
199
216
def restore(filename):
205
222
    try:
206
223
        osutils.rename(filename + ".THIS", filename)
207
224
        conflicted = True
208
 
    except OSError, e:
 
225
    except OSError as e:
209
226
        if e.errno != errno.ENOENT:
210
227
            raise
211
228
    try:
212
229
        os.unlink(filename + ".BASE")
213
230
        conflicted = True
214
 
    except OSError, e:
 
231
    except OSError as e:
215
232
        if e.errno != errno.ENOENT:
216
233
            raise
217
234
    try:
218
235
        os.unlink(filename + ".OTHER")
219
236
        conflicted = True
220
 
    except OSError, e:
 
237
    except OSError as e:
221
238
        if e.errno != errno.ENOENT:
222
239
            raise
223
240
    if not conflicted:
279
296
    def to_strings(self):
280
297
        """Generate strings for the provided conflicts"""
281
298
        for conflict in self:
282
 
            yield str(conflict)
 
299
            yield text_type(conflict)
283
300
 
284
301
    def remove_files(self, tree):
285
302
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
336
353
        if ignore_misses is not True:
337
354
            for path in [p for p in paths if p not in selected_paths]:
338
355
                if not os.path.exists(tree.abspath(path)):
339
 
                    print "%s does not exist" % path
 
356
                    print("%s does not exist" % path)
340
357
                else:
341
 
                    print "%s is not conflicted" % path
 
358
                    print("%s is not conflicted" % path)
342
359
        return new_conflicts, selected_conflicts
343
360
 
344
361
 
350
367
 
351
368
    def __init__(self, path, file_id=None):
352
369
        self.path = path
353
 
        # warn turned off, because the factory blindly transfers the Stanza
354
 
        # values to __init__ and Stanza is purely a Unicode api.
355
 
        self.file_id = osutils.safe_file_id(file_id, warn=False)
 
370
        # the factory blindly transfers the Stanza values to __init__ and
 
371
        # Stanza is purely a Unicode api.
 
372
        if isinstance(file_id, text_type):
 
373
            file_id = cache_utf8.encode(file_id)
 
374
        self.file_id = osutils.safe_file_id(file_id)
356
375
 
357
376
    def as_stanza(self):
358
377
        s = rio.Stanza(type=self.typestring, path=self.path)
367
386
    def __cmp__(self, other):
368
387
        if getattr(other, "_cmp_list", None) is None:
369
388
            return -1
370
 
        return cmp(self._cmp_list(), other._cmp_list())
 
389
        x = self._cmp_list()
 
390
        y = other._cmp_list()
 
391
        return (x > y) - (x < y)
371
392
 
372
393
    def __hash__(self):
373
394
        return hash((type(self), self.path, self.file_id))
378
399
    def __ne__(self, other):
379
400
        return not self.__eq__(other)
380
401
 
 
402
    def __unicode__(self):
 
403
        return self.describe()
 
404
 
381
405
    def __str__(self):
 
406
        return self.describe()
 
407
 
 
408
    def describe(self):
382
409
        return self.format % self.__dict__
383
410
 
384
411
    def __repr__(self):
420
447
        for fname in self.associated_filenames():
421
448
            try:
422
449
                osutils.delete_any(tree.abspath(fname))
423
 
            except OSError, e:
 
450
            except OSError as e:
424
451
                if e.errno != errno.ENOENT:
425
452
                    raise
426
453
 
494
521
            raise AssertionError('bad winner: %r' % (winner,))
495
522
        if path_to_create is not None:
496
523
            tid = tt.trans_id_tree_path(path_to_create)
 
524
            tree = self._revision_tree(tt._tree, revid)
497
525
            transform.create_from_tree(
498
 
                tt, tt.trans_id_tree_path(path_to_create),
499
 
                self._revision_tree(tt._tree, revid), file_id)
 
526
                tt, tid, tree, tree.id2path(file_id), file_id=file_id)
500
527
            tt.version_file(file_id, tid)
501
 
 
 
528
        else:
 
529
            tid = tt.trans_id_file_id(file_id)
502
530
        # Adjust the path for the retained file id
503
 
        tid = tt.trans_id_file_id(file_id)
504
531
        parent_tid = tt.get_tree_parent(tid)
505
 
        tt.adjust_path(path, parent_tid, tid)
 
532
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
506
533
        tt.apply()
507
534
 
508
535
    def _revision_tree(self, tree, revid):
571
598
        :param tt: The TreeTransform where the conflict is resolved.
572
599
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
573
600
 
574
 
        The resolution is symmetric, when taking THIS, OTHER is deleted and
 
601
        The resolution is symmetric: when taking THIS, OTHER is deleted and
575
602
        item.THIS is renamed into item and vice-versa.
576
603
        """
577
604
        try:
584
611
            # never existed or was already deleted (including the case
585
612
            # where the user deleted it)
586
613
            pass
587
 
        # Rename 'item.suffix_to_remove' (note that if
588
 
        # 'item.suffix_to_remove' has been deleted, this is a no-op)
589
 
        this_tid = tt.trans_id_file_id(self.file_id)
590
 
        parent_tid = tt.get_tree_parent(this_tid)
591
 
        tt.adjust_path(self.path, parent_tid, this_tid)
592
 
        tt.apply()
 
614
        try:
 
615
            this_path = tt._tree.id2path(self.file_id)
 
616
        except errors.NoSuchId:
 
617
            # The file is not present anymore. This may happen if the user
 
618
            # deleted the file either manually or when resolving a conflict on
 
619
            # the parent.  We may raise some exception to indicate that the
 
620
            # conflict doesn't exist anymore and as such doesn't need to be
 
621
            # resolved ? -- vila 20110615 
 
622
            this_tid = None
 
623
        else:
 
624
            this_tid = tt.trans_id_tree_path(this_path)
 
625
        if this_tid is not None:
 
626
            # Rename 'item.suffix_to_remove' (note that if
 
627
            # 'item.suffix_to_remove' has been deleted, this is a no-op)
 
628
            parent_tid = tt.get_tree_parent(this_tid)
 
629
            tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
 
630
            tt.apply()
593
631
 
594
632
    def action_take_this(self, tree):
595
633
        self._resolve_with_cleanups(tree, 'OTHER')
598
636
        self._resolve_with_cleanups(tree, 'THIS')
599
637
 
600
638
 
601
 
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
602
 
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
603
 
 
604
639
# TODO: There should be a base revid attribute to better inform the user about
605
640
# how the conflicts were generated.
606
 
class TextConflict(PathConflict):
 
641
class TextConflict(Conflict):
607
642
    """The merge algorithm could not resolve all differences encountered."""
608
643
 
609
644
    has_files = True
612
647
 
613
648
    format = 'Text conflict in %(path)s'
614
649
 
 
650
    rformat = '%(class)s(%(path)r, %(file_id)r)'
 
651
 
615
652
    def associated_filenames(self):
616
653
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
617
654
 
 
655
    def _resolve(self, tt, winner_suffix):
 
656
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
 
657
 
 
658
        :param tt: The TreeTransform where the conflict is resolved.
 
659
        :param winner_suffix: Either 'THIS' or 'OTHER'
 
660
 
 
661
        The resolution is symmetric, when taking THIS, item.THIS is renamed
 
662
        into item and vice-versa. This takes one of the files as a whole
 
663
        ignoring every difference that could have been merged cleanly.
 
664
        """
 
665
        # To avoid useless copies, we switch item and item.winner_suffix, only
 
666
        # item will exist after the conflict has been resolved anyway.
 
667
        item_tid = tt.trans_id_file_id(self.file_id)
 
668
        item_parent_tid = tt.get_tree_parent(item_tid)
 
669
        winner_path = self.path + '.' + winner_suffix
 
670
        winner_tid = tt.trans_id_tree_path(winner_path)
 
671
        winner_parent_tid = tt.get_tree_parent(winner_tid)
 
672
        # Switch the paths to preserve the content
 
673
        tt.adjust_path(osutils.basename(self.path),
 
674
                       winner_parent_tid, winner_tid)
 
675
        tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
 
676
        # Associate the file_id to the right content
 
677
        tt.unversion_file(item_tid)
 
678
        tt.version_file(self.file_id, winner_tid)
 
679
        tt.apply()
 
680
 
 
681
    def action_take_this(self, tree):
 
682
        self._resolve_with_cleanups(tree, 'THIS')
 
683
 
 
684
    def action_take_other(self, tree):
 
685
        self._resolve_with_cleanups(tree, 'OTHER')
 
686
 
618
687
 
619
688
class HandledConflict(Conflict):
620
689
    """A path problem that has been provisionally resolved.
652
721
                 conflict_file_id=None):
653
722
        HandledConflict.__init__(self, action, path, file_id)
654
723
        self.conflict_path = conflict_path
655
 
        # warn turned off, because the factory blindly transfers the Stanza
656
 
        # values to __init__.
657
 
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
658
 
                                                     warn=False)
 
724
        # the factory blindly transfers the Stanza values to __init__,
 
725
        # so they can be unicode.
 
726
        if isinstance(conflict_file_id, text_type):
 
727
            conflict_file_id = cache_utf8.encode(conflict_file_id)
 
728
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
659
729
 
660
730
    def _cmp_list(self):
661
731
        return HandledConflict._cmp_list(self) + [self.conflict_path,
709
779
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
710
780
 
711
781
    def action_take_this(self, tree):
712
 
        # just acccept bzr proposal
 
782
        # just acccept brz proposal
713
783
        pass
714
784
 
715
785
    def action_take_other(self, tree):
716
 
        # FIXME: We shouldn't have to manipulate so many paths here (and there
717
 
        # is probably a bug or two...)
718
 
        base_path = osutils.basename(self.path)
719
 
        conflict_base_path = osutils.basename(self.conflict_path)
720
786
        tt = transform.TreeTransform(tree)
721
787
        try:
722
788
            p_tid = tt.trans_id_file_id(self.file_id)
723
789
            parent_tid = tt.get_tree_parent(p_tid)
724
790
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
791
            cparent_tid = tt.get_tree_parent(cp_tid)
726
 
            tt.adjust_path(base_path, cparent_tid, cp_tid)
727
 
            tt.adjust_path(conflict_base_path, parent_tid, p_tid)
 
792
            tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
 
793
            tt.adjust_path(osutils.basename(self.conflict_path),
 
794
                           parent_tid, p_tid)
728
795
            tt.apply()
729
796
        finally:
730
797
            tt.finalize()
766
833
        tree.remove([self.path], force=True, keep_files=False)
767
834
 
768
835
    def action_take_other(self, tree):
769
 
        # just acccept bzr proposal
 
836
        # just acccept brz proposal
770
837
        pass
771
838
 
772
839
 
785
852
    # MissingParent from the *user* pov.
786
853
 
787
854
    def action_take_this(self, tree):
788
 
        # just acccept bzr proposal
 
855
        # just acccept brz proposal
789
856
        pass
790
857
 
791
858
    def action_take_other(self, tree):