/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 extra flush() call to make _update_tree work for dirstate.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2006 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
import errno
19
19
from stat import S_ISREG
20
20
 
 
21
from bzrlib import bzrdir, errors
21
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
23
                           ReusingTransform, NotVersionedError, CantMoveRoot,
23
 
                           ExistingLimbo, ImmortalLimbo)
 
24
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
24
25
from bzrlib.inventory import InventoryEntry
25
26
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
26
27
                            delete_any)
27
28
from bzrlib.progress import DummyProgress, ProgressPhase
28
29
from bzrlib.trace import mutter, warning
 
30
from bzrlib import tree
29
31
import bzrlib.ui 
 
32
import bzrlib.urlutils as urlutils
30
33
 
31
34
 
32
35
ROOT_PARENT = "root-parent"
70
73
     * set_executability
71
74
    """
72
75
    def __init__(self, tree, pb=DummyProgress()):
73
 
        """Note: a write lock is taken on the tree.
 
76
        """Note: a tree_write lock is taken on the tree.
74
77
        
75
78
        Use TreeTransform.finalize() to release the lock
76
79
        """
77
80
        object.__init__(self)
78
81
        self._tree = tree
79
 
        self._tree.lock_write()
 
82
        self._tree.lock_tree_write()
80
83
        try:
81
84
            control_files = self._tree._control_files
82
 
            self._limbodir = control_files.controlfilename('limbo')
 
85
            self._limbodir = urlutils.local_path_from_url(
 
86
                control_files.controlfilename('limbo'))
83
87
            try:
84
88
                os.mkdir(self._limbodir)
85
89
            except OSError, e:
101
105
        self._removed_id = set()
102
106
        self._tree_path_ids = {}
103
107
        self._tree_id_paths = {}
 
108
        self._realpaths = {}
 
109
        # Cache of realpath results, to speed up canonical_path
 
110
        self._relpaths = {}
 
111
        # Cache of relpath results, to speed up canonical_path
104
112
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
105
113
        self.__done = False
106
114
        self._pb = pb
211
219
    def canonical_path(self, path):
212
220
        """Get the canonical tree-relative path"""
213
221
        # don't follow final symlinks
214
 
        dirname, basename = os.path.split(self._tree.abspath(path))
215
 
        dirname = os.path.realpath(dirname)
216
 
        return self._tree.relpath(pathjoin(dirname, basename))
 
222
        abs = self._tree.abspath(path)
 
223
        if abs in self._relpaths:
 
224
            return self._relpaths[abs]
 
225
        dirname, basename = os.path.split(abs)
 
226
        if dirname not in self._realpaths:
 
227
            self._realpaths[dirname] = os.path.realpath(dirname)
 
228
        dirname = self._realpaths[dirname]
 
229
        abs = pathjoin(dirname, basename)
 
230
        if dirname in self._relpaths:
 
231
            relpath = pathjoin(self._relpaths[dirname], basename)
 
232
            relpath = relpath.rstrip('/\\')
 
233
        else:
 
234
            relpath = self._tree.relpath(abs)
 
235
        self._relpaths[abs] = relpath
 
236
        return relpath
217
237
 
218
238
    def trans_id_tree_path(self, path):
219
239
        """Determine (and maybe set) the transaction ID for a tree path."""
241
261
        New file takes the permissions of any existing file with that id,
242
262
        unless mode_id is specified.
