/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

  • Committer: John Arbash Meinel
  • Date: 2007-03-01 21:56:19 UTC
  • mto: (2255.7.84 dirstate)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: john@arbash-meinel.com-20070301215619-wpt6kz8yem3ypu1b
Update to dirstate locking.
Move all of WT4.lock_* functions locally, so that they can
properly interact and cleanup around when we lock/unlock the
dirstate file.
Change all Lock objects to be non-blocking. So that if someone
grabs a lock on the DirState we find out immediately, rather
than blocking.
Change WT4.unlock() so that if the dirstate is dirty, it will
save the contents even if it only has a read lock.
It does this by trying to take a write lock, if it fails
we just ignore it. If it succeeds, then we can flush to disk.
This is more important now that DirState tracks file changes.
It allows 'bzr status' to update the cached stat and sha values.

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
 
22
from bzrlib.lazy_import import lazy_import
 
23
lazy_import(globals(), """
 
24
from bzrlib import delta
 
25
""")
21
26
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
27
                           ReusingTransform, NotVersionedError, CantMoveRoot,
23
 
                           ExistingLimbo, ImmortalLimbo)
 
28
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
24
29
from bzrlib.inventory import InventoryEntry
25
30
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
26
31
                            delete_any)
27
32
from bzrlib.progress import DummyProgress, ProgressPhase
 
33
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
28
34
from bzrlib.trace import mutter, warning
 
35
from bzrlib import tree
29
36
import bzrlib.ui 
 
37
import bzrlib.urlutils as urlutils
30
38
 
31
39
 
32
40
ROOT_PARENT = "root-parent"
70
78
     * set_executability
71
79
    """
72
80
    def __init__(self, tree, pb=DummyProgress()):
73
 
        """Note: a write lock is taken on the tree.
 
81
        """Note: a tree_write lock is taken on the tree.
74
82
        
75
83
        Use TreeTransform.finalize() to release the lock
76
84
        """
77
85
        object.__init__(self)
78
86
        self._tree = tree
79
 
        self._tree.lock_write()
 
87
        self._tree.lock_tree_write()
80
88
        try:
81
89
            control_files = self._tree._control_files
82
 
            self._limbodir = control_files.controlfilename('limbo')
 
90
            self._limbodir = urlutils.local_path_from_url(
 
91
                control_files.controlfilename('limbo'))
83
92
            try:
84
93
                os.mkdir(self._limbodir)
85
94
            except OSError, e:
101
110
        self._removed_id = set()
102
111
        self._tree_path_ids = {}
103
112
        self._tree_id_paths = {}
 
113
        self._realpaths = {}
 
114
        # Cache of realpath results, to speed up canonical_path
 
115
        self._relpaths = {}
 
116
        # Cache of relpath results, to speed up canonical_path
104
117
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
105
118
        self.__done = False
106
119
        self._pb = pb
211
224
    def canonical_path(self, path):
212
225
        """Get the canonical tree-relative path"""
213
226
        # 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))
 
227
        abs = self._tree.abspath(path)
 
228
        if abs in self._relpaths:
 
229
            return self._relpaths[abs]
 
230
        dirname, basename = os.path.split(abs)
 
231
        if dirname not in self._realpaths:
 
232
            self._realpaths[dirname] = os.path.realpath(dirname)
 
233
        dirname = self._realpaths[dirname]
 
234
        abs = pathjoin(dirname, basename)
 
235
        if dirname in self._relpaths:
 
236
            relpath = pathjoin(self._relpaths[dirname], basename)
 
237
            relpath = relpath.rstrip('/\\')
 
238
        else:
 
239
            relpath = self._tree.relpath(abs)
 
240
        self._relpaths[abs] = relpath
 
241
        return relpath
217
242
 
218
243
    def trans_id_tree_path(self, path):
219
244
        """Determine (and maybe set) the transaction ID for a tree path."""
241
266
        New file takes the permissions of any existing file with that id,
242
267
        unless mode_id is specified.
