/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/transform.py

Add finished() notifications to transactions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
                           ReusingTransform, NotVersionedError, CantMoveRoot,
23
23
                           ExistingLimbo, ImmortalLimbo)
24
24
from bzrlib.inventory import InventoryEntry
25
 
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
26
 
                            delete_any)
27
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
 
26
from bzrlib.progress import DummyProgress
28
27
from bzrlib.trace import mutter, warning
29
 
import bzrlib.ui 
30
28
 
31
29
 
32
30
ROOT_PARENT = "root-parent"
287
285
        os.symlink(target, self._limbo_name(trans_id))
288
286
        unique_add(self._new_contents, trans_id, 'symlink')
289
287
 
 
288
    @staticmethod
 
289
    def delete_any(full_path):
 
290
        """Delete a file or directory."""
 
291
        try:
 
292
            os.unlink(full_path)
 
293
        except OSError, e:
 
294
        # We may be renaming a dangling inventory id
 
295
            if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
 
296
                raise
 
297
            os.rmdir(full_path)
 
298
 
290
299
    def cancel_creation(self, trans_id):
291
300
        """Cancel the creation of new file contents."""
292
301
        del self._new_contents[trans_id]
293
 
        delete_any(self._limbo_name(trans_id))
 
302
        self.delete_any(self._limbo_name(trans_id))
294
303
 
295
304
    def delete_contents(self, trans_id):
296
305
        """Schedule the contents of a path entry for deletion"""
428
437
        except KeyError:
429
438
            return os.path.basename(self._tree_id_paths[trans_id])
430
439
 
431
 
    def by_parent(self):
 
440
    def _by_parent(self):
432
441
        """Return a map of parent: children for known parents.
433
442
        
434
443
        Only new paths and parents of tree files with assigned ids are used.
455
464
        # ensure all children of all existent parents are known
456
465
        # all children of non-existent parents are known, by definition.
457
466
        self._add_tree_children()
458
 
        by_parent = self.by_parent()
 
467
        by_parent = self._by_parent()
459
468
        conflicts.extend(self._unversioned_parents(by_parent))
460
469
        conflicts.extend(self._parent_loops())
461
470
        conflicts.extend(self._duplicate_entries(by_parent))
472
481
        Active parents are those which gain children, and those which are
473
482
        removed.  This is a necessary first step in detecting conflicts.
474
483
        """
475
 
        parents = self.by_parent().keys()
 
484
        parents = self._by_parent().keys()
476
485
        parents.extend([t for t in self._removed_contents if 
477
486
                        self.tree_kind(t) == 'directory'])
478
487
        for trans_id in self._removed_id:
504
513
                continue
505
514
            yield self.trans_id_tree_path(childpath)
506
515
 
507
 
    def has_named_child(self, by_parent, parent_id, name):
508
 
        try:
509
 
            children = by_parent[parent_id]
510
 
        except KeyError:
511
 
            children = []
512
 
        for child in children:
513
 
            if self.final_name(child) == name:
514
 
                return True
515
 
        try:
516
 
            path = self._tree_id_paths[parent_id]
517
 
        except KeyError:
518
 
            return False
519
 
        childpath = joinpath(path, name)
520
 
        child_id = self._tree_path_ids.get(childpath)
521
 
        if child_id is None:
522
 
            return lexists(self._tree.abspath(childpath))
523
 
        else:
524
 
            if tt.final_parent(child_id) != parent_id:
525
 
                return False
526
 
            if child_id in tt._removed_contents:
527
 
                # XXX What about dangling file-ids?
528
 
                return False
529
 
            else:
530
 
                return True
531
 
 
532
516
    def _parent_loops(self):
533
517
        """No entry should be its own ancestor"""
534
518
        conflicts = []
620
604
                if name == last_name:
621
605
                    conflicts.append(('duplicate', last_trans_id, trans_id,
622
606
                    name))
623
 
                try:
624
 
                    kind = self.final_kind(trans_id)
625
 
                except NoSuchFile:
626
 
                    kind = None
627
 
                file_id = self.final_file_id(trans_id)
628
 
                if kind is not None or file_id is not None:
629
 
                    last_name = name
630
 
                    last_trans_id = trans_id
 
607
                last_name = name
 
608
                last_trans_id = trans_id
631
609
        return conflicts
632
610
 
633
611
    def _duplicate_ids(self):
687
665
            raise MalformedTransform(conflicts=conflicts)
688
666
        limbo_inv = {}
689
667
        inv = self._tree.inventory
690
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
691
 
        try:
692
 
            child_pb.update('Apply phase', 0, 2)
