~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

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
17
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
 
20
22
import os
21
23
 
22
24
from bzrlib.lazy_import import lazy_import
24
26
import errno
25
27
 
26
28
from bzrlib import (
27
 
    builtins,
28
29
    cleanup,
29
 
    commands,
30
30
    errors,
31
31
    osutils,
32
32
    rio,
34
34
    transform,
35
35
    workingtree,
36
36
    )
 
37
from bzrlib.i18n import gettext, ngettext
37
38
""")
38
39
from bzrlib import (
 
40
    commands,
39
41
    option,
40
42
    registry,
41
43
    )
59
61
    Use bzr resolve when you have fixed a problem.
60
62
    """
61
63
    takes_options = [
 
64
            'directory',
62
65
            option.Option('text',
63
66
                          help='List paths of files with text conflicts.'),
64
67
        ]
65
68
    _see_also = ['resolve', 'conflict-types']
66
69
 
67
 
    def run(self, text=False):
68
 
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
 
70
    def run(self, text=False, directory=u'.'):
 
71
        wt = workingtree.WorkingTree.open_containing(directory)[0]
69
72
        for conflict in wt.conflicts():
70
73
            if text:
71
74
                if conflict.typestring != 'text conflict':
72
75
                    continue
73
76
                self.outf.write(conflict.path + '\n')
74
77
            else:
75
 
                self.outf.write(str(conflict) + '\n')
 
78
                self.outf.write(unicode(conflict) + '\n')
76
79
 
77
80
 
78
81
resolve_action_registry = registry.Registry()
79
82
 
80
83
 
81
84
resolve_action_registry.register(
82
 
    'done', 'done', 'Marks the conflict as resolved' )
 
85
    'done', 'done', 'Marks the conflict as resolved.')
83
86
resolve_action_registry.register(
84
87
    'take-this', 'take_this',
85
 
    'Resolve the conflict preserving the version in the working tree' )
 
88
    'Resolve the conflict preserving the version in the working tree.')
86
89
resolve_action_registry.register(
87
90
    'take-other', 'take_other',
88
 
    'Resolve the conflict taking the merged version into account' )
 
91
    'Resolve the conflict taking the merged version into account.')
89
92
resolve_action_registry.default_key = 'done'
90
93
 
91
94
class ResolveActionOption(option.RegistryOption):
112
115
    aliases = ['resolved']
113
116
    takes_args = ['file*']
114
117
    takes_options = [
 
118
            'directory',
115
119
            option.Option('all', help='Resolve all conflicts in this tree.'),
116
120
            ResolveActionOption(),
117
121
            ]
118
122
    _see_also = ['conflicts']
119
 
    def run(self, file_list=None, all=False, action=None):
 
123
    def run(self, file_list=None, all=False, action=None, directory=None):
120
124
        if all:
121
125
            if file_list:
122
 
                raise errors.BzrCommandError("If --all is specified,"
123
 
                                             " no FILE may be provided")
124
 
            tree = workingtree.WorkingTree.open_containing('.')[0]
 
126
                raise errors.BzrCommandError(gettext("If --all is specified,"
 
127
                                             " no FILE may be provided"))
 
128
            if directory is None:
 
129
                directory = u'.'
 
130
            tree = workingtree.WorkingTree.open_containing(directory)[0]
125
131
            if action is None:
126
132
                action = 'done'
127
133
        else:
128
 
            tree, file_list = builtins.tree_files(file_list)
 
134
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
 
135
                file_list, directory)
129
136
            if file_list is None:
130
137
                if action is None:
131
138
                    # FIXME: There is a special case here related to the option
141
148
            if file_list is None:
142
149
                un_resolved, resolved = tree.auto_resolve()
143
150
                if len(un_resolved) > 0:
144
 
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
145
 
                    trace.note('Remaining conflicts:')
 