243
268
        """
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()
 
269
        name = self._limbo_name(trans_id)
 
270
        f = open(name, 'wb')
 
271
        try:
 
272
            try:
 
273
                unique_add(self._new_contents, trans_id, 'file')
 
274
            except:
 
275
                # Clean up the file, it never got registered so
 
276
                # TreeTransform.finalize() won't clean it up.
 
277
                f.close()
 
278
                os.unlink(name)
 
279
                raise
 
280
 
 
281
            f.writelines(contents)
 
282
        finally:
 
283
            f.close()
249
284
        self._set_mode(trans_id, mode_id, S_ISREG)
250
285
 
251
286
    def _set_mode(self, trans_id, mode_id, typefunc):
261
296
        except KeyError:
262
297
            return
263
298
        try:
264
 
            mode = os.stat(old_path).st_mode
 
299
            mode = os.stat(self._tree.abspath(old_path)).st_mode
265
300
        except OSError, e:
266
301
            if e.errno == errno.ENOENT:
267
302
                return
426
461
        try:
427
462
            return self._new_name[trans_id]
428
463
        except KeyError:
429
 
            return os.path.basename(self._tree_id_paths[trans_id])
 
464
            try:
 
465
                return os.path.basename(self._tree_id_paths[trans_id])
 
466
            except KeyError:
 
467
                raise NoFinalPath(trans_id, self)
430
468
 
431
469
    def by_parent(self):
432
470
        """Return a map of parent: children for known parents.
445
483
 
446
484
    def path_changed(self, trans_id):
447
485
        """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
 
486
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
 
487
 
 
488
    def new_contents(self, trans_id):
 
489
        return (trans_id in self._new_contents)
449
490
 
450
491
    def find_conflicts(self):
451
492
        """Find any violations of inventory or filesystem invariants"""
477
518
                        self.tree_kind(t) == 'directory'])
478
519
        for trans_id in self._removed_id:
479
520
            file_id = self.tree_file_id(trans_id)
480
 
            if self._tree.inventory[file_id].kind in ('directory', 
481
 
                                                      'root_directory'):
 
521
            if self._tree.inventory[file_id].kind == 'directory':
482
522
                parents.append(trans_id)
483
523
 
484
524
        for parent_id in parents:
521
561
        if child_id is None:
522
562
            return lexists(self._tree.abspath(childpath))
523
563
        else:
524
 
            if tt.final_parent(child_id) != parent_id:
 
564
            if self.final_parent(child_id) != parent_id:
525
565
                return False
526
 
            if child_id in tt._removed_contents:
 
566
            if child_id in self._removed_contents:
527
567
                # XXX What about dangling file-ids?
528
568
                return False
529
569
            else:
537
577
            parent_id = trans_id
538
578
            while parent_id is not ROOT_PARENT:
539
579
                seen.add(parent_id)
540
 
                parent_id = self.final_parent(parent_id)
 
580
                try:
 
581
                    parent_id = self.final_parent(parent_id)
 
582
                except KeyError:
 
583
                    break
541
584
                if parent_id == trans_id:
542
585
                    conflicts.append(('parent loop', trans_id))
543
586
                if parent_id in seen:
617
660
            last_name = None
618
661
            last_trans_id = None
619
662
            for name, trans_id in name_ids:
 
663
                try:
 
664
                    kind = self.final_kind(trans_id)
 
665
                except NoSuchFile:
 
666
                    kind = None
 
667
                file_id = self.final_file_id(trans_id)
 
668
                if kind is None and file_id is None:
 
669
                    continue
620
670
                if name == last_name:
621
671
                    conflicts.append(('duplicate', last_trans_id, trans_id,
622
672
                    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
 
673
                last_name = name
 
674
                last_trans_id = trans_id
631
675
        return conflicts
632
676
 
633
677
    def _duplicate_ids(self):
829
873
        parent_id is the transaction id of the parent directory of the file.
830
874
        contents is an iterator of bytestrings, which will be used to produce
831
875
        the file.
832
 
        file_id is the inventory ID of the file, if it is to be versioned.
 
876
        :param file_id: The inventory ID of the file, if it is to be versioned.
 
877
        :param executable: Only valid when a file_id has been supplied.
833
878
        """
834
879
        trans_id = self._new_entry(name, parent_id, file_id)
 
880
        # TODO: rather than scheduling a set_executable call,
 
881
        # have create_file create the file with the right mode.
835
882
        self.create_file(contents, trans_id)
836
883
        if executable is not None:
837
884
            self.set_executability(executable, trans_id)
861
908
        self.create_symlink(target, trans_id)
862
909
        return trans_id
863
910
 
 
911
    def _affected_ids(self):
 
912
        """Return the set of transform ids affected by the transform"""
 
