/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: 2019-02-04 01:01:24 UTC
  • mto: This revision was merged to the branch mainline in revision 7268.
  • Revision ID: jelmer@jelmer.uk-20190204010124-ni0i4qc6f5tnbvux
Fix source tests.

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 = [
62
 
            option.Option('text',
63
 
                          help='List paths of files with text conflicts.'),
 
66
        'directory',
 
67
        option.Option('text',
 
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
 
 
96
 
91
97
class ResolveActionOption(option.RegistryOption):
92
98
 
93
99
    def __init__(self):
103
109
    Merge will do its best to combine the changes in two branches, but there
104
110
    are some kinds of problems only a human can fix.  When it encounters those,
105
111
    it will mark a conflict.  A conflict means that you need to fix something,
106
 
    before you should commit.
 
112
    before you can commit.
107
113
 
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.
 
114
    Once you have fixed a problem, use "brz resolve" to automatically mark
 
115
    text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
 
116
    resolved, or "brz resolve --all" to mark all conflicts as resolved.
111
117
    """
112
118
    aliases = ['resolved']
113
119
    takes_args = ['file*']
114
120
    takes_options = [
115
 
            option.Option('all', help='Resolve all conflicts in this tree.'),
116
 
            ResolveActionOption(),
117
 
            ]
 
121
        'directory',
 
122
        option.Option('all', help='Resolve all conflicts in this tree.'),
 
123
        ResolveActionOption(),
 
124
        ]
118
125
    _see_also = ['conflicts']
119
 
    def run(self, file_list=None, all=False, action=None):
 
126
 
 
127
    def run(self, file_list=None, all=False, action=None, directory=None):
120
128
        if all:
121
129
            if file_list:
122
 
                raise errors.BzrCommandError("If --all is specified,"
123
 
                                             " no FILE may be provided")
124
 
            tree = workingtree.WorkingTree.open_containing('.')[0]
 
130
                raise errors.BzrCommandError(gettext("If --all is specified,"
 
131
                                                     " no FILE may be provided"))
 
132
            if directory is None:
 
133
                directory = u'.'
 
134
            tree = workingtree.WorkingTree.open_containing(directory)[0]
125
135
            if action is None:
126
136
                action = 'done'
127
137
        else:
128
 
            tree, file_list = builtins.tree_files(file_list)
 
138
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
 
139
                file_list, directory)
129
140
            if file_list is None:
130
141
                if action is None:
131
142
                    # FIXME: There is a special case here related to the option
141
152
            if file_list is None:
142
153
                un_resolved, resolved = tree.auto_resolve()
143
154
                if len(un_resolved) > 0:
144
 
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
 
                    trace.note('Remaining conflicts:')
 
155
                    trace.note(ngettext('%d conflict auto-resolved.',
 
156
                                        '%d conflicts auto-resolved.', len(resolved)),
 
157
                               len(resolved))
 
158
                    trace.note(gettext('Remaining conflicts:'))
146
159
                    for conflict in un_resolved:
147
 
                        trace.note(conflict)
 
160
                        trace.note(text_type(conflict))
148
161
                    return 1
149
162
                else:
150
 
                    trace.note('All conflicts resolved.')
 
163
                    trace.note(gettext('All conflicts resolved.'))
151
164
                    return 0
152
165
            else:
153
166
                # FIXME: This can never occur but the block above needs some
155
168
                # conflict.auto(tree) --vila 091242
156
169
                pass
157
170
        else:
158
 
            resolve(tree, file_list, action=action)
 
171
            before, after = resolve(tree, file_list, action=action)
 
172
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
 
173
                                '{0} conflicts resolved, {1} remaining',
 
174
                                before - after).format(before - after, after))
159
175
 
160
176
 
161
177
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
173
189
        paths do not have conflicts.
174
190
    :param action: How the conflict should be resolved,
175
191
    """
176
 
    tree.lock_tree_write()
177
 
    try:
 
192
    nb_conflicts_after = None
 
193
    with tree.lock_tree_write():
178
194
        tree_conflicts = tree.conflicts()
 
195
        nb_conflicts_before = len(tree_conflicts)
179
196
        if paths is None:
180
197
            new_conflicts = ConflictList()
181
198
            to_process = tree_conflicts
189
206
            except NotImplementedError:
190
207
                new_conflicts.append(conflict)
191
208
        try:
 
209
            nb_conflicts_after = len(new_conflicts)
192
210
            tree.set_conflicts(new_conflicts)
193
211
        except errors.UnsupportedOperation:
194
212
            pass
195
 
    finally:
196
 
        tree.unlock()
 
213
    if nb_conflicts_after is None:
 
214
        nb_conflicts_after = nb_conflicts_before
 
215
    return nb_conflicts_before, nb_conflicts_after
197
216
 
198
217
 
199
218
def restore(filename):
205
224
    try:
206
225
        osutils.rename(filename + ".THIS", filename)
207
226
        conflicted = True
208
 
    except OSError, e:
 
227
    except OSError as e:
209
228
        if e.errno != errno.ENOENT:
210
229
            raise
211
230
    try:
212
231
        os.unlink(filename + ".BASE")
213
232
        conflicted = True
214
 
    except OSError, e:
 
233
    except OSError as e:
215
234
        if e.errno != errno.ENOENT:
216
235
            raise
217
236
    try:
218
237
        os.unlink(filename + ".OTHER")
219
238
        conflicted = True
220
 
    except OSError, e:
 
239
    except OSError as e:
221
240
        if e.errno != errno.ENOENT:
222
241
            raise
223
242
    if not conflicted:
279
298
    def to_strings(self):
280
299
        """Generate strings for the provided conflicts"""
281
300
        for conflict in self:
282
 
            yield str(conflict)
 
301
            yield text_type(conflict)
283
302
 
284
303
    def remove_files(self, tree):
285
304
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
336
355
        if ignore_misses is not True:
337
356
            for path in [p for p in paths if p not in selected_paths]:
338
357
                if not os.path.exists(tree.abspath(path)):
339
 
                    print "%s does not exist" % path
 
358
                    print("%s does not exist" % path)
340
359
                else:
341
 
                    print "%s is not conflicted" % path
 
360
                    print("%s is not conflicted" % path)
342
361
        return new_conflicts, selected_conflicts
343
362
 
344
363
 
350
369
 
351
370
    def __init__(self, path, file_id=None):
352
371
        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)
 
372
        # the factory blindly transfers the Stanza values to __init__ and
 
373
        # Stanza is purely a Unicode api.
 
374
        if isinstance(file_id, text_type):
 
375
            file_id = cache_utf8.encode(file_id)
 
376
        self.file_id = osutils.safe_file_id(file_id)
356
377
 
357
378
    def as_stanza(self):
358
379
        s = rio.Stanza(type=self.typestring, path=self.path)
367
388
    def __cmp__(self, other):
368
389
        if getattr(other, "_cmp_list", None) is None:
369
390
            return -1
370
 
        return cmp(self._cmp_list(), other._cmp_list())
 
391
        x = self._cmp_list()
 
392
        y = other._cmp_list()
 
393
        return (x > y) - (x < y)
371
394
 
372
395
    def __hash__(self):
373
396
        return hash((type(self), self.path, self.file_id))
378
401
    def __ne__(self, other):
379
402
        return not self.__eq__(other)
380
403
 
 
404
    def __unicode__(self):
 
405
        return self.describe()
 
406
 
381
407
    def __str__(self):
 
408
        return self.describe()
 
409
 
 
410
    def describe(self):
382
411
        return self.format % self.__dict__
383
412
 
384
413
    def __repr__(self):
420
449
        for fname in self.associated_filenames():
421
450
            try:
422
451
                osutils.delete_any(tree.abspath(fname))
423
 
            except OSError, e:
 
452
            except OSError as e:
424
453
                if e.errno != errno.ENOENT:
425
454
                    raise
426
455
 
476
505
        path_to_create = None
477
506
        if winner == 'this':
478
507
            if self.path == '<deleted>':
479
 
                return # Nothing to do
 
508
                return  # Nothing to do
480
509
            if self.conflict_path == '<deleted>':
481
510
                path_to_create = self.path
482
511
                revid = tt._tree.get_parent_ids()[0]
494
523
            raise AssertionError('bad winner: %r' % (winner,))
495
524
        if path_to_create is not None:
496
525
            tid = tt.trans_id_tree_path(path_to_create)
 
526
            tree = self._revision_tree(tt._tree, revid)
497
527
            transform.create_from_tree(
498
 
                tt, tt.trans_id_tree_path(path_to_create),
499
 
                self._revision_tree(tt._tree, revid), file_id)
 
528
                tt, tid, tree, tree.id2path(file_id), file_id=file_id)
