/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 bzrlib/conflicts.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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, 2006, 2007, 2009, 2010 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: 'brz resolve' should accept a directory name and work from that
 
17
# TODO: 'bzr 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
23
21
 
24
 
from .lazy_import import lazy_import
 
22
from bzrlib.lazy_import import lazy_import
25
23
lazy_import(globals(), """
26
24
import errno
27
25
 
28
 
from breezy import (
 
26
from bzrlib import (
 
27
    builtins,
29
28
    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
38
37
""")
39
 
from . import (
40
 
    cache_utf8,
41
 
    commands,
 
38
from bzrlib import (
42
39
    option,
43
40
    registry,
44
41
    )
45
 
from .sixish import text_type
46
42
 
47
43
 
48
44
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
54
50
    Merge will do its best to combine the changes in two branches, but there
55
51
    are some kinds of problems only a human can fix.  When it encounters those,
56
52
    it will mark a conflict.  A conflict means that you need to fix something,
57
 
    before you can commit.
 
53
    before you should commit.
58
54
 
59
55
    Conflicts normally are listed as short, human-readable messages.  If --text
60
56
    is supplied, the pathnames of files with text conflicts are listed,
61
57
    instead.  (This is useful for editing all files with text conflicts.)
62
58
 
63
 
    Use brz resolve when you have fixed a problem.
 
59
    Use bzr resolve when you have fixed a problem.
64
60
    """
65
61
    takes_options = [
66
 
        'directory',
67
 
        option.Option('text',
68
 
                      help='List paths of files with text conflicts.'),
 
62
            option.Option('text',
 
63
                          help='List paths of files with text conflicts.'),
69
64
        ]
70
65
    _see_also = ['resolve', 'conflict-types']
71
66
 
72
 
    def run(self, text=False, directory=u'.'):
73
 
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
67
    def run(self, text=False):
 
68
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
74
69
        for conflict in wt.conflicts():
75
70
            if text:
76
71
                if conflict.typestring != 'text conflict':
77
72
                    continue
78
73
                self.outf.write(conflict.path + '\n')
79
74
            else:
80
 
                self.outf.write(text_type(conflict) + '\n')
 
75
                self.outf.write(str(conflict) + '\n')
81
76
 
82
77
 
83
78
resolve_action_registry = registry.Registry()
84
79
 
85
80
 
86
81
resolve_action_registry.register(
87
 
    'done', 'done', 'Marks the conflict as resolved.')
 
82
    'done', 'done', 'Marks the conflict as resolved' )
88
83
resolve_action_registry.register(
89
84
    'take-this', 'take_this',
90
 
    'Resolve the conflict preserving the version in the working tree.')
 
85
    'Resolve the conflict preserving the version in the working tree' )
91
86
resolve_action_registry.register(
92
87
    'take-other', 'take_other',
93
 
    'Resolve the conflict taking the merged version into account.')
 
88
    'Resolve the conflict taking the merged version into account' )
94
89
resolve_action_registry.default_key = 'done'
95
90
 
96
 
 
97
91
class ResolveActionOption(option.RegistryOption):
98
92
 
99
93
    def __init__(self):
109
103
    Merge will do its best to combine the changes in two branches, but there
110
104
    are some kinds of problems only a human can fix.  When it encounters those,
111
105
    it will mark a conflict.  A conflict means that you need to fix something,
112
 
    before you can commit.
 
106
    before you should commit.
113
107
 
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.
 
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.
117
111
    """
118
112
    aliases = ['resolved']
119
113
    takes_args = ['file*']
120
114
    takes_options = [
121
 
        'directory',
122
 
        option.Option('all', help='Resolve all conflicts in this tree.'),
123
 
        ResolveActionOption(),
124
 
        ]
 
115
            option.Option('all', help='Resolve all conflicts in this tree.'),
 
116
            ResolveActionOption(),
 
117
            ]
125
118
    _see_also = ['conflicts']
126
 
 
127
 
    def run(self, file_list=None, all=False, action=None, directory=None):
 
119
    def run(self, file_list=None, all=False, action=None):
128
120
        if all:
129
121
            if file_list:
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]
 
122
                raise errors.BzrCommandError("If --all is specified,"
 
123
                                             " no FILE may be provided")
 
124
            tree = workingtree.WorkingTree.open_containing('.')[0]
135
125
            if action is None:
136
126
                action = 'done'
137
127
        else:
138
 
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
139
 
                file_list, directory)
 
128
            tree, file_list = builtins.tree_files(file_list)
140
129
            if file_list is None:
141
130
                if action is None:
142
131
                    # FIXME: There is a special case here related to the option
152
141
            if file_list is None:
153
142
                un_resolved, resolved = tree.auto_resolve()
154
143
                if len(un_resolved) > 0:
155
 
                    trace.note(ngettext('%d conflict auto-resolved.',
156
 
                                        '%d conflicts auto-resolved.', len(resolved)),
157
 
                               len(resolved))