913
        trans_ids = set(self._removed_id)
 
914
        trans_ids.update(self._new_id.keys())
 
915
        trans_ids.update(self._removed_contents)
 
916
        trans_ids.update(self._new_contents.keys())
 
917
        trans_ids.update(self._new_executability.keys())
 
918
        trans_ids.update(self._new_name.keys())
 
919
        trans_ids.update(self._new_parent.keys())
 
920
        return trans_ids
 
921
 
 
922
    def _get_file_id_maps(self):
 
923
        """Return mapping of file_ids to trans_ids in the to and from states"""
 
924
        trans_ids = self._affected_ids()
 
925
        from_trans_ids = {}
 
926
        to_trans_ids = {}
 
927
        # Build up two dicts: trans_ids associated with file ids in the
 
928
        # FROM state, vs the TO state.
 
929
        for trans_id in trans_ids:
 
930
            from_file_id = self.tree_file_id(trans_id)
 
931
            if from_file_id is not None:
 
932
                from_trans_ids[from_file_id] = trans_id
 
933
            to_file_id = self.final_file_id(trans_id)
 
934
            if to_file_id is not None:
 
935
                to_trans_ids[to_file_id] = trans_id
 
936
        return from_trans_ids, to_trans_ids
 
937
 
 
938
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
 
939
        """Get data about a file in the from (tree) state
 
940
 
 
941
        Return a (name, parent, kind, executable) tuple
 
942
        """
 
943
        from_path = self._tree_id_paths.get(from_trans_id)
 
944
        if from_versioned:
 
945
            # get data from working tree if versioned
 
946
            from_entry = self._tree.inventory[file_id]
 
947
            from_name = from_entry.name
 
948
            from_parent = from_entry.parent_id
 
949
        else:
 
950
            from_entry = None
 
951
            if from_path is None:
 
952
                # File does not exist in FROM state
 
953
                from_name = None
 
954
                from_parent = None
 
955
            else:
 
956
                # File exists, but is not versioned.  Have to use path-
 
957
                # splitting stuff
 
958
                from_name = os.path.basename(from_path)
 
959
                tree_parent = self.get_tree_parent(from_trans_id)
 
960
                from_parent = self.tree_file_id(tree_parent)
 
961
        if from_path is not None:
 
962
            from_kind, from_executable, from_stats = \
 
963
                self._tree._comparison_data(from_entry, from_path)
 
964
        else:
 
965
            from_kind = None
 
966
            from_executable = False
 
967
        return from_name, from_parent, from_kind, from_executable
 
968
 
 
969
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
 
970
        """Get data about a file in the to (target) state
 
971
 
 
972
        Return a (name, parent, kind, executable) tuple
 
973
        """
 
974
        to_name = self.final_name(to_trans_id)
 
975
        try:
 
976
            to_kind = self.final_kind(to_trans_id)
 
977
        except NoSuchFile:
 
978
            to_kind = None
 
979
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
 
980
        if to_trans_id in self._new_executability:
 
981
            to_executable = self._new_executability[to_trans_id]
 
982
        elif to_trans_id == from_trans_id:
 
983
            to_executable = from_executable
 
984
        else:
 
985
            to_executable = False
 
986
        return to_name, to_parent, to_kind, to_executable
 
987
 
 
988
    def _iter_changes(self):
 
989
        """Produce output in the same format as Tree._iter_changes.
 
990
 
 
991
        Will produce nonsensical results if invoked while inventory/filesystem
 
992
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
 
993
 
 
994
        This reads the Transform, but only reproduces changes involving a
 
995
        file_id.  Files that are not versioned in either of the FROM or TO
 
996
        states are not reflected.
 
997
        """
 
998
        final_paths = FinalPaths(self)
 
999
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
 
1000
        results = []
 
1001
        # Now iterate through all active file_ids
 
1002
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
 
1003
            modified = False
 
1004
            from_trans_id = from_trans_ids.get(file_id)
 
1005
            # find file ids, and determine versioning state
 
1006
            if from_trans_id is None:
 
1007
                from_versioned = False
 
1008
                from_trans_id = to_trans_ids[file_id]
 
1009
            else:
 
1010
                from_versioned = True
 
1011
            to_trans_id = to_trans_ids.get(file_id)
 
1012
            if to_trans_id is None:
 
1013
                to_versioned = False
 
1014
                to_trans_id = from_trans_id
 