500
529
            tt.version_file(file_id, tid)
501
 
 
 
530
        else:
 
531
            tid = tt.trans_id_file_id(file_id)
502
532
        # Adjust the path for the retained file id
503
 
        tid = tt.trans_id_file_id(file_id)
504
533
        parent_tid = tt.get_tree_parent(tid)
505
 
        tt.adjust_path(path, parent_tid, tid)
 
534
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
506
535
        tt.apply()
507
536
 
508
537
    def _revision_tree(self, tree, revid):
515
544
        possible_paths = []
516
545
        for p in (self.path, self.conflict_path):
517
546
            if p == '<deleted>':
518
 
                # special hard-coded path 
 
547
                # special hard-coded path
519
548
                continue
520
549
            if p is not None:
521
550
                possible_paths.append(p)
571
600
        :param tt: The TreeTransform where the conflict is resolved.
572
601
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
573
602
 
574
 
        The resolution is symmetric, when taking THIS, OTHER is deleted and
 
603
        The resolution is symmetric: when taking THIS, OTHER is deleted and
575
604
        item.THIS is renamed into item and vice-versa.
576
605
        """
577
606
        try:
584
613
            # never existed or was already deleted (including the case
585
614
            # where the user deleted it)
586
615
            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()
 
616
        try:
 
617
            this_path = tt._tree.id2path(self.file_id)
 
618
        except errors.NoSuchId:
 
619
            # The file is not present anymore. This may happen if the user
 
620
            # deleted the file either manually or when resolving a conflict on
 
621
            # the parent.  We may raise some exception to indicate that the
 
622
            # conflict doesn't exist anymore and as such doesn't need to be
 
623
            # resolved ? -- vila 20110615
 
624
            this_tid = None
 
625
        else:
 
626
            this_tid = tt.trans_id_tree_path(this_path)
 
627
        if this_tid is not None:
 
628
            # Rename 'item.suffix_to_remove' (note that if
 
629
            # 'item.suffix_to_remove' has been deleted, this is a no-op)
 
630
            parent_tid = tt.get_tree_parent(this_tid)
 
631
            tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
 
632
            tt.apply()
593
633
 
594
634
    def action_take_this(self, tree):
595
635
        self._resolve_with_cleanups(tree, 'OTHER')
598
638
        self._resolve_with_cleanups(tree, 'THIS')
599
639
 
600
640
 
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
641
# TODO: There should be a base revid attribute to better inform the user about
605
642
# how the conflicts were generated.
606
 
class TextConflict(PathConflict):
 
643
class TextConflict(Conflict):
607
644
    """The merge algorithm could not resolve all differences encountered."""
608
645
 
609
646
    has_files = True
612
649
 
613
650
    format = 'Text conflict in %(path)s'
614
651
 
 
652
    rformat = '%(class)s(%(path)r, %(file_id)r)'
 
653
 
615
654
    def associated_filenames(self):
616
655
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
617
656
 
 
657
    def _resolve(self, tt, winner_suffix):
 
658
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
 
659
 
 
660
        :param tt: The TreeTransform where the conflict is resolved.
 
661
        :param winner_suffix: Either 'THIS' or 'OTHER'
 
662
 
 
663
        The resolution is symmetric, when taking THIS, item.THIS is renamed
 
664
        into item and vice-versa. This takes one of the files as a whole
 
665
        ignoring every difference that could have been merged cleanly.
 
666
        """
 