693
 
            self._apply_removals(inv, limbo_inv)
694
 
            child_pb.update('Apply phase', 1, 2)
695
 
            modified_paths = self._apply_insertions(inv, limbo_inv)
696
 
        finally:
697
 
            child_pb.finished()
 
668
        self._apply_removals(inv, limbo_inv)
 
669
        modified_paths = self._apply_insertions(inv, limbo_inv)
698
670
        self._tree._write_inventory(inv)
699
671
        self.__done = True
700
672
        self.finalize()
713
685
        """
714
686
        tree_paths = list(self._tree_path_ids.iteritems())
715
687
        tree_paths.sort(reverse=True)
716
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
717
 
        try:
718
 
            for num, data in enumerate(tree_paths):
719
 
                path, trans_id = data
720
 
                child_pb.update('removing file', num, len(tree_paths))
721
 
                full_path = self._tree.abspath(path)
722
 
                if trans_id in self._removed_contents:
723
 
                    delete_any(full_path)
724
 
                elif trans_id in self._new_name or trans_id in \
725
 
                    self._new_parent:
726
 
                    try:
727
 
                        os.rename(full_path, self._limbo_name(trans_id))
728
 
                    except OSError, e:
729
 
                        if e.errno != errno.ENOENT:
730
 
                            raise
731
 
                if trans_id in self._removed_id:
732
 
                    if trans_id == self._new_root:
733
 
                        file_id = self._tree.inventory.root.file_id
734
 
                    else:
735
 
                        file_id = self.tree_file_id(trans_id)
 
688
        for num, data in enumerate(tree_paths):
 
689
            path, trans_id = data
 
690
            self._pb.update('removing file', num+1, len(tree_paths))
 
691
            full_path = self._tree.abspath(path)
 
692
            if trans_id in self._removed_contents:
 
693
                self.delete_any(full_path)
 
694
            elif trans_id in self._new_name or trans_id in self._new_parent:
 
695
                try:
 
696
                    os.rename(full_path, self._limbo_name(trans_id))
 
697
                except OSError, e:
 
698
                    if e.errno != errno.ENOENT:
 
699
                        raise
 
700
            if trans_id in self._removed_id:
 
701
                if trans_id == self._new_root:
 
702
                    file_id = self._tree.inventory.root.file_id
 
703
                else:
 
704
                    file_id = self.tree_file_id(trans_id)
 
705
                del inv[file_id]
 
706
            elif trans_id in self._new_name or trans_id in self._new_parent:
 
707
                file_id = self.tree_file_id(trans_id)
 
708
                if file_id is not None:
 
709
                    limbo_inv[trans_id] = inv[file_id]
736
710
                    del inv[file_id]
737
 
                elif trans_id in self._new_name or trans_id in self._new_parent:
738
 
                    file_id = self.tree_file_id(trans_id)
739
 
                    if file_id is not None:
740
 
                        limbo_inv[trans_id] = inv[file_id]
741
 
                        del inv[file_id]
742
 
        finally:
743
 
            child_pb.finished()
 
711
        self._pb.clear()
744
712
 
745
713
    def _apply_insertions(self, inv, limbo_inv):
746
714
        """Perform tree operations that insert directory/inventory names.