1015
            else:
 
1016
                to_versioned = True
 
1017
 
 
1018
            from_name, from_parent, from_kind, from_executable = \
 
1019
                self._from_file_data(from_trans_id, from_versioned, file_id)
 
1020
 
 
1021
            to_name, to_parent, to_kind, to_executable = \
 
1022
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
 
1023
 
 
1024
            to_path = final_paths.get_path(to_trans_id)
 
1025
            if from_kind != to_kind:
 
1026
                modified = True
 
1027
            elif to_kind in ('file' or 'symlink') and (
 
1028
                to_trans_id != from_trans_id or
 
1029
                to_trans_id in self._new_contents):
 
1030
                modified = True
 
1031
            if (not modified and from_versioned == to_versioned and
 
1032
                from_parent==to_parent and from_name == to_name and
 
1033
                from_executable == to_executable):
 
1034
                continue
 
1035
            results.append((file_id, to_path, modified,
 
1036
                   (from_versioned, to_versioned),
 
1037
                   (from_parent, to_parent),
 
1038
                   (from_name, to_name),
 
1039
                   (from_kind, to_kind),
 
1040
                   (from_executable, to_executable)))
 
1041
        return iter(sorted(results, key=lambda x:x[1]))
 
1042
 
 
1043
 
864
1044
def joinpath(parent, child):
865
1045
    """Join tree-relative paths, handling the tree root specially"""
866
1046
    if parent is None or parent == "":
902
1082
    file_ids.sort(key=tree.id2path)
903
1083
    return file_ids
904
1084
 
 
1085
 
905
1086
def build_tree(tree, wt):
906
 
    """Create working tree for a branch, using a Transaction."""
 
1087
    """Create working tree for a branch, using a TreeTransform.
 
1088
    
 
1089
    This function should be used on empty trees, having a tree root at most.
 
1090
    (see merge and revert functionality for working with existing trees)
 
1091
 
 
1092
    Existing files are handled like so:
 
1093
    
 
1094
    - Existing bzrdirs take precedence over creating new items.  They are
 
1095
      created as '%s.diverted' % name.
 
1096
    - Otherwise, if the content on disk matches the content we are building,
 
1097
      it is silently replaced.
 
1098
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
 
1099
    """
 
1100
    wt.lock_tree_write()
 
1101
    try:
 
1102
        tree.lock_read()
 
1103
        try:
 
1104
            return _build_tree(tree, wt)
 
1105
        finally:
 
1106
            tree.unlock()
 
1107
    finally:
 
1108
        wt.unlock()
 
1109
 
 
1110
def _build_tree(tree, wt):
 
1111
    """See build_tree."""
 
1112
    if len(wt.inventory) > 1:  # more than just a root
 
1113
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
1114
    file_trans_id = {}
908
1115
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
1116
    pp = ProgressPhase("Build phase", 2, top_pb)
 
1117
#    if tree.inventory.root is not None:
 
1118
#        wt.set_root_id(tree.inventory.root.file_id)
910
1119
    tt = TreeTransform(wt)
 
1120
    divert = set()
911
1121
    try:
912
1122
        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)
 
1123
        file_trans_id[wt.get_root_id()] = \
 
1124
            tt.trans_id_tree_file_id(wt.get_root_id())
915
1125
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
916
1126
        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]
 
1127
            for num, (tree_path, entry) in \
 
1128
                enumerate(tree.inventory.iter_entries_by_dir()):
 
1129
                pb.update("Building tree", num, len(tree.inventory))
920
1130
                if entry.parent_id is None:
921
1131
                    continue
 
1132
                reparent = False
 
1133
                file_id = entry.file_id
 
1134
                target_path = wt.abspath(tree_path)
 
1135
                try:
 
1136
                    kind = file_kind(target_path)
 
1137
                except NoSuchFile:
 
1138
                    pass
 
1139
                else:
 
1140
                    if kind == "directory":
 
1141
                        try:
 
1142
                            bzrdir.BzrDir.open(target_path)
 
1143
                        except errors.NotBranchError:
 
1144
                            pass
 
1145
                        else:
 
1146
                            divert.add(file_id)
 
1147
                    if (file_id not in divert and
 
1148
                        _content_match(tree, entry, file_id, kind,
 
1149
                        target_path)):
 
1150
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
 
1151
                        if kind == 'directory':
 
1152
                            reparent = True
