~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-04-06 06:59:03 UTC
  • mfrom: (5051.5.1 subunit)
  • Revision ID: pqm@pqm.ubuntu.com-20100406065903-y9dxgwmog1pmw7dz
Use subunit when running tests in PQM.

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
18
18
# point down
19
19
 
20
20
import os
 
21
import re
21
22
 
22
23
from bzrlib.lazy_import import lazy_import
23
24
lazy_import(globals(), """
24
25
import errno
25
26
 
26
27
from bzrlib import (
 
28
    builtins,
27
29
    cleanup,
 
30
    commands,
28
31
    errors,
29
32
    osutils,
30
33
    rio,
34
37
    )
35
38
""")
36
39
from bzrlib import (
37
 
    commands,
38
40
    option,
39
41
    registry,
40
42
    )
44
46
 
45
47
 
46
48
class cmd_conflicts(commands.Command):
47
 
    __doc__ = """List files with conflicts.
 
49
    """List files with conflicts.
48
50
 
49
51
    Merge will do its best to combine the changes in two branches, but there
50
52
    are some kinds of problems only a human can fix.  When it encounters those,
58
60
    Use bzr resolve when you have fixed a problem.
59
61
    """
60
62
    takes_options = [
61
 
            'directory',
62
63
            option.Option('text',
63
64
                          help='List paths of files with text conflicts.'),
64
65
        ]
65
66
    _see_also = ['resolve', 'conflict-types']
66
67
 
67
 
    def run(self, text=False, directory=u'.'):
68
 
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
68
    def run(self, text=False):
 
69
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
69
70
        for conflict in wt.conflicts():
70
71
            if text:
71
72
                if conflict.typestring != 'text conflict':
72
73
                    continue
73
74
                self.outf.write(conflict.path + '\n')
74
75
            else:
75
 
                self.outf.write(unicode(conflict) + '\n')
 
76
                self.outf.write(str(conflict) + '\n')
76
77
 
77
78
 
78
79
resolve_action_registry = registry.Registry()
98
99
 
99
100
 
100
101
class cmd_resolve(commands.Command):
101
 
    __doc__ = """Mark a conflict as resolved.
 
102
    """Mark a conflict as resolved.
102
103
 
103
104
    Merge will do its best to combine the changes in two branches, but there
104
105
    are some kinds of problems only a human can fix.  When it encounters those,
112
113
    aliases = ['resolved']
113
114
    takes_args = ['file*']
114
115
    takes_options = [
115
 
            'directory',
116
116
            option.Option('all', help='Resolve all conflicts in this tree.'),
117
117
            ResolveActionOption(),
118
118
            ]
119
119
    _see_also = ['conflicts']
120
 
    def run(self, file_list=None, all=False, action=None, directory=None):
 
120
    def run(self, file_list=None, all=False, action=None):
121
121
        if all:
122
122
            if file_list:
123
123
                raise errors.BzrCommandError("If --all is specified,"
124
124
                                             " no FILE may be provided")
125
 
            if directory is None:
126
 
                directory = u'.'
127
 
            tree = workingtree.WorkingTree.open_containing(directory)[0]
 
125
            tree = workingtree.WorkingTree.open_containing('.')[0]
128
126
            if action is None:
129
127
                action = 'done'
130
128
        else:
131
 
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
132
 
                file_list, directory)
 
129
            tree, file_list = builtins.tree_files(file_list)
133
130
            if file_list is None:
134
131
                if action is None:
135
132
                    # FIXME: There is a special case here related to the option
148
145
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
149
146
                    trace.note('Remaining conflicts:')
150
147
                    for conflict in un_resolved:
151
 
                        trace.note(unicode(conflict))
 
148
                        trace.note(conflict)
152
149
                    return 1
153
150
                else:
154
151
                    trace.note('All conflicts resolved.')
159
156
                # conflict.auto(tree) --vila 091242
160
157
                pass
161
158
        else:
162
 
            before, after = resolve(tree, file_list, action=action)
163
 
            trace.note('%d conflict(s) resolved, %d remaining'
164
 
                       % (before - after, after))
 
159
            resolve(tree, file_list, action=action)
165
160
 
166
161
 
167
162
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
180
175
    :param action: How the conflict should be resolved,
181
176
    """
