~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/changeset.py

  • Committer: Martin Pool
  • Date: 2005-06-06 04:47:33 UTC
  • Revision ID: mbp@sourcefrog.net-20050606044733-e902b05ac1747cd2
- fix invocation of testbzr when giving explicit bzr location

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
21
 
 
22
 
# XXX: mbp: I'm not totally convinced that we should handle conflicts
23
 
# as part of changeset application, rather than only in the merge
24
 
# operation.
25
 
 
26
 
"""Represent and apply a changeset
27
 
 
28
 
Conflicts in applying a changeset are represented as exceptions.
29
 
"""
30
 
 
 
20
"""
 
21
Represent and apply a changeset
 
22
"""
31
23
__docformat__ = "restructuredtext"
32
24
 
33
25
NULL_ID = "!NULL"
34
26
 
35
 
class OldFailedTreeOp(Exception):
36
 
    def __init__(self):
37
 
        Exception.__init__(self, "bzr-tree-change contains files from a"
38
 
                           " previous failed merge operation.")
 
27
 
39
28
def invert_dict(dict):
40
29
    newdict = {}
41
30
    for (key,value) in dict.iteritems():
701
690
        :type reverse: bool
702
691
        :rtype: str
703
692
        """
704
 
        mutter("Finding new path for %s" % self.summarize_name(changeset))
705
693
        if reverse:
706
694
            parent = self.parent
707
695
            to_dir = self.dir
726
714
        if from_dir == to_dir:
727
715
            dir = os.path.dirname(id_map[self.id])
728
716
        else:
729
 
            mutter("path, new_path: %r %r" % (self.path, self.new_path))
730
717
            parent_entry = changeset.entries[parent]
731
718
            dir = parent_entry.get_new_path(id_map, changeset, reverse)
732
719
        if from_name == to_name:
839
826
    my_sort(target_entries, shortest_to_longest)
840
827
    return (source_entries, target_entries)
841
828
 
842
 
def rename_to_temp_delete(source_entries, inventory, dir, temp_dir, 
843
 
                          conflict_handler, reverse):
 
829
def rename_to_temp_delete(source_entries, inventory, dir, conflict_handler,
 
830
                          reverse):
844
831
    """Delete and rename entries as appropriate.  Entries are renamed to temp
845
 
    names.  A map of id -> temp name (or None, for deletions) is returned.
 
832
    names.  A map of id -> temp name is returned.
846
833
 
847
834
    :param source_entries: The entries to rename and delete
848
835
    :type source_entries: List of `ChangesetEntry`
855
842
    :return: a mapping of id to temporary name
856
843
    :rtype: Dictionary
857
844
    """
 
845
    temp_dir = os.path.join(dir, "temp")
858
846
    temp_name = {}
859
847
    for i in range(len(source_entries)):
860
848
        entry = source_entries[i]
861
849
        if entry.is_deletion(reverse):
862
850
            path = os.path.join(dir, inventory[entry.id])
863
851
            entry.apply(path, conflict_handler, reverse)
864
 
            temp_name[entry.id] = None
865
852
 
866
853
        else:
867
 
            to_name = os.path.join(temp_dir, str(i))
 
854
            to_name = temp_dir+"/"+str(i)
868
855
            src_path = inventory.get(entry.id)
869
856
            if src_path is not None:
870
857
                src_path = os.path.join(dir, src_path)
880
867
    return temp_name
881
868
 
882
869
 
883
 
def rename_to_new_create(changed_inventory, target_entries, inventory, 
884
 
                         changeset, dir, conflict_handler, reverse):
 
870
def rename_to_new_create(temp_name, target_entries, inventory, changeset, dir,
 
871
                         conflict_handler, reverse):
885
872
    """Rename entries with temp names to their final names, create new files.
886
873
 
887
 
    :param changed_inventory: A mapping of id to temporary name
888
 
    :type changed_inventory: Dictionary
 
874
    :param temp_name: A mapping of id to temporary name
 
875
    :type temp_name: Dictionary
889
876
    :param target_entries: The entries to apply changes to
890
877
    :type target_entries: List of `ChangesetEntry`
891
878
    :param changeset: The changeset to apply
896
883
    :type reverse: bool