922
1153
                if entry.parent_id not in file_trans_id:
923
1154
                    raise repr(entry.parent_id)
924
1155
                parent_id = file_trans_id[entry.parent_id]
925
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
 
1156
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
926
1157
                                                      tree)
 
1158
                if reparent:
 
1159
                    new_trans_id = file_trans_id[file_id]
 
1160
                    old_parent = tt.trans_id_tree_path(tree_path)
 
1161
                    _reparent_children(tt, old_parent, new_trans_id)
927
1162
        finally:
928
1163
            pb.finished()
929
1164
        pp.next_phase()
 
1165
        divert_trans = set(file_trans_id[f] for f in divert)
 
1166
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
 
1167
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
 
1168
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1169
        for conflict in conflicts:
 
1170
            warning(conflict)
 
1171
        try:
 
1172
            wt.add_conflicts(conflicts)
 
1173
        except errors.UnsupportedOperation:
 
1174
            pass
930
1175
        tt.apply()
931
1176
    finally:
932
1177
        tt.finalize()
933
1178
        top_pb.finished()
934
1179
 
 
1180
 
 
1181
def _reparent_children(tt, old_parent, new_parent):
 
1182
    for child in tt.iter_tree_children(old_parent):
 
1183
        tt.adjust_path(tt.final_name(child), new_parent, child)
 
1184
 
 
1185
 
 
1186
def _content_match(tree, entry, file_id, kind, target_path):
 
1187
    if entry.kind != kind:
 
1188
        return False
 
1189
    if entry.kind == "directory":
 
1190
        return True
 
1191
    if entry.kind == "file":
 
1192
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
 
1193
            return True
 
1194
    elif entry.kind == "symlink":
 
1195
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
 
1196
            return True
 
1197
    return False
 
1198
 
 
1199
 
 
1200
def resolve_checkout(tt, conflicts, divert):
 
1201
    new_conflicts = set()
 
1202
    for c_type, conflict in ((c[0], c) for c in conflicts):
 
1203
        # Anything but a 'duplicate' would indicate programmer error
 
1204
        assert c_type == 'duplicate', c_type
 
1205
        # Now figure out which is new and which is old
 
1206
        if tt.new_contents(conflict[1]):
 
1207
            new_file = conflict[1]
 
1208
            old_file = conflict[2]
 
1209
        else:
 
1210
            new_file = conflict[2]
 
1211
            old_file = conflict[1]
 
1212
 
 
1213
        # We should only get here if the conflict wasn't completely
 
1214
        # resolved
 
1215
        final_parent = tt.final_parent(old_file)
 
1216
        if new_file in divert:
 
1217
            new_name = tt.final_name(old_file)+'.diverted'
 
1218
            tt.adjust_path(new_name, final_parent, new_file)
 
1219
            new_conflicts.add((c_type, 'Diverted to',
 
1220
                               new_file, old_file))
 
1221
        else:
 
1222
            new_name = tt.final_name(old_file)+'.moved'
 
1223
            tt.adjust_path(new_name, final_parent, old_file)
 
1224
            new_conflicts.add((c_type, 'Moved existing file to',
 
1225
                               old_file, new_file))
 
1226
    return new_conflicts
 
1227
 
 
1228
 
935
1229
def new_by_entry(tt, entry, parent_id, tree):
936
1230
    """Create a new file according to its inventory entry"""
937
1231
    name = entry.name
950
1244
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
951
1245
    """Create new file contents according to an inventory entry."""
952
1246
    if entry.kind == "file":
953
 
        if lines == None:
 
1247
        if lines is None:
954
1248
            lines = tree.get_file(entry.file_id).readlines()
955
1249
        tt.create_file(lines, trans_id, mode_id=mode_id)
956
1250
    elif entry.kind == "symlink":
964
1258
        tt.set_executability(entry.executable, trans_id)
965
1259
 
966
1260
 
 
1261
@deprecated_function(zero_fifteen)
967
1262
def find_interesting(working_tree, target_tree, filenames):
968
 
    """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
 
1263
    """Find the ids corresponding to specified filenames.
 
1264
    
 
1265
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
 
