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