182
177
    tree.lock_tree_write()
183
 
    nb_conflicts_after = None
184
178
    try:
185
179
        tree_conflicts = tree.conflicts()
186
 
        nb_conflicts_before = len(tree_conflicts)
187
180
        if paths is None:
188
181
            new_conflicts = ConflictList()
189
182
            to_process = tree_conflicts
197
190
            except NotImplementedError:
198
191
                new_conflicts.append(conflict)
199
192
        try:
200
 
            nb_conflicts_after = len(new_conflicts)
201
193
            tree.set_conflicts(new_conflicts)
202
194
        except errors.UnsupportedOperation:
203
195
            pass
204
196
    finally:
205
197
        tree.unlock()
206
 
    if nb_conflicts_after is None:
207
 
        nb_conflicts_after = nb_conflicts_before
208
 
    return nb_conflicts_before, nb_conflicts_after
209
198
 
210
199
 
211
200
def restore(filename):
291
280
    def to_strings(self):
292
281
        """Generate strings for the provided conflicts"""
293
282
        for conflict in self:
294
 
            yield unicode(conflict)
 
283
            yield str(conflict)
295
284
 
296
285
    def remove_files(self, tree):
297
286
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
390
379
    def __ne__(self, other):
391
380
        return not self.__eq__(other)
392
381
 
393
 
    def __unicode__(self):
 
382
    def __str__(self):
394
383
        return self.format % self.__dict__
395
384
 
396
385
    def __repr__(self):
507
496
        if path_to_create is not None:
508
497
            tid = tt.trans_id_tree_path(path_to_create)
509
498
            transform.create_from_tree(
510
 
                tt, tid, self._revision_tree(tt._tree, revid), file_id)
 
499
                tt, tt.trans_id_tree_path(path_to_create),
 
500
                self._revision_tree(tt._tree, revid), file_id)
511
501
            tt.version_file(file_id, tid)
512
 
        else:
513
 
            tid = tt.trans_id_file_id(file_id)
 
502
 
514
503
        # Adjust the path for the retained file id
 
504
        tid = tt.trans_id_file_id(file_id)
515
505
        parent_tid = tt.get_tree_parent(tid)
516
 
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
 
506
        tt.adjust_path(path, parent_tid, tid)
517
507
        tt.apply()
518
508
 
519
509
    def _revision_tree(self, tree, revid):
582
572
        :param tt: The TreeTransform where the conflict is resolved.
583
573
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
584
574
 
585
 
        The resolution is symmetric: when taking THIS, OTHER is deleted and
 
575
        The resolution is symmetric, when taking THIS, OTHER is deleted and
586
576
        item.THIS is renamed into item and vice-versa.
