~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

Merge from bzr.dev

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
 
25
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
 
26
                            delete_any)
26
27
from bzrlib.progress import DummyProgress, ProgressPhase
27
28
from bzrlib.trace import mutter, warning
28
29
import bzrlib.ui 
286
287
        os.symlink(target, self._limbo_name(trans_id))
287
288
        unique_add(self._new_contents, trans_id, 'symlink')
288
289
 
289
 
    @staticmethod
290
 
    def delete_any(full_path):
291
 
        """Delete a file or directory."""
292
 
        try:
293
 
            os.unlink(full_path)
294
 
        except OSError, e:
295
 
        # We may be renaming a dangling inventory id
296
 
            if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
297
 
                raise
298
 
            os.rmdir(full_path)
299
 
 
300
290
    def cancel_creation(self, trans_id):
301
291
        """Cancel the creation of new file contents."""
302
292
        del self._new_contents[trans_id]
303
 
        self.delete_any(self._limbo_name(trans_id))
 
293
        delete_any(self._limbo_name(trans_id))
304
294
 
305
295
    def delete_contents(self, trans_id):
306
296
        """Schedule the contents of a path entry for deletion"""
438
428
        except KeyError:
439
429
            return os.path.basename(self._tree_id_paths[trans_id])
440
430
 
441
 
    def _by_parent(self):
 
431
    def by_parent(self):
442
432
        """Return a map of parent: children for known parents.
443
433
        
444
434
        Only new paths and parents of tree files with assigned ids are used.
465
455
        # ensure all children of all existent parents are known
466
456
        # all children of non-existent parents are known, by definition.
467
457
        self._add_tree_children()
468
 
        by_parent = self._by_parent()
 
458
        by_parent = self.by_parent()
469
459
        conflicts.extend(self._unversioned_parents(by_parent))
470
460
        conflicts.extend(self._parent_loops())
471
461
        conflicts.extend(self._duplicate_entries(by_parent))
482
472
        Active parents are those which gain children, and those which are
483
473
        removed.  This is a necessary first step in detecting conflicts.
484
474
        """
485
 
        parents = self._by_parent().keys()
 
475
        parents = self.by_parent().keys()
486
476
        parents.extend([t for t in self._removed_contents if 
487
477
                        self.tree_kind(t) == 'directory'])
488
478
        for trans_id in self._removed_id:
514
504
                continue
515
505
            yield self.trans_id_tree_path(childpath)
516
506
 
 
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
 
517
532
    def _parent_loops(self):
518
533
        """No entry should be its own ancestor"""
519
534
        conflicts = []
605
620
                if name == last_name:
606
621
                    conflicts.append(('duplicate', last_trans_id, trans_id,
607
622
                    name))
608
 
                last_name = name
609
 
                last_trans_id = trans_id
 
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
610
631
        return conflicts
611
632
 
612
633
    def _duplicate_ids(self):
699
720
                child_pb.update('removing file', num, len(tree_paths))
700
721
                full_path = self._tree.abspath(path)
701
722
                if trans_id in self._removed_contents:
702
 
                    self.delete_any(full_path)
 
723
                    delete_any(full_path)
703
724
                elif trans_id in self._new_name or trans_id in \
704
725
                    self._new_parent:
705
726
                    try:
950
971
    else:
951
972
        interesting_ids = set()
952
973
        for tree_path in filenames:
 
974
            not_found = True
953
975
            for tree in (working_tree, target_tree):
954
 
                not_found = True
955
976
                file_id = tree.inventory.path2id(tree_path)
956
977
                if file_id is not None:
957
978
                    interesting_ids.add(file_id)
958
979
                    not_found = False
959
 
                if not_found:
960
 
                    raise NotVersionedError(path=tree_path)
 
980
            if not_found:
 
981
                raise NotVersionedError(path=tree_path)
961
982
    return interesting_ids
962
983
 
963
984
 
964
985
def change_entry(tt, file_id, working_tree, target_tree, 
965
 
                 trans_id_file_id, backups, trans_id):
 
986
                 trans_id_file_id, backups, trans_id, by_parent):
966
987
    """Replace a file_id's contents with those from a target tree."""
967
988
    e_trans_id = trans_id_file_id(file_id)
968
989
    entry = target_tree.inventory[file_id]
975
996
                tt.delete_contents(e_trans_id)
976
997
            else:
977
998
                parent_trans_id = trans_id_file_id(entry.parent_id)
978
 
                tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_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)
979
1002
                tt.unversion_file(e_trans_id)
980
1003
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
981
1004
                tt.version_file(file_id, e_trans_id)
999
1022
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1000
1023
 
1001
1024
 
 
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
 
