~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/changeset.py

  • Committer: Martin Pool
  • Date: 2005-07-22 22:37:53 UTC
  • Revision ID: mbp@sourcefrog.net-20050722223753-7dced4e32d3ce21d
- add the start of a test for inventory file-id matching

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
import errno
18
18
import patch
19
19
import stat
 
20
from bzrlib.trace import mutter
20
21
"""
21
22
Represent and apply a changeset
22
23
"""
24
25
 
25
26
NULL_ID = "!NULL"
26
27
 
27
 
 
 
28
class OldFailedTreeOp(Exception):
 
29
    def __init__(self):
 
30
        Exception.__init__(self, "bzr-tree-change contains files from a"
 
31
                           " previous failed merge operation.")
28
32
def invert_dict(dict):
29
33
    newdict = {}
30
34
    for (key,value) in dict.iteritems():
690
694
        :type reverse: bool
691
695
        :rtype: str
692
696
        """
 
697
        mutter("Finding new path for %s" % self.summarize_name(changeset))
693
698
        if reverse:
694
699
            parent = self.parent
695
700
            to_dir = self.dir
714
719
        if from_dir == to_dir:
715
720
            dir = os.path.dirname(id_map[self.id])
716
721
        else:
 
722
            mutter("path, new_path: %r %r" % (self.path, self.new_path))
717
723
            parent_entry = changeset.entries[parent]
718
724
            dir = parent_entry.get_new_path(id_map, changeset, reverse)
719
725
        if from_name == to_name:
826
832
    my_sort(target_entries, shortest_to_longest)
827
833
    return (source_entries, target_entries)
828
834
 
829
 
def rename_to_temp_delete(source_entries, inventory, dir, conflict_handler,
830
 
                          reverse):
 
835
def rename_to_temp_delete(source_entries, inventory, dir, temp_dir, 
 
836
                          conflict_handler, reverse):
831
837
    """Delete and rename entries as appropriate.  Entries are renamed to temp
832
 
    names.  A map of id -> temp name is returned.
 
838
    names.  A map of id -> temp name (or None, for deletions) is returned.
833
839
 
834
840
    :param source_entries: The entries to rename and delete
835
841
    :type source_entries: List of `ChangesetEntry`
842
848
    :return: a mapping of id to temporary name
843
849
    :rtype: Dictionary
844
850
    """
845
 
    temp_dir = os.path.join(dir, "temp")
846
851
    temp_name = {}
847
852
    for i in range(len(source_entries)):
848
853
        entry = source_entries[i]
849
854
        if entry.is_deletion(reverse):
850
855
            path = os.path.join(dir, inventory[entry.id])
851
856
            entry.apply(path, conflict_handler, reverse)
 
857
            temp_name[entry.id] = None
852
858
 
853
859
        else:
854
 
            to_name = temp_dir+"/"+str(i)
 
860
            to_name = os.path.join(temp_dir, str(i))
855
861
            src_path = inventory.get(entry.id)
856
862
            if src_path is not None:
857
863
                src_path = os.path.join(dir, src_path)
867
873
    return temp_name
868
874
 
869
875
 
870
 
def rename_to_new_create(temp_name, target_entries, inventory, changeset, dir,
871
 
                         conflict_handler, reverse):
 
876
def rename_to_new_create(changed_inventory, target_entries, inventory, 
 
877
                         changeset, dir, conflict_handler, reverse):
872
878
    """Rename entries with temp names to their final names, create new files.
873
879
 
874
 
    :param temp_name: A mapping of id to temporary name
875
 
    :type temp_name: Dictionary
 
880
    :param changed_inventory: A mapping of id to temporary name
 
881
    :type changed_inventory: Dictionary
876
882
    :param target_entries: The entries to apply changes to
877
883
    :type target_entries: List of `ChangesetEntry`
878
884
    :param changeset: The changeset to apply
883
889
    :type reverse: bool
884
890
    """
885
891
    for entry in target_entries:
886
 
        new_path = entry.get_new_path(inventory, changeset, reverse)
887
 
        if new_path is None:
 
892
        new_tree_path = entry.get_new_path(inventory, changeset, reverse)
 
893
        if new_tree_path is None:
888
894
            continue
889
 
        new_path = os.path.join(dir, new_path)
890
 
        old_path = temp_name.get(entry.id)
 
895
        new_path = os.path.join(dir, new_tree_path)
 
896
        old_path = changed_inventory.get(entry.id)
891
897
        if os.path.exists(new_path):
892
898
            if conflict_handler.target_exists(entry, new_path, old_path) == \
893
899
                "skip":
894
900
                continue
895
901
        if entry.is_creation(reverse):
896
902
            entry.apply(new_path, conflict_handler, reverse)
 
903
            changed_inventory[entry.id] = new_tree_path
897
904
        else:
898
905
            if old_path is None:
899
906
                continue
900
907
            try:
901
908
                os.rename(old_path, new_path)
 
909
                changed_inventory[entry.id] = new_tree_path
902
910
            except OSError, e:
903
911
                raise Exception ("%s is missing" % new_path)
904
912
 
1006
1014
        Exception.__init__(self, msg)
1007
1015
        self.filename = filename
1008
1016
 
 
1017
class NewContentsConflict(Exception):
 
1018
    def __init__(self, filename):
 
1019
        msg = "Conflicting contents for new file %s" % (filename)
 
1020
        Exception.__init__(self, msg)
 
1021
 
 
1022
 
 
1023
class MissingForMerge(Exception):
 
1024
    def __init__(self, filename):
 
1025
        msg = "The file %s was modified, but does not exist in this tree"\
 
1026
            % (filename)
 
1027
        Exception.__init__(self, msg)
 
1028
 
 
1029
 
1009
1030
class ExceptionConflictHandler(object):
1010
1031
    def __init__(self, dir):
1011
1032
        self.dir = dir
1066
1087
    def missing_for_rename(self, filename):
1067
1088
        raise MissingForRename(filename)
1068
1089
 
 
1090
    def missing_for_merge(self, file_id, inventory):
 
1091
        raise MissingForMerge(inventory.other.get_path(file_id))
 
1092
 
 
1093
    def new_contents_conflict(self, filename, other_contents):
 
1094
        raise NewContentsConflict(filename)
 
1095
 
1069
1096
    def finalize():
1070
1097
        pass
1071
1098
 
1086
1113
    """