151
                    trace.note(ngettext('%d conflict auto-resolved.',
 
152
                        '%d conflicts auto-resolved.', len(resolved)),
 
153
                        len(resolved))
 
154
                    trace.note(gettext('Remaining conflicts:'))
146
155
                    for conflict in un_resolved:
147
 
                        trace.note(conflict)
 
156
                        trace.note(unicode(conflict))
148
157
                    return 1
149
158
                else:
150
 
                    trace.note('All conflicts resolved.')
 
159
                    trace.note(gettext('All conflicts resolved.'))
151
160
                    return 0
152
161
            else:
153
162
                # FIXME: This can never occur but the block above needs some
155
164
                # conflict.auto(tree) --vila 091242
156
165
                pass
157
166
        else:
158
 
            resolve(tree, file_list, action=action)
 
167
            before, after = resolve(tree, file_list, action=action)
 
168
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
 
169
                                '{0} conflicts resolved, {1} remaining',
 
170
                                before-after).format(before - after, after))
159
171
 
160
172
 
161
173
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
174
186
    :param action: How the conflict should be resolved,
175
187
    """
176
188
    tree.lock_tree_write()
 
189
    nb_conflicts_after = None
177
190
    try:
178
191
        tree_conflicts = tree.conflicts()
 
192
        nb_conflicts_before = len(tree_conflicts)
179
193
        if paths is None:
180
194
            new_conflicts = ConflictList()
181
195
            to_process = tree_conflicts
189
203
            except NotImplementedError:
190
204
                new_conflicts.append(conflict)
191
205
        try:
 
206
            nb_conflicts_after = len(new_conflicts)
192
207
            tree.set_conflicts(new_conflicts)
193
208
        except errors.UnsupportedOperation:
194
209
            pass
195
210
    finally:
196
211
        tree.unlock()
 
212
    if nb_conflicts_after is None:
 
213
        nb_conflicts_after = nb_conflicts_before
 
214
    return nb_conflicts_before, nb_conflicts_after
197
215
 
198
216
 
199
217
def restore(filename):
279
297
    def to_strings(self):
280
298
        """Generate strings for the provided conflicts"""
281
299
        for conflict in self:
282
 
            yield str(conflict)
 
300
            yield unicode(conflict)
283
301
 
284
302
    def remove_files(self, tree):
285
303
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
378
396
    def __ne__(self, other):
379
397
        return not self.__eq__(other)
380
398
 
381
 
    def __str__(self):
 
399
    def __unicode__(self):
382
400
        return self.format % self.__dict__
383
401
 
384
402
    def __repr__(self):
495
513
        if path_to_create is not None:
496
514
            tid = tt.trans_id_tree_path(path_to_create)
497
515
            transform.create_from_tree(
498
 
                tt, tt.trans_id_tree_path(path_to_create),
499
 
                self._revision_tree(tt._tree, revid), file_id)
 
516
                tt, tid, self._revision_tree(tt._tree, revid), file_id)
500
517
            tt.version_file(file_id, tid)
501
 
 
 
518
        else:
 
519
            tid = tt.trans_id_file_id(file_id)
502
520
        # Adjust the path for the retained file id
503
 
        tid = tt.trans_id_file_id(file_id)
504
521
        parent_tid = tt.get_tree_parent(tid)
505
 
        tt.adjust_path(path, parent_tid, tid)
 
522
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
506
523
        tt.apply()
507
524
 
508
525
    def _revision_tree(self, tree, revid):
571
588
        :param tt: The TreeTransform where the conflict is resolved.
572
589
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
573
590
 
574
 
        The resolution is symmetric, when taking THIS, OTHER is deleted and
 
591
        The resolution is symmetric: when taking THIS, OTHER is deleted and
575
592
        item.THIS is renamed into item and vice-versa.
576
593
        """
577
594
        try:
584
601
            # never existed or was already deleted (including the case
585
602
            # where the user deleted it)
586
603
            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()
 
604
        try:
 