587
577
        """
588
578
        try:
595
585
            # never existed or was already deleted (including the case
596
586
            # where the user deleted it)
597
587
            pass
598
 
        try:
599
 
            this_path = tt._tree.id2path(self.file_id)
600
 
        except errors.NoSuchId:
601
 
            # The file is not present anymore. This may happen if the user
602
 
            # deleted the file either manually or when resolving a conflict on
603
 
            # the parent.  We may raise some exception to indicate that the
604
 
            # conflict doesn't exist anymore and as such doesn't need to be
605
 
            # resolved ? -- vila 20110615 
606
 
            this_tid = None
607
 
        else:
608
 
            this_tid = tt.trans_id_tree_path(this_path)
609
 
        if this_tid is not None:
610
 
            # Rename 'item.suffix_to_remove' (note that if
611
 
            # 'item.suffix_to_remove' has been deleted, this is a no-op)
612
 
            parent_tid = tt.get_tree_parent(this_tid)
613
 
            tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
614
 
            tt.apply()
 
588
        # Rename 'item.suffix_to_remove' (note that if
 
589
        # 'item.suffix_to_remove' has been deleted, this is a no-op)
 
590
        this_tid = tt.trans_id_file_id(self.file_id)
 
591
        parent_tid = tt.get_tree_parent(this_tid)
 
592
        tt.adjust_path(self.path, parent_tid, this_tid)
 
593
        tt.apply()
615
594
 
616
595
    def action_take_this(self, tree):
617
596
        self._resolve_with_cleanups(tree, 'OTHER')
620
599
        self._resolve_with_cleanups(tree, 'THIS')
621
600
 
622
601
 
 
602
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
 
603
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
 
604
 
623
605
# TODO: There should be a base revid attribute to better inform the user about
624
606
# how the conflicts were generated.
625
 
class TextConflict(Conflict):
 
607
class TextConflict(PathConflict):
626
608
    """The merge algorithm could not resolve all differences encountered."""
627
609
 
628
610
    has_files = True
631
613
 
632
614
    format = 'Text conflict in %(path)s'
633
615
 
634
 
    rformat = '%(class)s(%(path)r, %(file_id)r)'
635
 
 
636
616
    def associated_filenames(self):
637
617
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
638
618
 
639
 
    def _resolve(self, tt, winner_suffix):
640
 
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
641
 
 
642
 
        :param tt: The TreeTransform where the conflict is resolved.
643
 
        :param winner_suffix: Either 'THIS' or 'OTHER'
644
 
 
645
 
        The resolution is symmetric, when taking THIS, item.THIS is renamed
646
 
        into item and vice-versa. This takes one of the files as a whole
647
 
        ignoring every difference that could have been merged cleanly.
648
 
        """
649
 
        # To avoid useless copies, we switch item and item.winner_suffix, only
650
 
        # item will exist after the conflict has been resolved anyway.
651
 
        item_tid = tt.trans_id_file_id(self.file_id)
652
 
        item_parent_tid = tt.get_tree_parent(item_tid)
653
 
        winner_path = self.path + '.' + winner_suffix
654
 
        winner_tid = tt.trans_id_tree_path(winner_path)
655
 
        winner_parent_tid = tt.get_tree_parent(winner_tid)
656
 
        # Switch the paths to preserve the content
657
 
        tt.adjust_path(osutils.basename(self.path),
658
 
                       winner_parent_tid, winner_tid)
659
 
        tt.adjust_path(osutils.basename(winner_path), item_parent_tid, item_tid)
660
 
        # Associate the file_id to the right content
661
 
        tt.unversion_file(item_tid)
662
 
        tt.version_file(self.file_id, winner_tid)
663
 
        tt.apply()
664
 
 
665
 
    def action_take_this(self, tree):
666
 
        self._resolve_with_cleanups(tree, 'THIS')
667
 
 
668
 
    def action_take_other(self, tree):
669
 
        self._resolve_with_cleanups(tree, 'OTHER')
670
 
 
671
619
 
672
620
class HandledConflict(Conflict):
673
621
    """A path problem that has been provisionally resolved.
766
714
        pass
767
715
 
768
716
    def action_take_other(self, tree):
 
717
        # FIXME: We shouldn't have to manipulate so many paths here (and there
 
718
        # is probably a bug or two...)
 
719
        base_path = osutils.basename(self.path)
 
720
        conflict_base_path = osutils.basename(self.conflict_path)
769
721
        tt = transform.TreeTransform(tree)
770
722
        try:
771
723
            p_tid = tt.trans_id_file_id(self.file_id)
772
724
            parent_tid = tt.get_tree_parent(p_tid)
773
725
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
774
726
            cparent_tid = tt.get_tree_parent(cp_tid)
775
 
            tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
776
 
            tt.adjust_path(osutils.basename(self.conflict_path),
777
 
                           parent_tid, p_tid)
 
727
            tt.adjust_path(base_path, cparent_tid, cp_tid)
 
728
            tt.adjust_path(conflict_base_path, parent_tid, p_tid)
778
729
            tt.apply()
779
730
        finally:
780
731
            tt.finalize()