667
        # To avoid useless copies, we switch item and item.winner_suffix, only
 
668
        # item will exist after the conflict has been resolved anyway.
 
669
        item_tid = tt.trans_id_file_id(self.file_id)
 
670
        item_parent_tid = tt.get_tree_parent(item_tid)
 
671
        winner_path = self.path + '.' + winner_suffix
 
672
        winner_tid = tt.trans_id_tree_path(winner_path)
 
673
        winner_parent_tid = tt.get_tree_parent(winner_tid)
 
674
        # Switch the paths to preserve the content
 
675
        tt.adjust_path(osutils.basename(self.path),
 
676
                       winner_parent_tid, winner_tid)
 
677
        tt.adjust_path(osutils.basename(winner_path),
 
678
                       item_parent_tid, item_tid)
 
679
        # Associate the file_id to the right content
 
680
        tt.unversion_file(item_tid)
 
681
        tt.version_file(self.file_id, winner_tid)
 
682
        tt.apply()
 
683
 
 
684
    def action_take_this(self, tree):
 
685
        self._resolve_with_cleanups(tree, 'THIS')
 
686
 
 
687
    def action_take_other(self, tree):
 
688
        self._resolve_with_cleanups(tree, 'OTHER')
 
689
 
618
690
 
619
691
class HandledConflict(Conflict):
620
692
    """A path problem that has been provisionally resolved.
652
724
                 conflict_file_id=None):
653
725
        HandledConflict.__init__(self, action, path, file_id)
654
726
        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)
 
727
        # the factory blindly transfers the Stanza values to __init__,
 
728
        # so they can be unicode.
 
729
        if isinstance(conflict_file_id, text_type):
 
730
            conflict_file_id = cache_utf8.encode(conflict_file_id)
 
731
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
659
732
 
660
733
    def _cmp_list(self):
661
734
        return HandledConflict._cmp_list(self) + [self.conflict_path,
709
782
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
710
783
 
711
784
    def action_take_this(self, tree):
712
 
        # just acccept bzr proposal
 
785
        # just acccept brz proposal
713
786
        pass
714
787
 
715
788
    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
789
        tt = transform.TreeTransform(tree)
721
790
        try:
722
791
            p_tid = tt.trans_id_file_id(self.file_id)
723
792
            parent_tid = tt.get_tree_parent(p_tid)
724
793
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
794
            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)
 
795
            tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
 
796
            tt.adjust_path(osutils.basename(self.conflict_path),
 
797
                           parent_tid, p_tid)
728
798
            tt.apply()
729
799
        finally:
730
800
            tt.finalize()
766
836
        tree.remove([self.path], force=True, keep_files=False)
767
837
 
768
838
    def action_take_other(self, tree):
769
 
        # just acccept bzr proposal
 
839
        # just acccept brz proposal
770
840
        pass
771
841
 
772
842
 
785
855
    # MissingParent from the *user* pov.
786
856
 
787
857
    def action_take_this(self, tree):
788
 
        # just acccept bzr proposal
 
858
        # just acccept brz proposal
789
859
        pass
790
860
 
791
861
    def action_take_other(self, tree):
832
902
    for conflict_type in conflict_types:
833
903
        ctype[conflict_type.typestring] = conflict_type
834
904
 
 
905
 
835
906
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
836
907
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
837
908
               DeletingParent, NonDirectoryParent)