1266
    """
 
1267
    working_tree.lock_read()
 
1268
    try:
 
1269
        target_tree.lock_read()
 
1270
        try:
 
1271
            return working_tree.paths2ids(filenames, [target_tree])
 
1272
        finally:
 
1273
            target_tree.unlock()
 
1274
    finally:
 
1275
        working_tree.unlock()
983
1276
 
984
1277
 
985
1278
def change_entry(tt, file_id, working_tree, target_tree, 
1023
1316
 
1024
1317
 
1025
1318
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
1319
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
1320
 
 
1321
 
 
1322
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1026
1323
    """Produce a backup-style name that appears to be available"""
1027
1324
    def name_gen():
1028
1325
        counter = 1
1029
1326
        while True:
1030
 
            yield "%s.~%d~" % (entry.name, counter)
 
1327
            yield "%s.~%d~" % (name, counter)
1031
1328
            counter += 1
1032
 
    for name in name_gen():
1033
 
        if not tt.has_named_child(by_parent, parent_trans_id, name):
1034
 
            return name
 
1329
    for new_name in name_gen():
 
1330
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
1331
            return new_name
 
1332
 
1035
1333
 
1036
1334
def _entry_changes(file_id, entry, working_tree):
1037
1335
    """Determine in which ways the inventory entry has changed.
1045
1343
    try:
1046
1344
        working_kind = working_tree.kind(file_id)
1047
1345
        has_contents = True
1048
 
    except OSError, e:
1049
 
        if e.errno != errno.ENOENT:
1050
 
            raise
 
1346
    except NoSuchFile:
1051
1347
        has_contents = False
1052
1348
        contents_mod = True
1053
1349
        meta_mod = False
1054
1350
    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:
 
1351
        if entry.kind != working_kind:
1059
1352
            contents_mod, meta_mod = True, False
1060
1353
        else:
1061
1354
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1065
1358
    return has_contents, contents_mod, meta_mod
1066
1359
 
1067
1360
 
1068
 
def revert(working_tree, target_tree, filenames, backups=False, 
1069
 
           pb=DummyProgress()):
 
1361
def revert(working_tree, target_tree, filenames, backups=False,
 
1362
           pb=DummyProgress(), change_reporter=None):