243
263
        """
244
 
        f = file(self._limbo_name(trans_id), 'wb')
245
 
        unique_add(self._new_contents, trans_id, 'file')
246
 
        for segment in contents:
247
 
            f.write(segment)
248
 
        f.close()
 
264
        name = self._limbo_name(trans_id)
 
265
        f = open(name, 'wb')
 
266
        try:
 
267
            try:
 
268
                unique_add(self._new_contents, trans_id, 'file')
 
269
            except:
 
270
                # Clean up the file, it never got registered so
 
271
                # TreeTransform.finalize() won't clean it up.
 
272
                f.close()
 
273
                os.unlink(name)
 
274
                raise
 
275
 
 
276
            f.writelines(contents)
 
277
        finally:
 
278
            f.close()
249
279
        self._set_mode(trans_id, mode_id, S_ISREG)
250
280
 
251
281
    def _set_mode(self, trans_id, mode_id, typefunc):
261
291
        except KeyError:
262
292
            return
263
293
        try:
264
 
            mode = os.stat(old_path).st_mode
 
294
            mode = os.stat(self._tree.abspath(old_path)).st_mode
265
295
        except OSError, e:
266
296
            if e.errno == errno.ENOENT:
267
297
                return
426
456
        try:
427
457
            return self._new_name[trans_id]
428
458
        except KeyError:
429
 
            return os.path.basename(self._tree_id_paths[trans_id])
 
459
            try:
 
460
                return os.path.basename(self._tree_id_paths[trans_id])
 
461
            except KeyError:
 
462
                raise NoFinalPath(trans_id, self)
430
463
 
431
464
    def by_parent(self):
432
465
        """Return a map of parent: children for known parents.
445
478
 
446
479
    def path_changed(self, trans_id):
447
480
        """Return True if a trans_id's path has changed."""
448
 
        return trans_id in self._new_name or trans_id in self._new_parent
 
481
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
 
482
 
 
483
    def new_contents(self, trans_id):
 
484
        return (trans_id in self._new_contents)
449
485
 
450
486
    def find_conflicts(self):
451
487
        """Find any violations of inventory or filesystem invariants"""
477
513
                        self.tree_kind(t) == 'directory'])
478
514
        for trans_id in self._removed_id:
479
515
            file_id = self.tree_file_id(trans_id)
480
 
            if self._tree.inventory[file_id].kind in ('directory', 
481
 
                                                      'root_directory'):
 
516
            if self._tree.inventory[file_id].kind == 'directory':
482
517
                parents.append(trans_id)
483
518
 
484
519
        for parent_id in parents:
521
556
        if child_id is None:
522
557
            return lexists(self._tree.abspath(childpath))
523
558
        else:
524
 
            if tt.final_parent(child_id) != parent_id:
 
559
            if self.final_parent(child_id) != parent_id:
525
560
                return False
526
 
            if child_id in tt._removed_contents:
 
561
            if child_id in self._removed_contents:
527
562
                # XXX What about dangling file-ids?
528
563
                return False
529
564
            else:
537
572
            parent_id = trans_id
538
573
            while parent_id is not ROOT_PARENT:
539
574
                seen.add(parent_id)
540
 
                parent_id = self.final_parent(parent_id)
 
575
                try:
 
576
                    parent_id = self.final_parent(parent_id)
 
577
                except KeyError:
 
578
                    break
541
579
                if parent_id == trans_id:
542
580
                    conflicts.append(('parent loop', trans_id))
543
581
                if parent_id in seen:
617
655
            last_name = None
618
656
            last_trans_id = None
619
657
            for name, trans_id in name_ids:
 
658
                try:
 
659
                    kind = self.final_kind(trans_id)
 
660
                except NoSuchFile:
 
661
                    kind = None
 
662
                file_id = self.final_file_id(trans_id)
 
663
                if kind is None and file_id is None:
 
664
                    continue
620
665
                if name == last_name:
621
666
                    conflicts.append(('duplicate', last_trans_id, trans_id,
622
667
                    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
 
668
                last_name = name
 
669
                last_trans_id = trans_id
631
670
        return conflicts
632
671
 
633
672
    def _duplicate_ids(self):
829
868
        parent_id is the transaction id of the parent directory of the file.
830
869
        contents is an iterator of bytestrings, which will be used to produce
831
870
        the file.
832
 
        file_id is the inventory ID of the file, if it is to be versioned.
 
871
        :param file_id: The inventory ID of the file, if it is to be versioned.
 
872
        :param executable: Only valid when a file_id has been supplied.
833
873
        """
834
874
        trans_id = self._new_entry(name, parent_id, file_id)
 
875
        # TODO: rather than scheduling a set_executable call,
 
876
        # have create_file create the file with the right mode.
835
877
        self.create_file(contents, trans_id)
836
878
        if executable is not None:
837
879
            self.set_executability(executable, trans_id)
902
944
    file_ids.sort(key=tree.id2path)
903
945
    return file_ids
904
946
 
 
947
 
905
948
def build_tree(tree, wt):
906
 
    """Create working tree for a branch, using a Transaction."""
 
949
    """Create working tree for a branch, using a TreeTransform.
 
950
    
 
951
    This function should be used on empty trees, having a tree root at most.
 
952
    (see merge and revert functionality for working with existing trees)
 
953
 
 
954
    Existing files are handled like so:
 
955
    
 
956
    - Existing bzrdirs take precedence over creating new items.  They are
 
957
      created as '%s.diverted' % name.
 
958
    - Otherwise, if the content on disk matches the content we are building,
 
959
      it is silently replaced.
 
960
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
 
961
    """
 
962
    if len(wt.inventory) > 1:  # more than just a root
 
963
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
964
    file_trans_id = {}
908
965
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
966
    pp = ProgressPhase("Build phase", 2, top_pb)
 
967
    if tree.inventory.root is not None:
 
968
        wt.set_root_id(tree.inventory.root.file_id)
910
969
    tt = TreeTransform(wt)
 
970
    divert = set()
911
971
    try:
912
972
        pp.next_phase()
913
 
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
914
 
        file_ids = topology_sorted_ids(tree)
 
973
        file_trans_id[wt.get_root_id()] = \
 
974
            tt.trans_id_tree_file_id(wt.get_root_id())
915
975
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
916
976
        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]
 
977
            for num, (tree_path, entry) in \
 
978
                enumerate(tree.inventory.iter_entries_by_dir()):
 
979
                pb.update("Building tree", num, len(tree.inventory))
920
980
                if entry.parent_id is None:
921
981
                    continue
 
982
                reparent = False
 
983
                file_id = entry.file_id
 
984
                target_path = wt.abspath(tree_path)
 
985
                try:
 
986
                    kind = file_kind(target_path)
 
987
                except NoSuchFile:
 
988
                    pass
 
989
                else:
 
990
                    if kind == "directory":
 
991
                        try:
 
992
                            bzrdir.BzrDir.open(target_path)
 
993
                        except errors.NotBranchError:
 
994
                            pass
 
995
                        else:
 
996
                            divert.add(file_id)
 
997
                    if (file_id not in divert and
 
998
                        _content_match(tree, entry, file_id, kind,
 
999
                        target_path)):
 
1000
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
 
1001
                        if kind == 'directory':
 
1002
                            reparent = True
922
1003
                if entry.parent_id not in file_trans_id:
923
1004
                    raise repr(entry.parent_id)
924
1005
                parent_id = file_trans_id[entry.parent_id]
925
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
 
1006
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
926
1007
                                                      tree)
 
1008
                if reparent:
 
1009
                    new_trans_id = file_trans_id[file_id]
 
1010
                    old_parent = tt.trans_id_tree_path(tree_path)
 
1011
                    _reparent_children(tt, old_parent, new_trans_id)
927
1012
        finally:
928
1013
            pb.finished()
929
1014
        pp.next_phase()
 
1015
        divert_trans = set(file_trans_id[f] for f in divert)
 
1016
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
 
1017
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
 
1018
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1019
        for conflict in conflicts:
 
1020
            warning(conflict)
 
1021
        try:
 
1022
            wt.add_conflicts(conflicts)
 
1023
        except errors.UnsupportedOperation:
 
1024
            pass
930
1025
        tt.apply()
931
1026
    finally:
932
1027
        tt.finalize()
933
1028
        top_pb.finished()
934
1029
 
 
1030
 
 
1031
def _reparent_children(tt, old_parent, new_parent):
 
1032
    for child in tt.iter_tree_children(old_parent):
 
1033
        tt.adjust_path(tt.final_name(child), new_parent, child)
 
1034
 
 
1035
 
 
1036
def _content_match(tree, entry, file_id, kind, target_path):
 
1037
    if entry.kind != kind:
 
1038
        return False
 
1039
    if entry.kind == "directory":
 
1040
        return True
 
1041
    if entry.kind == "file":
 
1042
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
 
1043
            return True
 
1044
    elif entry.kind == "symlink":
 
1045
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
 
1046
            return True
 
1047
    return False
 
1048
 
 
1049
 
 
1050
def resolve_checkout(tt, conflicts, divert):
 
1051
    new_conflicts = set()
 
1052
    for c_type, conflict in ((c[0], c) for c in conflicts):
 
1053
        # Anything but a 'duplicate' would indicate programmer error
 
1054
        assert c_type == 'duplicate', c_type
 
1055
        # Now figure out which is new and which is old
 
1056
        if tt.new_contents(conflict[1]):
 
1057
            new_file = conflict[1]
 
1058
            old_file = conflict[2]
 
1059
        else:
 
1060
            new_file = conflict[2]
 
1061
            old_file = conflict[1]
 
1062
 
 
1063
        # We should only get here if the conflict wasn't completely
 
1064
        # resolved
 
1065
        final_parent = tt.final_parent(old_file)
 
1066
        if new_file in divert:
 
1067
            new_name = tt.final_name(old_file)+'.diverted'
 
1068
            tt.adjust_path(new_name, final_parent, new_file)
 
1069
            new_conflicts.add((c_type, 'Diverted to',
 
1070
                               new_file, old_file))
 
1071
        else:
 
1072
            new_name = tt.final_name(old_file)+'.moved'
 
1073
            tt.adjust_path(new_name, final_parent, old_file)
 
1074
            new_conflicts.add((c_type, 'Moved existing file to',
 
1075
                               old_file, new_file))
 
1076
    return new_conflicts
 
1077
 
 
1078
 
935
1079
def new_by_entry(tt, entry, parent_id, tree):
936
1080
    """Create a new file according to its inventory entry"""
937
1081
    name = entry.name
950
1094
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
951
1095
    """Create new file contents according to an inventory entry."""
952
1096
    if entry.kind == "file":
953
 
        if lines == None:
 
1097
        if lines is None:
954
1098
            lines = tree.get_file(entry.file_id).readlines()
955
1099
        tt.create_file(lines, trans_id, mode_id=mode_id)
956
1100
    elif entry.kind == "symlink":
966
1110
 
967
1111
def find_interesting(working_tree, target_tree, filenames):
968
1112
    """Find the ids corresponding to specified filenames."""
969
 
    if not filenames:
970
 
        interesting_ids = None
971
 
    else:
972
 
        interesting_ids = set()
973
 
        for tree_path in filenames:
974
 
            not_found = True
975
 
            for tree in (working_tree, target_tree):
976
 
                file_id = tree.inventory.path2id(tree_path)
977
 
                if file_id is not None:
978
 
                    interesting_ids.add(file_id)
979
 
                    not_found = False
980
 
            if not_found:
981
 
                raise NotVersionedError(path=tree_path)
982
 
    return interesting_ids
 
1113
    trees = (working_tree, target_tree)
 
1114
    return tree.find_ids_across_trees(filenames, trees)
983
1115
 
984
1116
 
985
1117
def change_entry(tt, file_id, working_tree, target_tree, 
1023
1155
 
1024
1156
 
1025
1157
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
1158
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
1159
 
 
1160
 
 
1161
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1026
1162
    """Produce a backup-style name that appears to be available"""
1027
1163
    def name_gen():
1028
1164
        counter = 1
1029
1165
        while True:
1030
 
            yield "%s.~%d~" % (entry.name, counter)
 
1166
            yield "%s.~%d~" % (name, counter)
1031
1167
            counter += 1
1032
 
    for name in name_gen():
1033
 
        if not tt.has_named_child(by_parent, parent_trans_id, name):
1034
 
            return name
 
1168
    for new_name in name_gen():
 
1169
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
1170
            return new_name
 
1171
 
1035
1172
 
1036
1173
def _entry_changes(file_id, entry, working_tree):
1037
1174
    """Determine in which ways the inventory entry has changed.
1045
1182
    try:
1046
1183
        working_kind = working_tree.kind(file_id)
1047
1184
        has_contents = True
1048
 
    except OSError, e:
1049
 
        if e.errno != errno.ENOENT:
1050
 
            raise
 
1185
    except NoSuchFile:
1051
1186
        has_contents = False
1052
1187
        contents_mod = True
1053
1188
        meta_mod = False
1054
1189
    if has_contents is True:
1055
 
        real_e_kind = entry.kind
1056
 
        if real_e_kind == 'root_directory':
1057
 
            real_e_kind = 'directory'
1058
 
        if real_e_kind != working_kind:
 
1190
        if entry.kind != working_kind:
1059
1191
            contents_mod, meta_mod = True, False
1060
1192
        else:
1061
1193
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1065
1197
    return has_contents, contents_mod, meta_mod
1066
1198
 
1067
1199
 
1068
 
def revert(working_tree, target_tree, filenames, backups=False, 
1069
 
           pb=DummyProgress()):
 
1200
def revert(working_tree, target_tree, filenames, backups=False,
 
1201
           pb=DummyProgress(), change_reporter=None):
1070
1202
    """Revert a working tree's contents to those of a target tree."""
1071
 
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
1072
 
    def interesting(file_id):
1073
 
        return interesting_ids is None or file_id in interesting_ids
1074
 
 
 
1203
    target_tree.lock_read()
1075
1204
    tt = TreeTransform(working_tree, pb)
1076
1205
    try:
1077
 
        merge_modified = working_tree.merge_modified()
1078
 
        trans_id = {}
1079
 
        def trans_id_file_id(file_id):
1080
 
            try:
1081
 
                return trans_id[file_id]
1082
 
            except KeyError:
1083
 
                return tt.trans_id_tree_file_id(file_id)
1084
 
 
1085
 
        pp = ProgressPhase("Revert phase", 4, pb)
1086
 
        pp.next_phase()
1087
 
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1088
 
                              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()
1111
 
        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]
 
1206
        interesting_ids = find_interesting(working_tree, target_tree, filenames)
 
1207
        pp = ProgressPhase("Revert phase", 3, pb)
 
1208
        pp.next_phase()
 
1209
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1210
        try:
 
1211
            _alter_files(working_tree, target_tree, tt, child_pb,
 
1212
                         interesting_ids, backups, change_reporter)
1123
1213
        finally:
1124
1214
            child_pb.finished()
1125
1215
        pp.next_phase()
1135
1225
        tt.apply()
1136
1226
        working_tree.set_merge_modified({})
1137
1227
    finally:
 
1228
        target_tree.unlock()
1138
1229
        tt.finalize()
1139
1230
        pb.clear()
1140
1231
    return conflicts
1141
1232
 
1142
1233
 
1143
 
def resolve_conflicts(tt, pb=DummyProgress()):
 
1234
def _alter_files(working_tree, target_tree, tt, pb, interesting_ids, backups,
 
1235
                 report_changes):
 
1236
    from bzrlib import delta
 
1237
    merge_modified = working_tree.merge_modified()
 
1238
    change_list = list(target_tree._iter_changes(working_tree,
 
1239
        specific_file_ids=interesting_ids, pb=pb))
 
1240
    if target_tree.inventory.root is None:
 
1241
        skip_root = True
 
1242
    else:
 
1243
        skip_root = False
 
1244
    basis_tree = None
 
1245
    try:
 
1246
        if report_changes:
 
1247
            change_reporter = delta.ChangeReporter(working_tree.inventory)
 
1248
            delta.report_changes(change_list, change_reporter)
 
1249
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
1250
                kind, executable) in enumerate(change_list):
 