1002
1036
def _entry_changes(file_id, entry, working_tree):
1003
1037
    """Determine in which ways the inventory entry has changed.
1004
1038
 
1054
1088
                              interesting(i)]
1055
1089
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1056
1090
        try:
 
1091
            by_parent = tt.by_parent()
1057
1092
            for id_num, file_id in enumerate(sorted_interesting):
1058
1093
                child_pb.update("Reverting file", id_num+1, 
1059
1094
                                len(sorted_interesting))
1068
1103
                        backup_this = False
1069
1104
                        del merge_modified[file_id]
1070
1105
                    change_entry(tt, file_id, working_tree, target_tree, 
1071
 
                                 trans_id_file_id, backup_this, trans_id)
 
1106
                                 trans_id_file_id, backup_this, trans_id,
 
1107
                                 by_parent)
1072
1108
        finally:
1073
1109
            child_pb.finished()
1074
1110
        pp.next_phase()
1092
1128
            raw_conflicts = resolve_conflicts(tt, child_pb)
1093
1129
        finally:
1094
1130
            child_pb.finished()
1095
 
        for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
1096
 
            warning(line)
 
1131
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1132
        for conflict in conflicts:
 
1133
            warning(conflict)
1097
1134
        pp.next_phase()
1098
1135
        tt.apply()
1099
1136
        working_tree.set_merge_modified({})
1100
1137
    finally:
1101
1138
        tt.finalize()
1102
1139
        pb.clear()
 
1140
    return conflicts
1103
1141
 
1104
1142
 
1105
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1162
1200
def cook_conflicts(raw_conflicts, tt):
1163
1201
    """Generate a list of cooked conflicts, sorted by file path"""
1164
1202
    def key(conflict):
1165
 
        if conflict[2] is not None:
1166
 
            return conflict[2], conflict[0]
1167
 
        elif len(conflict) == 6:
1168
 
            return conflict[4], conflict[0]
 
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
1169
1207
        else:
1170
 
            return None, conflict[0]
 
1208
            return None, conflict.typestring
1171
1209
 
1172
1210
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1173
1211
 
1174
1212
def iter_cook_conflicts(raw_conflicts, tt):
1175
 
    cooked_conflicts = []
 
1213
    from bzrlib.conflicts import Conflict
1176
1214
    fp = FinalPaths(tt)
1177
1215
    for conflict in raw_conflicts:
1178
1216
        c_type = conflict[0]
1180
1218
        modified_path = fp.get_path(conflict[2])
1181
1219
        modified_id = tt.final_file_id(conflict[2])
1182
1220
        if len(conflict) == 3:
1183
 
            yield c_type, action, modified_path, modified_id
 
1221
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
1222
                                     file_id=modified_id)
 
1223
             
1184
1224
        else:
1185
1225
            conflicting_path = fp.get_path(conflict[3])
1186
1226
            conflicting_id = tt.final_file_id(conflict[3])
1187
 
            yield (c_type, action, modified_path, modified_id, 
1188
 
                   conflicting_path, conflicting_id)
1189
 
 
1190
 
 
1191
 
def conflicts_strings(conflicts):
1192
 
    """Generate strings for the provided conflicts"""
1193
 
    for conflict in conflicts:
1194
 
        conflict_type = conflict[0]
1195
 
        if conflict_type == 'text conflict':
1196
 
            yield 'Text conflict in %s' % conflict[2]
1197
 
        elif conflict_type == 'contents conflict':
1198
 
            yield 'Contents conflict in %s' % conflict[2]
1199
 
        elif conflict_type == 'path conflict':
1200
 
            yield 'Path conflict: %s / %s' % conflict[2:]
1201
 
        elif conflict_type == 'duplicate id':
1202
 
            vals = (conflict[4], conflict[1], conflict[2])
1203
 
            yield 'Conflict adding id to %s.  %s %s.' % vals
1204
 
        elif conflict_type == 'duplicate':
1205
 
            vals = (conflict[4], conflict[1], conflict[2])
1206
 
            yield 'Conflict adding file %s.  %s %s.' % vals
1207
 
        elif conflict_type == 'parent loop':
1208
 
            vals = (conflict[4], conflict[2], conflict[1])
1209
 
            yield 'Conflict moving %s into %s.  %s.' % vals
1210
 
        elif conflict_type == 'unversioned parent':
1211
 
            vals = (conflict[2], conflict[1])
1212
 
            yield 'Conflict adding versioned files to %s.  %s.' % vals
1213
 
        elif conflict_type == 'missing parent':
1214
 
            vals = (conflict[2], conflict[1])
1215
 
            yield 'Conflict adding files to %s.  %s.' % vals
 
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)