1070
1363
    """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
 
 
 
1364
    target_tree.lock_read()
1075
1365
    tt = TreeTransform(working_tree, pb)
1076
1366
    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]
 
1367
        pp = ProgressPhase("Revert phase", 3, pb)
 
1368
        pp.next_phase()
 
1369
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1370
        try:
 
1371
            _alter_files(working_tree, target_tree, tt, child_pb,
 
1372
                         filenames, backups)
1123
1373
        finally:
1124
1374
            child_pb.finished()
1125
1375
        pp.next_phase()
1129
1379
        finally:
1130
1380
            child_pb.finished()
1131
1381
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1382
        if change_reporter:
 
1383
            change_reporter = delta.ChangeReporter(working_tree.inventory)
 
1384
            delta.report_changes(tt._iter_changes(), change_reporter)
1132
1385
        for conflict in conflicts:
1133
1386
            warning(conflict)
1134
1387
        pp.next_phase()
1135
1388
        tt.apply()
1136
1389
        working_tree.set_merge_modified({})
1137
1390
    finally:
 
1391
        target_tree.unlock()
1138
1392
        tt.finalize()
1139
1393
        pb.clear()
1140
1394
    return conflicts
1141
1395
 
1142
1396
 
1143
 
def resolve_conflicts(tt, pb=DummyProgress()):
 
1397
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
 
1398
                 backups):
 
1399
    merge_modified = working_tree.merge_modified()
 
1400
    change_list = target_tree._iter_changes(working_tree,
 
1401
        specific_files=specific_files, pb=pb)
 
1402
    if target_tree.inventory.root is None:
 
1403
        skip_root = True
 
1404
    else:
 
1405
        skip_root = False
 
1406
    basis_tree = None
 
1407
    try:
 
1408
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
1409
                kind, executable) in enumerate(change_list):
 
1410
            if skip_root and file_id[0] is not None and parent[0] is None:
 
1411
                continue
 
1412
            trans_id = tt.trans_id_file_id(file_id)
 
1413
            mode_id = None
 
1414
            if changed_content:
 
1415
                keep_content = False
 
1416
                if kind[0] == 'file' and (backups or kind[1] is None):
 
1417
                    wt_sha1 = working_tree.get_file_sha1(file_id)
 
1418
                    if merge_modified.get(file_id) != wt_sha1:
 
1419
                        # acquire the basis tree lazyily to prevent the expense
 
1420
                        # of accessing it when its not needed ? (Guessing, RBC,
 
1421
                        # 200702)
 
1422
                        if basis_tree is None:
 
1423
                            basis_tree = working_tree.basis_tree()
 
1424
                            basis_tree.lock_read()
 
1425
                        if file_id in basis_tree:
 
1426
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
1427
                                keep_content = True
 
1428
                        elif kind[1] is None and not versioned[1]:
 
1429
                            keep_content = True
 
1430
                if kind[0] is not None:
 
1431
                    if not keep_content:
 
1432
                        tt.delete_contents(trans_id)
 
1433
                    elif kind[1] is not None:
 
1434
                        parent_trans_id = tt.trans_id_file_id(parent[0])
 
1435
                        by_parent = tt.by_parent()
 
1436
                        backup_name = _get_backup_name(name[0], by_parent,
 
1437
                                                       parent_trans_id, tt)
 
1438
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
 
1439
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
 
1440
                        if versioned == (True, True):
 
1441
                            tt.unversion_file(trans_id)
 
1442
                            tt.version_file(file_id, new_trans_id)
 
1443
                        # New contents should have the same unix perms as old
 
1444
                        # contents
 
1445
                        mode_id = trans_id
 
1446
                        trans_id = new_trans_id
 
1447
                if kind[1] == 'directory':
 
1448
                    tt.create_directory(trans_id)
 
1449
                elif kind[1] == 'symlink':
 
1450
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
1451
                                      trans_id)
 
1452
                elif kind[1] == 'file':
 
1453
                    tt.create_file(target_tree.get_file_lines(file_id),
 
1454
                                   trans_id, mode_id)
 
1455
                    # preserve the execute bit when backing up
 
1456
                    if keep_content and executable[0] == executable[1]:
 
1457
                        tt.set_executability(executable[1], trans_id)
 
1458
                else:
 
1459
                    assert kind[1] is None
 
1460
            if versioned == (False, True):
 
1461
                tt.version_file(file_id, trans_id)
 
1462
            if versioned == (True, False):
 
1463
                tt.unversion_file(trans_id)
 
1464
            if (name[1] is not None and 
 
1465
                (name[0] != name[1] or parent[0] != parent[1])):
 
1466
                tt.adjust_path(
 
1467
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
 
1468
            if executable[0] != executable[1] and kind[1] == "file":
 
1469
                tt.set_executability(executable[1], trans_id)
 
1470
    finally:
 
1471
        if basis_tree is not None:
 
1472
            basis_tree.unlock()
 
1473
 
 
1474
 
 
1475
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1476
    """Make many conflict-resolution attempts, but die if they fail"""
 
1477
    if pass_func is None:
 
1478
        pass_func = conflict_pass
1145
1479
    new_conflicts = set()
1146
1480
    try:
1147
1481
        for n in range(10):
1149
1483
            conflicts = tt.find_conflicts()
1150
1484
            if len(conflicts) == 0:
1151
1485
                return new_conflicts
1152
 
            new_conflicts.update(conflict_pass(tt, conflicts))
 
1486
            new_conflicts.update(pass_func(tt, conflicts))
1153
1487
        raise MalformedTransform(conflicts=conflicts)
1154
1488
    finally:
1155
1489
        pb.clear()
1188
1522
            trans_id = conflict[1]
1189
1523
            try:
1190
1524
                tt.cancel_deletion(trans_id)
1191
 
                new_conflicts.add((c_type, 'Not deleting', trans_id))
 
1525
                new_conflicts.add(('deleting parent', 'Not deleting', 
 
1526
                                   trans_id))
1192
1527
            except KeyError:
1193
1528
                tt.create_directory(trans_id)
1194
 
                new_conflicts.add((c_type, 'Created directory.', trans_id))
 
1529
                new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1530
        elif c_type == 'unversioned parent':
1196
1531
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1532
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1533
    return new_conflicts
1199
1534
 
 
1535
 
1200
1536
def cook_conflicts(raw_conflicts, tt):
1201
1537
    """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
 
1538
    from bzrlib.conflicts import Conflict
 
1539
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
 
1540
    return sorted(conflict_iter, key=Conflict.sort_key)
1209
1541
 
1210
 
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1211
1542
 
1212
1543
def iter_cook_conflicts(raw_conflicts, tt):
1213
1544
    from bzrlib.conflicts import Conflict