~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-09 17:13:04 UTC
  • mto: (5029.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5030.
  • Revision ID: v.ladeuil+lp@free.fr-20100209171304-2ppoju422x02s7fm
Move MemoryServer to bzrlib.tests.test_server

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, 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 (
27
 
    cleanup,
 
28
    builtins,
28
29
    commands,
29
30
    errors,
30
31
    osutils,
44
45
 
45
46
 
46
47
class cmd_conflicts(commands.Command):
47
 
    __doc__ = """List files with conflicts.
 
48
    """List files with conflicts.
48
49
 
49
50
    Merge will do its best to combine the changes in two branches, but there
50
51
    are some kinds of problems only a human can fix.  When it encounters those,
58
59
    Use bzr resolve when you have fixed a problem.
59
60
    """
60
61
    takes_options = [
61
 
            'directory',
62
62
            option.Option('text',
63
63
                          help='List paths of files with text conflicts.'),
64
64
        ]
65
65
    _see_also = ['resolve', 'conflict-types']
66
66
 
67
 
    def run(self, text=False, directory=u'.'):
68
 
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
67
    def run(self, text=False):
 
68
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
69
69
        for conflict in wt.conflicts():
70
70
            if text:
71
71
                if conflict.typestring != 'text conflict':
98
98
 
99
99
 
100
100
class cmd_resolve(commands.Command):
101
 
    __doc__ = """Mark a conflict as resolved.
 
101
    """Mark a conflict as resolved.
102
102
 
103
103
    Merge will do its best to combine the changes in two branches, but there
104
104
    are some kinds of problems only a human can fix.  When it encounters those,
112
112
    aliases = ['resolved']
113
113
    takes_args = ['file*']
114
114
    takes_options = [
115
 
            'directory',
116
115
            option.Option('all', help='Resolve all conflicts in this tree.'),
117
116
            ResolveActionOption(),
118
117
            ]
119
118
    _see_also = ['conflicts']
120
 
    def run(self, file_list=None, all=False, action=None, directory=u'.'):
 
119
    def run(self, file_list=None, all=False, action=None):
121
120
        if all:
122
121
            if file_list:
123
122
                raise errors.BzrCommandError("If --all is specified,"
124
123
                                             " no FILE may be provided")
125
 
            tree = workingtree.WorkingTree.open_containing(directory)[0]
 
124
            tree = workingtree.WorkingTree.open_containing('.')[0]
126
125
            if action is None:
127
126
                action = 'done'
128
127
        else:
129
 
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
130
 
                file_list)
 
128
            tree, file_list = builtins.tree_files(file_list)
131
129
            if file_list is None:
132
130
                if action is None:
133
131
                    # FIXME: There is a special case here related to the option
414
412
            raise NotImplementedError(self.__class__.__name__ + '.' + action)
415
413
        meth(tree)
416
414
 
417
 
    def associated_filenames(self):
418
 
        """The names of the files generated to help resolve the conflict."""
419
 
        raise NotImplementedError(self.associated_filenames)
420
 
 
421
415
    def cleanup(self, tree):
422
 
        for fname in self.associated_filenames():
423
 
            try:
424
 
                osutils.delete_any(tree.abspath(fname))
425
 
            except OSError, e:
426
 
                if e.errno != errno.ENOENT:
427
 
                    raise
 
416
        raise NotImplementedError(self.cleanup)
428
417
 
429
418
    def action_done(self, tree):
430
419
        """Mark the conflict as solved once it has been handled."""
437
426
    def action_take_other(self, tree):
438
427
        raise NotImplementedError(self.action_take_other)
439
428
 
440
 
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
441
 
        tt = transform.TreeTransform(tree)
442
 
        op = cleanup.OperationWithCleanups(self._resolve)
443
 
        op.add_cleanup(tt.finalize)
444
 
        op.run_simple(tt, *args, **kwargs)
445
 
 
446
429
 
447
430
class PathConflict(Conflict):
448
431
    """A conflict was encountered merging file paths"""
463
446
            s.add('conflict_path', self.conflict_path)
464
447
        return s
465
448
 
466
 
    def associated_filenames(self):
 
449
    def cleanup(self, tree):
467
450
        # No additional files have been generated here
468
 
        return []
469
 
 
470
 
    def _resolve(self, tt, file_id, path, winner):
471
 
        """Resolve the conflict.
472
 
 
473
 
        :param tt: The TreeTransform where the conflict is resolved.
474
 
        :param file_id: The retained file id.
475
 
        :param path: The retained path.
476
 
        :param winner: 'this' or 'other' indicates which side is the winner.
477
 
        """
478
 
        path_to_create = None
479
 
        if winner == 'this':
480
 
            if self.path == '<deleted>':
481
 
                return # Nothing to do
482
 
            if self.conflict_path == '<deleted>':
483
 
                path_to_create = self.path
484
 
                revid = tt._tree.get_parent_ids()[0]
485
 
        elif winner == 'other':
486
 
            if self.conflict_path == '<deleted>':
487
 
                return  # Nothing to do
488
 
            if self.path == '<deleted>':
489
 
                path_to_create = self.conflict_path
490
 
                # FIXME: If there are more than two parents we may need to
491
 
                # iterate. Taking the last parent is the safer bet in the mean
492
 
                # time. -- vila 20100309
493
 
                revid = tt._tree.get_parent_ids()[-1]
494
 
        else:
495
 
            # Programmer error
496
 
            raise AssertionError('bad winner: %r' % (winner,))
497
 
        if path_to_create is not None:
498
 
            tid = tt.trans_id_tree_path(path_to_create)
499
 
            transform.create_from_tree(
500
 
                tt, tt.trans_id_tree_path(path_to_create),
501
 
                self._revision_tree(tt._tree, revid), file_id)
502
 
            tt.version_file(file_id, tid)
503
 
 
504
 
        # Adjust the path for the retained file id
505
 
        tid = tt.trans_id_file_id(file_id)
506
 
        parent_tid = tt.get_tree_parent(tid)
507
 
        tt.adjust_path(path, parent_tid, tid)
508
 
        tt.apply()
509
 
 
510
 
    def _revision_tree(self, tree, revid):
511
 
        return tree.branch.repository.revision_tree(revid)
512
 
 
513
 
    def _infer_file_id(self, tree):
514
 
        # Prior to bug #531967, file_id wasn't always set, there may still be
515
 
        # conflict files in the wild so we need to cope with them
516
 
        # Establish which path we should use to find back the file-id
517
 
        possible_paths = []
518
 
        for p in (self.path, self.conflict_path):
519
 
            if p == '<deleted>':
520
 
                # special hard-coded path 
521
 
                continue
522
 
            if p is not None:
523
 
                possible_paths.append(p)
524
 
        # Search the file-id in the parents with any path available
525
 
        file_id = None
526
 
        for revid in tree.get_parent_ids():
527
 
            revtree = self._revision_tree(tree, revid)
528
 
            for p in possible_paths:
529
 
                file_id = revtree.path2id(p)
530
 
                if file_id is not None:
531
 
                    return revtree, file_id
532
 
        return None, None
 
451
        pass
533
452
 
534
453
    def action_take_this(self, tree):
535
 
        if self.file_id is not None:
536
 
            self._resolve_with_cleanups(tree, self.file_id, self.path,
537
 
                                        winner='this')
538
 
        else:
539
 
            # Prior to bug #531967 we need to find back the file_id and restore
540
 
            # the content from there
541
 
            revtree, file_id = self._infer_file_id(tree)
542
 
            tree.revert([revtree.id2path(file_id)],
543
 
                        old_tree=revtree, backups=False)
 
454
        tree.rename_one(self.conflict_path, self.path)
544
455
 
545
456
    def action_take_other(self, tree):
546
 
        if self.file_id is not None:
547
 
            self._resolve_with_cleanups(tree, self.file_id,
548
 
                                        self.conflict_path,
549
 
                                        winner='other')
550
 
        else:
551
 
            # Prior to bug #531967 we need to find back the file_id and restore
552
 
            # the content from there
553
 
            revtree, file_id = self._infer_file_id(tree)
554
 
            tree.revert([revtree.id2path(file_id)],
555
 
                        old_tree=revtree, backups=False)
 
457
        # just acccept bzr proposal
 
458
        pass
556
459
 
557
460
 
558
461
class ContentsConflict(PathConflict):
559
 
    """The files are of different types (or both binary), or not present"""
 
462
    """The files are of different types, or not present"""
560
463
 
561
464
    has_files = True
562
465
 
564
467
 
565
468
    format = 'Contents conflict in %(path)s'
566
469
 
567
 
    def associated_filenames(self):
568
 
        return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
569
 
 
570
 
    def _resolve(self, tt, suffix_to_remove):
571
 
        """Resolve the conflict.
572
 
 
573
 
        :param tt: The TreeTransform where the conflict is resolved.
574
 
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
575
 
 
576
 
        The resolution is symmetric, when taking THIS, OTHER is deleted and
577
 
        item.THIS is renamed into item and vice-versa.
578
 
        """
579
 
        try:
580
 
            # Delete 'item.THIS' or 'item.OTHER' depending on
581
 
            # suffix_to_remove
582
 
            tt.delete_contents(
583
 
                tt.trans_id_tree_path(self.path + '.' + suffix_to_remove))
584
 
        except errors.NoSuchFile:
585
 
            # There are valid cases where 'item.suffix_to_remove' either
586
 
            # never existed or was already deleted (including the case
587
 
            # where the user deleted it)
588
 
            pass
589
 
        # Rename 'item.suffix_to_remove' (note that if
590
 
        # 'item.suffix_to_remove' has been deleted, this is a no-op)
591
 
        this_tid = tt.trans_id_file_id(self.file_id)
592
 
        parent_tid = tt.get_tree_parent(this_tid)
593
 
        tt.adjust_path(self.path, parent_tid, this_tid)
594
 
        tt.apply()
595
 
 
 
470
    def cleanup(self, tree):
 
471
        for suffix in ('.BASE', '.OTHER'):
 
472
            try:
 
473
                osutils.delete_any(tree.abspath(self.path + suffix))
 
474
            except OSError, e:
 
475
                if e.errno != errno.ENOENT:
 
476
                    raise
 
477
 
 
478
    # FIXME: I smell something weird here and it seems we should be able to be
 
479
    # more coherent with some other conflict ? bzr *did* a choice there but
 
480
    # neither action_take_this nor action_take_other reflect that...
 
481
    # -- vila 20091224
596
482
    def action_take_this(self, tree):
597
 
        self._resolve_with_cleanups(tree, 'OTHER')
 
483
        tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
598
484
 
599
485
    def action_take_other(self, tree):
600
 
        self._resolve_with_cleanups(tree, 'THIS')
 
486
        tree.remove([self.path], force=True, keep_files=False)
 
487
 
601
488
 
602
489
 
603
490
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
614
501
 
615
502
    format = 'Text conflict in %(path)s'
616
503
 
617
 
    def associated_filenames(self):
618
 
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
 
504
    def cleanup(self, tree):
 
505
        for suffix in CONFLICT_SUFFIXES:
 
506
            try:
 
507
                osutils.delete_any(tree.abspath(self.path+suffix))
 
508
            except OSError, e:
 
509
                if e.errno != errno.ENOENT:
 
510
                    raise
619
511
 
620
512
 
621
513
class HandledConflict(Conflict):
637
529
        s.add('action', self.action)
638
530
        return s
639
531
 
640
 
    def associated_filenames(self):
641
 
        # Nothing has been generated here
642
 
        return []
 
532
    def cleanup(self, tree):
 
533
        """Nothing to cleanup."""
 
534
        pass
643
535
 
644
536
 
645
537
class HandledPathConflict(HandledConflict):
708
600
 
709
601
    typestring = 'parent loop'
710
602
 
711
 
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
 
603
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
712
604
 
713
605
    def action_take_this(self, tree):
714
606
        # just acccept bzr proposal