605
            this_path = tt._tree.id2path(self.file_id)
 
606
        except errors.NoSuchId:
 
607
            # The file is not present anymore. This may happen if the user
 
608
            # deleted the file either manually or when resolving a conflict on
 
609
            # the parent.  We may raise some exception to indicate that the
 
610
            # conflict doesn't exist anymore and as such doesn't need to be
 
611
            # resolved ? -- vila 20110615 
 
612
            this_tid = None
 
613
        else:
 
614
            this_tid = tt.trans_id_tree_path(this_path)
 
615
        if this_tid is not None:
 
616
            # Rename 'item.suffix_to_remove' (note that if
 
617
            # 'item.suffix_to_remove' has been deleted, this is a no-op)
 
618
            parent_tid = tt.get_tree_parent(this_tid)
 
619
            tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
 
620
            tt.apply()
593
621
 
594
622
    def action_take_this(self, tree):
595
623
        self._resolve_with_cleanups(tree, 'OTHER')
598
626
        self._resolve_with_cleanups(tree, 'THIS')
599
627
 
600
628
 
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
629
# TODO: There should be a base revid attribute to better inform the user about
605
630
# how the conflicts were generated.
606
 
class TextConflict(PathConflict):
 
631
class TextConflict(Conflict):
607
632
    """The merge algorithm could not resolve all differences encountered."""
608
633
 
609
634
    has_files = True
612
637
 
613
638
    format = 'Text conflict in %(path)s'
614
639
 
 
640
    rformat = '%(class)s(%(path)r, %(file_id)r)'
 
641
 
615
642
    def associated_filenames(self):
616
643
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
617
644
 
 
645
    def _resolve(self, tt, winner_suffix):
 
646
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
 
647
 
 
648
        :param tt: The TreeTransform where the conflict is resolved.
 
649
        :param winner_suffix: Either 'THIS' or 'OTHER'
 
650
 
 
651
        The resolution is symmetric, when taking THIS, item.THIS is renamed
 
652
        into item and vice-versa. This takes one of the files as a whole
 
653
        ignoring every difference that could have been merged cleanly.
 
654
        """
 
655
        # To avoid useless copies, we switch item and item.winner_suffix, only
 
656
        # item will exist after the conflict has been resolved anyway.
 
657
        item_tid = tt.trans_id_file_id(self.file_id)
 
658
        item_parent_tid = tt.get_tree_parent(item_tid)
 
659
        winner_path = self.path + '.' + winner_suffix
 
660
        winner_tid = tt.trans_id_tree_path(winner_path)
 
661
        winner_parent_tid = tt.get_tree_parent(winner_tid)
 
662
        # Switch the paths to preserve the content
 
663
        tt.adjust_path(osutils.basename(self.path),
 
664
                       winner_parent_tid, winner_tid)
 
665
        tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
 
666
        # Associate the file_id to the right content
 
667
        tt.unversion_file(item_tid)
 
668
        tt.version_file(self.file_id, winner_tid)
 
669
        tt.apply()
 
670
 
 
671
    def action_take_this(self, tree):
 
672
        self._resolve_with_cleanups(tree, 'THIS')
 
673
 
 
674
    def action_take_other(self, tree):
 
675
        self._resolve_with_cleanups(tree, 'OTHER')
 
676
 
618
677
 
619
678
class HandledConflict(Conflict):
620
679
    """A path problem that has been provisionally resolved.
713
772
        pass
714
773
 
715
774
    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
775
        tt = transform.TreeTransform(tree)
721
776
        try:
722
777
            p_tid = tt.trans_id_file_id(self.file_id)
723
778
            parent_tid = tt.get_tree_parent(p_tid)
724
779
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
725
780
            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)
 
781
            tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
 
782
            tt.adjust_path(osutils.basename(self.conflict_path),
 
783
                           parent_tid, p_tid)
728
784
            tt.apply()
729
785
        finally:
730
786
            tt.finalize()