751
719
        """
752
720
        new_paths = self.new_paths()
753
721
        modified_paths = []
754
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
755
 
        try:
756
 
            for num, (path, trans_id) in enumerate(new_paths):
757
 
                child_pb.update('adding file', num, len(new_paths))
 
722
        for num, (path, trans_id) in enumerate(new_paths):
 
723
            self._pb.update('adding file', num+1, len(new_paths))
 
724
            try:
 
725
                kind = self._new_contents[trans_id]
 
726
            except KeyError:
 
727
                kind = contents = None
 
728
            if trans_id in self._new_contents or self.path_changed(trans_id):
 
729
                full_path = self._tree.abspath(path)
758
730
                try:
759
 
                    kind = self._new_contents[trans_id]
760
 
                except KeyError:
761
 
                    kind = contents = None
762
 
                if trans_id in self._new_contents or \
763
 
                    self.path_changed(trans_id):
764
 
                    full_path = self._tree.abspath(path)
765
 
                    try:
766
 
                        os.rename(self._limbo_name(trans_id), full_path)
767
 
                    except OSError, e:
768
 
                        # We may be renaming a dangling inventory id
769
 
                        if e.errno != errno.ENOENT:
770
 
                            raise
771
 
                    if trans_id in self._new_contents:
772
 
                        modified_paths.append(full_path)
773
 
                        del self._new_contents[trans_id]
774
 
 
775
 
                if trans_id in self._new_id:
776
 
                    if kind is None:
777
 
                        kind = file_kind(self._tree.abspath(path))
778
 
                    inv.add_path(path, kind, self._new_id[trans_id])
779
 
                elif trans_id in self._new_name or trans_id in\
780
 
                    self._new_parent:
781
 
                    entry = limbo_inv.get(trans_id)
782
 
                    if entry is not None:
783
 
                        entry.name = self.final_name(trans_id)
784
 
                        parent_path = os.path.dirname(path)
785
 
                        entry.parent_id = \
786
 
                            self._tree.inventory.path2id(parent_path)
787
 
                        inv.add(entry)
788
 
 
789
 
                # requires files and inventory entries to be in place
790
 
                if trans_id in self._new_executability:
791
 
                    self._set_executability(path, inv, trans_id)
792
 
        finally:
793
 
            child_pb.finished()
 
731
                    os.rename(self._limbo_name(trans_id), full_path)
 
732
                except OSError, e:
 
733
                    # We may be renaming a dangling inventory id
 
734
                    if e.errno != errno.ENOENT:
 
735
                        raise
 
736
                if trans_id in self._new_contents:
 
737
                    modified_paths.append(full_path)
 
738
                    del self._new_contents[trans_id]
 
739
 
 
740
            if trans_id in self._new_id:
 
741
                if kind is None:
 
742
                    kind = file_kind(self._tree.abspath(path))
 
743
                inv.add_path(path, kind, self._new_id[trans_id])
 
744
            elif trans_id in self._new_name or trans_id in self._new_parent:
 
745
                entry = limbo_inv.get(trans_id)
 
746
                if entry is not None:
 
747
                    entry.name = self.final_name(trans_id)
 
748
                    parent_path = os.path.dirname(path)
 
749
                    entry.parent_id = self._tree.inventory.path2id(parent_path)
 
750
                    inv.add(entry)
 
751
 
 
752
            # requires files and inventory entries to be in place
 
753
            if trans_id in self._new_executability:
 
754
                self._set_executability(path, inv, trans_id)
 
755
        self._pb.clear()
794
756
        return modified_paths
795
757
 
796
758
    def _set_executability(self, path, inv, trans_id):
905
867
def build_tree(tree, wt):
906
868
    """Create working tree for a branch, using a Transaction."""
907
869
    file_trans_id = {}
908
 
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
 
    pp = ProgressPhase("Build phase", 2, top_pb)
910
870
    tt = TreeTransform(wt)
911
871
    try:
912
 
        pp.next_phase()
913
872
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
914
873
        file_ids = topology_sorted_ids(tree)
915
 
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
916
 
        try:
917
 
            for num, file_id in enumerate(file_ids):
918
 
                pb.update("Building tree", num, len(file_ids))
919
 
                entry = tree.inventory[file_id]
920
 
                if entry.parent_id is None:
921
 
                    continue
922
 
                if entry.parent_id not in file_trans_id:
923
 
                    raise repr(entry.parent_id)
924
 
                parent_id = file_trans_id[entry.parent_id]
925
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
926
 
                                                      tree)
927
 
        finally:
928
 
            pb.finished()
929
 
        pp.next_phase()
 
874
        for file_id in file_ids:
 
875
            entry = tree.inventory[file_id]
 
876
            if entry.parent_id is None:
 
877
                continue
 
878
            if entry.parent_id not in file_trans_id:
 
879
                raise repr(entry.parent_id)
 
880
            parent_id = file_trans_id[entry.parent_id]
 
881
            file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
930
882
        tt.apply()
931
883
    finally:
932
884
        tt.finalize()
933
 
        top_pb.finished()
934
885
 
935
886
def new_by_entry(tt, entry, parent_id, tree):
936
887
    """Create a new file according to its inventory entry"""
971
922
    else:
972
923
        interesting_ids = set()
973
924
        for tree_path in filenames:
974
 
            not_found = True
975
925
            for tree in (working_tree, target_tree):
 
926
                not_found = True
976
927
                file_id = tree.inventory.path2id(tree_path)
977
928
                if file_id is not None:
978
929
                    interesting_ids.add(file_id)
979
930
                    not_found = False
980
 
            if not_found:
981
 
                raise NotVersionedError(path=tree_path)
 
931
                if not_found:
 
932
                    raise NotVersionedError(path=tree_path)
982
933
    return interesting_ids
983
934
 
984
935
 
985
936
def change_entry(tt, file_id, working_tree, target_tree, 
986
 
                 trans_id_file_id, backups, trans_id, by_parent):
 
937
                 trans_id_file_id, backups, trans_id):
987
938
    """Replace a file_id's contents with those from a target tree."""