158
 
                    trace.note(gettext('Remaining conflicts:'))
 
144
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
 
145
                    trace.note('Remaining conflicts:')
159
146
                    for conflict in un_resolved:
160
 
                        trace.note(text_type(conflict))
 
147
                        trace.note(conflict)
161
148
                    return 1
162
149
                else:
163
 
                    trace.note(gettext('All conflicts resolved.'))
 
150
                    trace.note('All conflicts resolved.')
164
151
                    return 0
165
152
            else:
166
153
                # FIXME: This can never occur but the block above needs some
168
155
                # conflict.auto(tree) --vila 091242
169
156
                pass
170
157
        else:
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))
 
158
            resolve(tree, file_list, action=action)
175
159
 
176
160
 
177
161
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
189
173
        paths do not have conflicts.
190
174
    :param action: How the conflict should be resolved,
191
175
    """
192
 
    nb_conflicts_after = None
193
 
    with tree.lock_tree_write():
 
176
    tree.lock_tree_write()
 
177
    try:
194
178
        tree_conflicts = tree.conflicts()
195
 
        nb_conflicts_before = len(tree_conflicts)
196
179
        if paths is None:
197
180
            new_conflicts = ConflictList()
198
181
            to_process = tree_conflicts
206
189
            except NotImplementedError:
207
190
                new_conflicts.append(conflict)
208
191
        try:
209
 
            nb_conflicts_after = len(new_conflicts)
210
192
            tree.set_conflicts(new_conflicts)
211
193
        except errors.UnsupportedOperation:
212
194
            pass
213
 
    if nb_conflicts_after is None:
214
 
        nb_conflicts_after = nb_conflicts_before
215
 
    return nb_conflicts_before, nb_conflicts_after
 
195
    finally:
 
196
        tree.unlock()
216
197
 
217
198
 
218
199
def restore(filename):
224
205
    try:
225
206
        osutils.rename(filename + ".THIS", filename)
226
207
        conflicted = True
227
 
    except OSError as e:
 
208
    except OSError, e:
228
209
        if e.errno != errno.ENOENT:
229
210
            raise
230
211
    try:
231
212
        os.unlink(filename + ".BASE")
232
213
        conflicted = True
233
 
    except OSError as e:
 
214
    except OSError, e:
234
215
        if e.errno != errno.ENOENT:
235
216
            raise
236
217
    try:
237
218
        os.unlink(filename + ".OTHER")
238
219
        conflicted = True
239
 
    except OSError as e:
 
220
    except OSError, e:
240
221
        if e.errno != errno.ENOENT:
241
222
            raise
242
223
    if not conflicted:
298
279
    def to_strings(self):
299
280
        """Generate strings for the provided conflicts"""
300
281
        for conflict in self:
301
 
            yield text_type(conflict)
 
282
            yield str(conflict)
302
283
 
303
284
    def remove_files(self, tree):
304
285
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
355
336
        if ignore_misses is not True:
356
337
            for path in [p for p in paths if p not in selected_paths]:
357
338
                if not os.path.exists(tree.abspath(path)):
358
 
                    print("%s does not exist" % path)
 
339
                    print "%s does not exist" % path
359
340
                else:
360
 
                    print("%s is not conflicted" % path)
 
341
                    print "%s is not conflicted" % path
361
342
        return new_conflicts, selected_conflicts
362
343
 
363
344
 
369
350
 
370
351
    def __init__(self, path, file_id=None):
371
352
        self.path = path
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)
 
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)
377
356
 
378
357
    def as_stanza(self):
379
358
        s = rio.Stanza(type=self.typestring, path=self.path)
388
367
    def __cmp__(self, other):
389
368
        if getattr(other, "_cmp_list", None) is None:
390
369
            return -1
391
 
        x = self._cmp_list()
392
 
        y = other._cmp_list()
393
 
        return (x > y) - (x < y)
 
370
        return cmp(self._cmp_list(), other._cmp_list())
394
371
 
395
372
    def __hash__(self):
396
373
        return hash((type(self), self.path, self.file_id))
401
378
    def __ne__(self, other):
402
379
        return not self.__eq__(other)
403
380
 
404
 
    def __unicode__(self):
405
 
        return self.describe()
406
 
 
407
381
    def __str__(self):
408
 
        return self.describe()
409
 
 
410
 
    def describe(self):
411
382
        return self.format % self.__dict__
412
383
 
413
384
    def __repr__(self):
449
420
        for fname in self.associated_filenames():
450
421
            try:
451
422
                osutils.delete_any(tree.abspath(fname))
452
 
            except OSError as e:
 
423
            except OSError, e:
453
424
                if e.errno != errno.ENOENT:
454
425
                    raise
455
426
 
505
476
        path_to_create = None
506
477
        if winner == 'this':
507
478
            if self.path == '<deleted>':
508
 
                return  # Nothing to do
 
479
                return # Nothing to do
509
480
            if self.conflict_path == '<deleted>':
510
481
                path_to_create = self.path
511
482
                revid = tt._tree.get_parent_ids()[0]
523
494
            raise AssertionError('bad winner: %r' % (winner,))
524
495
        if path_to_create is not None:
525
496
            tid = tt.trans_id_tree_path(path_to_create)
526
 
            tree = self._revision_tree(tt._tree, revid)
527
497
            transform.create_from_tree(
528
 
                tt, tid, tree, tree.id2path(file_id), file_id=file_id)
 
498
                tt, tt.trans_id_tree_path(path_to_create),
 
499
                self._revision_tree(tt._tree, revid), file_id)
529
500
            tt.version_file(file_id, tid)
530
 
        else:
531
 
            tid = tt.trans_id_file_id(file_id)
 
501
 
532
502
        # Adjust the path for the retained file id
 
503
        tid = tt.trans_id_file_id(file_id)
533
504
        parent_tid = tt.get_tree_parent(tid)
534
 
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
 
505
        tt.adjust_path(path, parent_tid, tid)
535
506
        tt.apply()
536
507
 
537
508
    def _revision_tree(self, tree, revid):
544
515
        possible_paths = []
545
516
        for p in (self.path, self.conflict_path):
546
517
            if p == '<deleted>':
547
 
                # special hard-coded path
 
518
                # special hard-coded path 
548
519
                continue
549
520
            if p is not None:
550
521
                possible_paths.append(p)
600
571
        :param tt: The TreeTransform where the conflict is resolved.
601
572
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
602
573
 
603
 
        The resolution is symmetric: when taking THIS, OTHER is deleted and
 
574
        The resolution is symmetric, when taking THIS, OTHER is deleted and
604
575
        item.THIS is renamed into item and vice-versa.
605
576
        """