1251
            if skip_root and file_id[0] is not None and parent[0] is None:
 
1252
                continue
 
1253
            trans_id = tt.trans_id_file_id(file_id)
 
1254
            mode_id = None
 
1255
            if changed_content:
 
1256
                keep_content = False
 
1257
                if kind[0] == 'file' and (backups or kind[1] is None):
 
1258
                    wt_sha1 = working_tree.get_file_sha1(file_id)
 
1259
                    if merge_modified.get(file_id) != wt_sha1:
 
1260
                        # acquire the basis tree lazyily to prevent the expense
 
1261
                        # of accessing it when its not needed ? (Guessing, RBC,
 
1262
                        # 200702)
 
1263
                        if basis_tree is None:
 
1264
                            basis_tree = working_tree.basis_tree()
 
1265
                            basis_tree.lock_read()
 
1266
                        if file_id in basis_tree:
 
1267
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
1268
                                keep_content = True
 
1269
                        elif kind[1] is None and not versioned[1]:
 
1270
                            keep_content = True
 
1271
                if kind[0] is not None:
 
1272
                    if not keep_content:
 
1273
                        tt.delete_contents(trans_id)
 
1274
                    elif kind[1] is not None:
 
1275
                        parent_trans_id = tt.trans_id_file_id(parent[0])
 