1087
1114
    if conflict_handler is None:
1088
1115
        conflict_handler = ExceptionConflictHandler(dir)
1089
 
    temp_dir = dir+"/temp"
1090
 
    os.mkdir(temp_dir)
 
1116
    temp_dir = os.path.join(dir, "bzr-tree-change")
 
1117
    try:
 
1118
        os.mkdir(temp_dir)
 
1119
    except OSError, e:
 
1120
        if e.errno == errno.EEXIST:
 
1121
            try:
 
1122
                os.rmdir(temp_dir)
 
1123
            except OSError, e:
 
1124
                if e.errno == errno.ENOTEMPTY:
 
1125
                    raise OldFailedTreeOp()
 
1126
            os.mkdir(temp_dir)
 
1127
        else:
 
1128
            raise
1091
1129
    
1092
1130
    #apply changes that don't affect filenames
1093
1131
    for entry in changeset.entries.itervalues():
1102
1140
    (source_entries, target_entries) = get_rename_entries(changeset, inventory,
1103
1141
                                                          reverse)
1104
1142
 
1105
 
    temp_name = rename_to_temp_delete(source_entries, inventory, dir,
1106
 
                                      conflict_handler, reverse)
 
1143
    changed_inventory = rename_to_temp_delete(source_entries, inventory, dir,
 
1144
                                              temp_dir, conflict_handler,
 
1145
                                              reverse)
1107
1146
 
1108
 
    rename_to_new_create(temp_name, target_entries, inventory, changeset, dir,
1109
 
                         conflict_handler, reverse)
 
1147
    rename_to_new_create(changed_inventory, target_entries, inventory,
 
1148
                         changeset, dir, conflict_handler, reverse)
1110
1149
    os.rmdir(temp_dir)
1111
 
    r_inventory = invert_dict(inventory)
1112
 
    new_entries, removed_entries = get_inventory_change(inventory,
1113
 
    r_inventory, changeset, reverse)
1114
 
    new_inventory = {}
1115
 
    for path, file_id in new_entries.iteritems():
1116
 
        new_inventory[file_id] = path
1117
 
    for file_id in removed_entries:
1118
 
        new_inventory[file_id] = None
1119
 
    return new_inventory
 
1150
    return changed_inventory
1120
1151
 
1121
1152
 
1122
1153
def apply_changeset_tree(cset, tree, reverse=False):
1133
1164
def get_inventory_change(inventory, new_inventory, cset, reverse=False):
1134
1165
    new_entries = {}
1135
1166
    remove_entries = []
1136
 
    r_inventory = invert_dict(inventory)
1137
 
    r_new_inventory = invert_dict(new_inventory)
1138
1167
    for entry in cset.entries.itervalues():
1139
1168
        if entry.needs_rename():
1140
 
            old_path = r_inventory.get(entry.id)
1141
 
            if old_path is not None:
1142
 
                remove_entries.append(old_path)
 
1169
            new_path = entry.get_new_path(inventory, cset)
 
1170
            if new_path is None:
 
1171
                remove_entries.append(entry.id)
1143
1172
            else:
1144
 
                new_path = entry.get_new_path(inventory, cset)
1145
 
                if new_path is not None:
1146
 
                    new_entries[new_path] = entry.id
 
1173
                new_entries[new_path] = entry.id
1147
1174
    return new_entries, remove_entries
1148
1175
 
1149
1176
 
1502
1529
 
1503
1530
 
1504
1531
        
1505
 
    
 
1532
# XXX: Can't we unify this with the regular inventory object
1506
1533
class Inventory(object):
1507
1534
    def __init__(self, inventory):
1508
1535
        self.inventory = inventory
1517
1544
        return self.inventory.get(id)
1518
1545
 
1519
1546
    def get_name(self, id):
1520
 
        return os.path.basename(self.get_path(id))
 
1547
        path = self.get_path(id)
 
1548
        if path is None:
 
1549
            return None
 
1550
        else:
 
1551
            return os.path.basename(path)
1521
1552
 
1522
1553
    def get_dir(self, id):
1523
1554
        path = self.get_path(id)
1524
1555
        if path == "":
1525
1556
            return None
 
1557
        if path is None:
 
1558
            return None
1526
1559
        return os.path.dirname(path)
1527
1560
 
1528
1561
    def get_parent(self, id):
 
1562
        if self.get_path(id) is None:
 
1563
            return None
1529
1564
        directory = self.get_dir(id)
1530
1565
        if directory == '.':
1531
1566
            directory = './.'