606
577
        try:
613
584
            # never existed or was already deleted (including the case
614
585
            # where the user deleted it)
615
586
            pass
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()
 
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()
633
593
 
634
594
    def action_take_this(self, tree):
635
595
        self._resolve_with_cleanups(tree, 'OTHER')
638
598
        self._resolve_with_cleanups(tree, 'THIS')
639
599
 
640
600
 
 
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
 
641
604
# TODO: There should be a base revid attribute to better inform the user about
642
605
# how the conflicts were generated.
643
 
class TextConflict(Conflict):
 
606
class TextConflict(PathConflict):
644
607
    """The merge algorithm could not resolve all differences encountered."""
645
608
 
646
609
    has_files = True
649
612
 
650
613
    format = 'Text conflict in %(path)s'
651
614
 
652
 
    rformat = '%(class)s(%(path)r, %(file_id)r)'
653
 
 
654
615
    def associated_filenames(self):
655
616
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
656
617
 
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
 
 
690
618
 
691
619
class HandledConflict(Conflict):
692
620
    """A path problem that has been provisionally resolved.
724
652
                 conflict_file_id=None):
725
653
        HandledConflict.__init__(self, action, path, file_id)
726
654
        self.conflict_path = conflict_path
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)
 
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)
732
659
 
733
660
    def _cmp_list(self):
734
661
        return HandledConflict._cmp_list(self) + [self.conflict_path,
782
709
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
783
710
 
784
711
    def action_take_this(self, tree):
785
 
        # just acccept brz proposal
 
712
        # just acccept bzr proposal
786
713
        pass
787
714
 
788
715
    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)
789
720
        tt = transform.TreeTransform(tree)
790
721
        try:
791
722
            p_tid = tt.trans_id_file_id(self.file_id)
792
723
            parent_tid = tt.get_tree_parent(p_tid)
793
724
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
794
725
            cparent_tid = tt.get_tree_parent(cp_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)
 
726
            tt.adjust_path(base_path, cparent_tid, cp_tid)
 
727
            tt.adjust_path(conflict_base_path, parent_tid, p_tid)
798
728
            tt.apply()
799
729
        finally:
800
730
            tt.finalize()
836
766
        tree.remove([self.path], force=True, keep_files=False)
837
767
 
838
768
    def action_take_other(self, tree):
839
 
        # just acccept brz proposal
 
769
        # just acccept bzr proposal
840
770
        pass
841
771
 
842
772
 
855
785
    # MissingParent from the *user* pov.
856
786
 
857
787
    def action_take_this(self, tree):
858
 
        # just acccept brz proposal
 
788
        # just acccept bzr proposal
859
789
        pass
860
790
 
861
791
    def action_take_other(self, tree):
902
832
    for conflict_type in conflict_types:
903
833
        ctype[conflict_type.typestring] = conflict_type
904
834
 
905
 
 
906
835
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
907
836
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
908
837
               DeletingParent, NonDirectoryParent)