988
939
    e_trans_id = trans_id_file_id(file_id)
989
940
    entry = target_tree.inventory[file_id]
996
947
                tt.delete_contents(e_trans_id)
997
948
            else:
998
949
                parent_trans_id = trans_id_file_id(entry.parent_id)
999
 
                backup_name = get_backup_name(entry, by_parent,
1000
 
                                              parent_trans_id, tt)
1001
 
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
 
950
                tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id)
1002
951
                tt.unversion_file(e_trans_id)
1003
952
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
1004
953
                tt.version_file(file_id, e_trans_id)
1022
971
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1023
972
 
1024
973
 
1025
 
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1026
 
    """Produce a backup-style name that appears to be available"""
1027
 
    def name_gen():
1028
 
        counter = 1
1029
 
        while True:
1030
 
            yield "%s.~%d~" % (entry.name, counter)
1031
 
            counter += 1
1032
 
    for name in name_gen():
1033
 
        if not tt.has_named_child(by_parent, parent_trans_id, name):
1034
 
            return name
1035
 
 
1036
974
def _entry_changes(file_id, entry, working_tree):
1037
975
    """Determine in which ways the inventory entry has changed.
1038
976
 
1073
1011
        return interesting_ids is None or file_id in interesting_ids
1074
1012
 
1075
1013
    tt = TreeTransform(working_tree, pb)
 
1014
    merge_modified = working_tree.merge_modified()
1076
1015
    try:
1077
 
        merge_modified = working_tree.merge_modified()
1078
1016
        trans_id = {}
1079
1017
        def trans_id_file_id(file_id):
1080
1018
            try:
1082
1020
            except KeyError:
1083
1021
                return tt.trans_id_tree_file_id(file_id)
1084
1022
 
1085
 
        pp = ProgressPhase("Revert phase", 4, pb)
1086
 
        pp.next_phase()
1087
1023
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1088
1024
                              interesting(i)]
1089
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1090
 
        try:
1091
 
            by_parent = tt.by_parent()
1092
 
            for id_num, file_id in enumerate(sorted_interesting):
1093
 
                child_pb.update("Reverting file", id_num+1, 
1094
 
                                len(sorted_interesting))
1095
 
                if file_id not in working_tree.inventory:
1096
 
                    entry = target_tree.inventory[file_id]
1097
 
                    parent_id = trans_id_file_id(entry.parent_id)
1098
 
                    e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1099
 
                    trans_id[file_id] = e_trans_id
1100
 
                else:
1101
 
                    backup_this = backups
1102
 
                    if file_id in merge_modified:
1103
 
                        backup_this = False
1104
 
                        del merge_modified[file_id]
1105
 
                    change_entry(tt, file_id, working_tree, target_tree, 
1106
 
                                 trans_id_file_id, backup_this, trans_id,
1107
 
                                 by_parent)
1108
 
        finally:
1109
 
            child_pb.finished()
1110
 
        pp.next_phase()
 
1025
        for id_num, file_id in enumerate(sorted_interesting):
 
1026
            pb.update("Reverting file", id_num+1, len(sorted_interesting))
 
1027
            if file_id not in working_tree.inventory:
 
1028
                entry = target_tree.inventory[file_id]
 
1029
                parent_id = trans_id_file_id(entry.parent_id)
 
1030
                e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
 
1031
                trans_id[file_id] = e_trans_id
 
1032
            else:
 
1033
                backup_this = backups
 
1034
                if file_id in merge_modified:
 
1035
                    backup_this = False
 
1036
                    del merge_modified[file_id]
 
1037
                change_entry(tt, file_id, working_tree, target_tree, 
 
1038
                             trans_id_file_id, backup_this, trans_id)
1111
1039
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1112
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1113
 
        try:
1114
 
            for id_num, file_id in enumerate(wt_interesting):
1115
 
                child_pb.update("New file check", id_num+1, 
1116
 
                                len(sorted_interesting))
1117
 
                if file_id not in target_tree:
1118
 
                    trans_id = tt.trans_id_tree_file_id(file_id)
1119
 
                    tt.unversion_file(trans_id)
1120
 
                    if file_id in merge_modified:
1121
 
                        tt.delete_contents(trans_id)
1122
 
                        del merge_modified[file_id]
1123
 
        finally:
1124
 
            child_pb.finished()
1125
 
        pp.next_phase()
1126
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1127
 
        try:
1128
 
            raw_conflicts = resolve_conflicts(tt, child_pb)
1129
 
        finally:
1130
 
            child_pb.finished()
1131
 
        conflicts = cook_conflicts(raw_conflicts, tt)
1132
 
        for conflict in conflicts:
1133
 
            warning(conflict)
1134
 
        pp.next_phase()
 
1040
        for id_num, file_id in enumerate(wt_interesting):
 
1041
            pb.update("New file check", id_num+1, len(sorted_interesting))
 
1042
            if file_id not in target_tree:
 
1043
                trans_id = tt.trans_id_tree_file_id(file_id)
 
1044
                tt.unversion_file(trans_id)
 
1045
                if file_id in merge_modified:
 
1046
                    tt.delete_contents(trans_id)
 
1047
                    del merge_modified[file_id]
 
1048
        raw_conflicts = resolve_conflicts(tt, pb)
 
1049
        for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
 
1050
            warning(line)
1135
1051
        tt.apply()
1136
1052
        working_tree.set_merge_modified({})
1137
1053
    finally:
1138
1054
        tt.finalize()
1139
1055
        pb.clear()
1140
 
    return conflicts
1141
1056
 
1142
1057
 
1143
1058
def resolve_conflicts(tt, pb=DummyProgress()):
1200
1115
def cook_conflicts(raw_conflicts, tt):
1201
1116
    """Generate a list of cooked conflicts, sorted by file path"""
1202
1117
    def key(conflict):
1203
 
        if conflict.path is not None:
1204
 
            return conflict.path, conflict.typestring
1205
 
        elif getattr(conflict, "conflict_path", None) is not None:
1206
 
            return conflict.conflict_path, conflict.typestring
 
1118
        if conflict[2] is not None:
 
1119
            return conflict[2], conflict[0]
 
1120
        elif len(conflict) == 6:
 
1121
            return conflict[4], conflict[0]
1207
1122
        else:
1208
 
            return None, conflict.typestring
 
1123
            return None, conflict[0]
1209
1124
 
1210
1125
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1211
1126
 
1212
1127
def iter_cook_conflicts(raw_conflicts, tt):
1213
 
    from bzrlib.conflicts import Conflict
 
1128
    cooked_conflicts = []
1214
1129
    fp = FinalPaths(tt)
1215
1130
    for conflict in raw_conflicts:
1216
1131
        c_type = conflict[0]
1218
1133
        modified_path = fp.get_path(conflict[2])
1219
1134
        modified_id = tt.final_file_id(conflict[2])
1220
1135
        if len(conflict) == 3:
1221
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
1222
 
                                     file_id=modified_id)
1223
 
             
 
1136
            yield c_type, action, modified_path, modified_id
1224
1137
        else:
1225
1138
            conflicting_path = fp.get_path(conflict[3])
1226
1139
            conflicting_id = tt.final_file_id(conflict[3])
1227
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
1228
 
                                   file_id=modified_id, 
1229
 
                                   conflict_path=conflicting_path,
1230
 
                                   conflict_file_id=conflicting_id)
 
1140
            yield (c_type, action, modified_path, modified_id, 
 
1141
                   conflicting_path, conflicting_id)
 
1142
 
 
1143
 
 
1144
def conflicts_strings(conflicts):
 
1145
    """Generate strings for the provided conflicts"""
 
1146
    for conflict in conflicts:
 
1147
        conflict_type = conflict[0]
 
1148
        if conflict_type == 'text conflict':
 
1149
            yield 'Text conflict in %s' % conflict[2]
 
1150
        elif conflict_type == 'contents conflict':
 
1151
            yield 'Contents conflict in %s' % conflict[2]
 
1152
        elif conflict_type == 'path conflict':
 
1153
            yield 'Path conflict: %s / %s' % conflict[2:]
 
1154
        elif conflict_type == 'duplicate id':
 
1155
            vals = (conflict[4], conflict[1], conflict[2])
 
1156
            yield 'Conflict adding id to %s.  %s %s.' % vals
 
1157
        elif conflict_type == 'duplicate':
 
1158
            vals = (conflict[4], conflict[1], conflict[2])
 
1159
            yield 'Conflict adding file %s.  %s %s.' % vals
 
1160
        elif conflict_type == 'parent loop':
 
1161
            vals = (conflict[4], conflict[2], conflict[1])
 
1162
            yield 'Conflict moving %s into %s.  %s.' % vals
 
1163
        elif conflict_type == 'unversioned parent':
 
1164
            vals = (conflict[2], conflict[1])
 
1165
            yield 'Conflict adding versioned files to %s.  %s.' % vals
 
1166
        elif conflict_type == 'missing parent':
 
1167
            vals = (conflict[2], conflict[1])
 
1168
            yield 'Conflict adding files to %s.  %s.' % vals