1276
                        by_parent = tt.by_parent()
 
1277
                        backup_name = _get_backup_name(name[0], by_parent,
 
1278
                                                       parent_trans_id, tt)
 
1279
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
 
1280
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
 
1281
                        if versioned == (True, True):
 
1282
                            tt.unversion_file(trans_id)
 
1283
                            tt.version_file(file_id, new_trans_id)
 
1284
                        # New contents should have the same unix perms as old
 
1285
                        # contents
 
1286
                        mode_id = trans_id
 
1287
                        trans_id = new_trans_id
 
1288
                if kind[1] == 'directory':
 
1289
                    tt.create_directory(trans_id)
 
1290
                elif kind[1] == 'symlink':
 
1291
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
1292
                                      trans_id)
 
1293
                elif kind[1] == 'file':
 
1294
                    tt.create_file(target_tree.get_file_lines(file_id),
 
1295
                                   trans_id, mode_id)
 
1296
                    # preserve the execute bit when backing up
 
1297
                    if keep_content and executable[0] == executable[1]:
 
1298
                        tt.set_executability(executable[1], trans_id)
 
1299
                else:
 
1300
                    assert kind[1] is None
 
1301
            if versioned == (False, True):
 
1302
                tt.version_file(file_id, trans_id)
 
1303
            if versioned == (True, False):
 
