/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
381
    def __str__(self):
406
 
        return self.describe()
407
 
 
408
 
    def describe(self):
409
382
        return self.format % self.__dict__
410
383
 
411
384
    def __repr__(self):
447
420
        for fname in self.associated_filenames():
448
421
            try:
449
422
                osutils.delete_any(tree.abspath(fname))
450
 
            except OSError as e:
 
423
            except OSError, e:
451
424
                if e.errno != errno.ENOENT:
452
425
                    raise
453
426
 
521
494
            raise AssertionError('bad winner: %r' % (winner,))
522
495
        if path_to_create is not None:
523
496
            tid = tt.trans_id_tree_path(path_to_create)
524
 
            tree = self._revision_tree(tt._tree, revid)
525
497
            transform.create_from_tree(
526
 
                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)
527
500
            tt.version_file(file_id, tid)
528
 
        else:
529
 
            tid = tt.trans_id_file_id(file_id)
 
501
 
530
502
        # Adjust the path for the retained file id
 
503
        tid = tt.trans_id_file_id(file_id)
531
504
        parent_tid = tt.get_tree_parent(tid)
532
 
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
 
505
        tt.adjust_path(path, parent_tid, tid)
533
506
        tt.apply()
534
507
 
535
508
    def _revision_tree(self, tree, revid):
598
571
        :param tt: The TreeTransform where the conflict is resolved.
599
572
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
600
573
 
601
 
        The resolution is symmetric: when taking THIS, OTHER is deleted and
 
574
        The resolution is symmetric, when taking THIS, OTHER is deleted and
602
575
        item.THIS is renamed into item and vice-versa.
603
576
        """
604
577
        try:
611
584
            # never existed or was already deleted (including the case
612
585
            # where the user deleted it)
613
586
            pass
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()
 
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()
631
593
 
632
594
    def action_take_this(self, tree):
633
595
        self._resolve_with_cleanups(tree, 'OTHER')
636
598
        self._resolve_with_cleanups(tree, 'THIS')
637
599
 
638
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
 
639
604
# TODO: There should be a base revid attribute to better inform the user about
640
605
# how the conflicts were generated.
641
 
class TextConflict(Conflict):
 
606
class TextConflict(PathConflict):
642
607
    """The merge algorithm could not resolve all differences encountered."""
643
608
 
644
609
    has_files = True
647
612
 
648
613
    format = 'Text conflict in %(path)s'
649
614
 
650
 
    rformat = '%(class)s(%(path)r, %(file_id)r)'
651
 
 
652
615
    def associated_filenames(self):
653
616
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
654
617
 
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
 
 
687
618
 
688
619
class HandledConflict(Conflict):
689
620
    """A path problem that has been provisionally resolved.
721
652
                 conflict_file_id=None):
722
653
        HandledConflict.__init__(self, action, path, file_id)
723
654
        self.conflict_path = conflict_path
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)
 
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)
729
659
 
730
660
    def _cmp_list(self):
731
661
        return HandledConflict._cmp_list(self) + [self.conflict_path,
779
709
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
780
710
 
781
711
    def action_take_this(self, tree):
782
 
        # just acccept brz proposal
 
712
        # just acccept bzr proposal
783
713
        pass
784
714
 
785
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)
786
720
        tt = transform.TreeTransform(tree)
787
721
        try:
788
722
            p_tid = tt.trans_id_file_id(self.file_id)
789
723
            parent_tid = tt.get_tree_parent(p_tid)
790
724
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
791
725
            cparent_tid = tt.get_tree_parent(cp_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)
 
726
            tt.adjust_path(base_path, cparent_tid, cp_tid)
 
727
            tt.adjust_path(conflict_base_path, parent_tid, p_tid)
795
728
            tt.apply()
796
729
        finally:
797
730
            tt.finalize()
833
766
        tree.remove([self.path], force=True, keep_files=False)
834
767
 
835
768
    def action_take_other(self, tree):
836
 
        # just acccept brz proposal
 
769
        # just acccept bzr proposal
837
770
        pass
838
771
 
839
772
 
852
785
    # MissingParent from the *user* pov.
853
786
 
854
787
    def action_take_this(self, tree):
855
 
        # just acccept brz proposal
 
788
        # just acccept bzr proposal
856
789
        pass
857
790
 
858
791
    def action_take_other(self, tree):