897
884
    """
898
885
    for entry in target_entries:
899
 
        new_tree_path = entry.get_new_path(inventory, changeset, reverse)
900
 
        if new_tree_path is None:
 
886
        new_path = entry.get_new_path(inventory, changeset, reverse)
 
887
        if new_path is None:
901
888
            continue
902
 
        new_path = os.path.join(dir, new_tree_path)
903
 
        old_path = changed_inventory.get(entry.id)
 
889
        new_path = os.path.join(dir, new_path)
 
890
        old_path = temp_name.get(entry.id)
904
891
        if os.path.exists(new_path):
905
892
            if conflict_handler.target_exists(entry, new_path, old_path) == \
906
893
                "skip":
907
894
                continue
908
895
        if entry.is_creation(reverse):
909
896
            entry.apply(new_path, conflict_handler, reverse)
910
 
            changed_inventory[entry.id] = new_tree_path
911
897
        else:
912
898
            if old_path is None:
913
899
                continue
914
900
            try:
915
901
                os.rename(old_path, new_path)
916
 
                changed_inventory[entry.id] = new_tree_path
917
902
            except OSError, e:
918
903
                raise Exception ("%s is missing" % new_path)
919
904
 
1021
1006
        Exception.__init__(self, msg)
1022
1007
        self.filename = filename
1023
1008
 
1024
 
class NewContentsConflict(Exception):
1025
 
    def __init__(self, filename):
1026
 
        msg = "Conflicting contents for new file %s" % (filename)
1027
 
        Exception.__init__(self, msg)
1028
 
 
1029
 
 
1030
 
class MissingForMerge(Exception):
1031
 
    def __init__(self, filename):
1032
 
        msg = "The file %s was modified, but does not exist in this tree"\
1033
 
            % (filename)
1034
 
        Exception.__init__(self, msg)
1035
 
 
1036
 
 
1037
1009
class ExceptionConflictHandler(object):
1038
 
    """Default handler for merge exceptions.
1039
 
 
1040
 
    This throws an error on any kind of conflict.  Conflict handlers can
1041
 
    descend from this class if they have a better way to handle some or
1042
 
    all types of conflict.
1043
 
    """
1044
1010
    def __init__(self, dir):
1045
1011
        self.dir = dir
1046
1012
    
1100
1066
    def missing_for_rename(self, filename):
1101
1067
        raise MissingForRename(filename)
1102
1068
 
1103
 
    def missing_for_merge(self, file_id, inventory):
1104
 
        raise MissingForMerge(inventory.other.get_path(file_id))
1105
 
 
1106
 
    def new_contents_conflict(self, filename, other_contents):
1107
 
        raise NewContentsConflict(filename)
1108
 
 
1109
1069
    def finalize():
1110
1070
        pass
1111
1071
 
1126
1086
    """
1127
1087
    if conflict_handler is None:
1128
1088
        conflict_handler = ExceptionConflictHandler(dir)
1129
 
    temp_dir = os.path.join(dir, "bzr-tree-change")
1130
 
    try:
1131
 
        os.mkdir(temp_dir)
1132
 
    except OSError, e:
1133
 
        if e.errno == errno.EEXIST:
1134
 
            try:
1135
 
                os.rmdir(temp_dir)
1136
 
            except OSError, e:
1137
 
                if e.errno == errno.ENOTEMPTY:
1138
 
                    raise OldFailedTreeOp()
1139
 
            os.mkdir(temp_dir)
1140
 
        else:
1141
 
            raise
 
1089
    temp_dir = dir+"/temp"
 
1090
    os.mkdir(temp_dir)
1142
1091
    
1143
1092
    #apply changes that don't affect filenames
1144
1093
    for entry in changeset.entries.itervalues():
1153
1102
    (source_entries, target_entries) = get_rename_entries(changeset, inventory,
1154
1103
                                                          reverse)
1155
1104
 
1156
 
    changed_inventory = rename_to_temp_delete(source_entries, inventory, dir,
1157
 
                                              temp_dir, conflict_handler,
1158
 
                                              reverse)
 
1105
    temp_name = rename_to_temp_delete(source_entries, inventory, dir,
 
1106
                                      conflict_handler, reverse)
1159
1107
 
1160
 
    rename_to_new_create(changed_inventory, target_entries, inventory,
1161
 
                         changeset, dir, conflict_handler, reverse)
 
1108
    rename_to_new_create(temp_name, target_entries, inventory, changeset, dir,
 
1109
                         conflict_handler, reverse)
1162
1110
    os.rmdir(temp_dir)
1163
 
    return changed_inventory
 
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
1164
1120
 
1165
1121
 
1166
1122
def apply_changeset_tree(cset, tree, reverse=False):
1177
1133
def get_inventory_change(inventory, new_inventory, cset, reverse=False):
1178
1134
    new_entries = {}
1179
1135
    remove_entries = []
 
1136
    r_inventory = invert_dict(inventory)
 
1137
    r_new_inventory = invert_dict(new_inventory)
1180
1138
    for entry in cset.entries.itervalues():
1181
1139
        if entry.needs_rename():
1182
 
            new_path = entry.get_new_path(inventory, cset)
1183
 
            if new_path is None:
1184
 
                remove_entries.append(entry.id)
 
1140
            old_path = r_inventory.get(entry.id)
 
1141
            if old_path is not None:
 
1142
                remove_entries.append(old_path)
1185
1143
            else:
1186
 
                new_entries[new_path] = entry.id
 
1144
                new_path = entry.get_new_path(inventory, cset)
 
1145
                if new_path is not None:
 
1146
                    new_entries[new_path] = entry.id
1187
1147
    return new_entries, remove_entries
1188
1148
 
1189
1149
 
1395
1355
        parent = inventory[dirname]
1396
1356
        return parent.id
1397
1357
 
1398
 
    def get_path(self, entry, tree):
 
1358
    def get_paths(self, entry, tree):
1399
1359
        if entry is None:
1400
1360
            return (None, None)
 
1361
        full_path = tree.readonly_path(entry.id)
1401
1362
        if entry.path == ".":
1402
 
            return ""
1403
 
        return entry.path
 
1363
            return ("", full_path)
 
1364
        return (entry.path, full_path)
1404
1365
 
1405
1366
    def make_basic_entry(self, id, only_interesting):
1406
1367
        entry_a = self.r_inventory_a.get(id)
1407
1368
        entry_b = self.r_inventory_b.get(id)
1408
1369
        if only_interesting and not self.is_interesting(entry_a, entry_b):
1409
 
            return None
 
1370
            return (None, None, None)
1410
1371
        parent = self.get_entry_parent(entry_a, self.inventory_a)
1411
 
        path = self.get_path(entry_a, self.tree_a)
 
1372
        (path, full_path_a) = self.get_paths(entry_a, self.tree_a)
1412
1373
        cs_entry = ChangesetEntry(id, parent, path)
1413
1374
        new_parent = self.get_entry_parent(entry_b, self.inventory_b)
1414
1375
 
1415
1376
 
1416
 
        new_path = self.get_path(entry_b, self.tree_b)
 
1377
        (new_path, full_path_b) = self.get_paths(entry_b, self.tree_b)
1417
1378
 
1418
1379
        cs_entry.new_path = new_path
1419
1380
        cs_entry.new_parent = new_parent
1420
 
        return cs_entry
 
1381
        return (cs_entry, full_path_a, full_path_b)
1421
1382
 
1422
1383
    def is_interesting(self, entry_a, entry_b):
1423
1384
        if entry_a is not None:
1429
1390
        return False
1430
1391
 
1431
1392
    def make_boring_entry(self, id):
1432
 
        cs_entry = self.make_basic_entry(id, only_interesting=False)
 
1393
        (cs_entry, full_path_a, full_path_b) = \
 
1394
            self.make_basic_entry(id, only_interesting=False)
1433
1395
        if cs_entry.is_creation_or_deletion():
1434
1396
            return self.make_entry(id, only_interesting=False)
1435
1397
        else:
1437
1399
        
1438
1400
 
1439
1401
    def make_entry(self, id, only_interesting=True):
1440
 
        cs_entry = self.make_basic_entry(id, only_interesting)
 
1402
        (cs_entry, full_path_a, full_path_b) = \
 
1403
            self.make_basic_entry(id, only_interesting)
1441
1404
 
1442
1405
        if cs_entry is None:
1443
1406
            return None
1444
 
        if id in self.tree_a and id in self.tree_b:
1445
 
            a_sha1 = self.tree_a.get_file_sha1(id)
1446
 
            b_sha1 = self.tree_b.get_file_sha1(id)
1447
 
            if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1448
 
                return cs_entry
1449
 
 
1450
 
        full_path_a = self.tree_a.readonly_path(id)
1451
 
        full_path_b = self.tree_b.readonly_path(id)
 
1407
       
1452
1408
        stat_a = self.lstat(full_path_a)
1453
1409
        stat_b = self.lstat(full_path_b)
1454
1410
        if stat_b is None:
1546
1502
 
1547
1503
 
1548
1504
        
1549
 
# XXX: Can't we unify this with the regular inventory object
 
1505
    
1550
1506
class Inventory(object):
1551
1507
    def __init__(self, inventory):
1552
1508
        self.inventory = inventory
1561
1517
        return self.inventory.get(id)
1562
1518
 
1563
1519
    def get_name(self, id):
1564
 
        path = self.get_path(id)
1565
 
        if path is None:
1566
 
            return None
1567
 
        else:
1568
 
            return os.path.basename(path)
 
1520
        return os.path.basename(self.get_path(id))
1569
1521
 
1570
1522
    def get_dir(self, id):
1571
1523
        path = self.get_path(id)
1572
1524
        if path == "":
1573
1525
            return None
1574
 
        if path is None:
1575
 
            return None
1576
1526
        return os.path.dirname(path)
1577
1527
 
1578
1528
    def get_parent(self, id):
1579
 
        if self.get_path(id) is None:
1580
 
            return None
1581
1529
        directory = self.get_dir(id)
1582
1530
        if directory == '.':
1583
1531
            directory = './.'