1304
                tt.unversion_file(trans_id)
 
1305
            if (name[1] is not None and 
 
1306
                (name[0] != name[1] or parent[0] != parent[1])):
 
1307
                tt.adjust_path(
 
1308
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
 
1309
            if executable[0] != executable[1] and kind[1] == "file":
 
1310
                tt.set_executability(executable[1], trans_id)
 
1311
    finally:
 
1312
        if basis_tree is not None:
 
1313
            basis_tree.unlock()
 
1314
 
 
1315
 
 
1316
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1317
    """Make many conflict-resolution attempts, but die if they fail"""
 
1318
    if pass_func is None:
 
1319
        pass_func = conflict_pass
1145
1320
    new_conflicts = set()
1146
1321
    try:
1147
1322
        for n in range(10):
1149
1324
            conflicts = tt.find_conflicts()
1150
1325
            if len(conflicts) == 0:
1151
1326
                return new_conflicts
1152
 
            new_conflicts.update(conflict_pass(tt, conflicts))
 
1327
            new_conflicts.update(pass_func(tt, conflicts))
1153
1328
        raise MalformedTransform(conflicts=conflicts)
1154
1329
    finally:
1155
1330
        pb.clear()
1188
1363
            trans_id = conflict[1]
1189
1364
            try:
1190
1365
                tt.cancel_deletion(trans_id)
1191
 
                new_conflicts.add((c_type, 'Not deleting', trans_id))
 
1366
                new_conflicts.add(('deleting parent', 'Not deleting', 
 
1367
                                   trans_id))
1192
1368
            except KeyError:
1193
1369
                tt.create_directory(trans_id)
1194
 
                new_conflicts.add((c_type, 'Created directory.', trans_id))
 
1370
                new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1371
        elif c_type == 'unversioned parent':
1196
1372
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1373
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1374
    return new_conflicts
1199
1375
 
 
1376
 
1200
1377
def cook_conflicts(raw_conflicts, tt):
1201
1378
    """Generate a list of cooked conflicts, sorted by file path"""
1202
 
    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
1207
 
        else:
1208
 
            return None, conflict.typestring
 
1379
    from bzrlib.conflicts import Conflict
 
1380
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
 
1381
    return sorted(conflict_iter, key=Conflict.sort_key)
1209
1382
 
1210
 
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1211
1383
 
1212
1384
def iter_cook_conflicts(raw_conflicts, tt):
1213
1385
    from bzrlib.conflicts import Conflict