~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

 * Hook up the new remote method ``RemoteBzrDir.find_repositoryV2`` so
   that it is now attempted first when lookup up repositories, leading to
   an extra round trip on older bzr smart servers but supporting the
   feature on newer servers. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import os
18
18
import errno
19
 
from stat import S_ISREG, S_IEXEC
 
19
from stat import S_ISREG
 
20
import tempfile
20
21
 
21
22
from bzrlib.lazy_import import lazy_import
22
23
lazy_import(globals(), """
23
24
from bzrlib import (
24
 
    annotate,
25
25
    bzrdir,
26
26
    delta,
27
27
    errors,
28
 
    inventory,
29
 
    osutils,
30
 
    revision as _mod_revision,
 
28
    inventory
31
29
    )
32
30
""")
33
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
35
33
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
36
34
                           UnableCreateSymlink)
37
35
from bzrlib.inventory import InventoryEntry
38
 
from bzrlib.osutils import (
39
 
    delete_any,
40
 
    file_kind,
41
 
    has_symlinks,
42
 
    lexists,
43
 
    pathjoin,
44
 
    sha_file,
45
 
    splitpath,
46
 
    supports_executable,
47
 
)
 
36
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
 
37
                            delete_any, has_symlinks)
48
38
from bzrlib.progress import DummyProgress, ProgressPhase
49
39
from bzrlib.symbol_versioning import (
50
40
        deprecated_function,
51
 
        deprecated_in,
 
41
        zero_fifteen,
 
42
        zero_ninety,
52
43
        )
53
44
from bzrlib.trace import mutter, warning
54
45
from bzrlib import tree
130
121
        # Cache of relpath results, to speed up canonical_path
131
122
        self._relpaths = {}
132
123
        # The trans_id that will be used as the tree root
133
 
        root_id = tree.get_root_id()
134
 
        if root_id is not None:
135
 
            self._new_root = self.trans_id_tree_file_id(root_id)
136
 
        else:
137
 
            self._new_root = None
 
124
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
138
125
        # Indictor of whether the transform has been applied
139
126
        self._done = False
140
127
        # A progress bar
201
188
        previous_name = self._new_name.get(trans_id)
202
189
        self._new_name[trans_id] = name
203
190
        self._new_parent[trans_id] = parent
204
 
        if parent == ROOT_PARENT:
205
 
            if self._new_root is not None:
206
 
                raise ValueError("Cannot have multiple roots.")
207
 
            self._new_root = trans_id
208
191
        if (trans_id in self._limbo_files and
209
192
            trans_id not in self._needs_rename):
210
193
            self._rename_in_limbo([trans_id])
267
250
        This reflects only files that already exist, not ones that will be
268
251
        added by transactions.
269
252
        """
270
 
        if inventory_id is None:
271
 
            raise ValueError('None is not a valid file id')
272
 
        path = self._tree.id2path(inventory_id)
 
253
        path = self._tree.inventory.id2path(inventory_id)
273
254
        return self.trans_id_tree_path(path)
274
255
 
275
256
    def trans_id_file_id(self, file_id):
278
259
        a transaction has been unversioned, it is deliberately still returned.
279
260
        (this will likely lead to an unversioned parent conflict.)
280
261
        """
281
 
        if file_id is None:
282
 
            raise ValueError('None is not a valid file id')
283
262
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
284
263
            return self._r_new_id[file_id]
 
264
        elif file_id in self._tree.inventory:
 
265
            return self.trans_id_tree_file_id(file_id)
 
266
        elif file_id in self._non_present_ids:
 
267
            return self._non_present_ids[file_id]
285
268
        else:
286
 
            try:
287
 
                self._tree.iter_entries_by_dir([file_id]).next()
288
 
            except StopIteration:
289
 
                if file_id in self._non_present_ids:
290
 
                    return self._non_present_ids[file_id]
291
 
                else:
292
 
                    trans_id = self._assign_id()
293
 
                    self._non_present_ids[file_id] = trans_id
294
 
                    return trans_id
295
 
            else:
296
 
                return self.trans_id_tree_file_id(file_id)
 
269
            trans_id = self._assign_id()
 
270
            self._non_present_ids[file_id] = trans_id
 
271
            return trans_id
297
272
 
298
273
    def canonical_path(self, path):
299
274
        """Get the canonical tree-relative path"""
372
347
        try:
373
348
            mode = os.stat(self._tree.abspath(old_path)).st_mode
374
349
        except OSError, e:
375
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
376
 
                # Either old_path doesn't exist, or the parent of the
377
 
                # target is not a directory (but will be one eventually)
378
 
                # Either way, we know it doesn't exist *right now*
379
 
                # See also bug #248448
 
350
            if e.errno == errno.ENOENT:
380
351
                return
381
352
            else:
382
353
                raise
383
354
        if typefunc(mode):
384
355
            os.chmod(self._limbo_name(trans_id), mode)
385
356
 
386
 
    def create_hardlink(self, path, trans_id):
387
 
        """Schedule creation of a hard link"""
388
 
        name = self._limbo_name(trans_id)
389
 
        try:
390
 
            os.link(path, name)
391
 
        except OSError, e:
392
 
            if e.errno != errno.EPERM:
393
 
                raise
394
 
            raise errors.HardLinkNotSupported(path)
395
 
        try:
396
 
            unique_add(self._new_contents, trans_id, 'file')
397
 
        except:
398
 
            # Clean up the file, it never got registered so
399
 
            # TreeTransform.finalize() won't clean it up.
400
 
            os.unlink(name)
401
 
            raise
402
 
 
403
357
    def create_directory(self, trans_id):
404
358
        """Schedule creation of a new directory.
405
359
        
469
423
 
470
424
    def version_file(self, file_id, trans_id):
471
425
        """Schedule a file to become versioned."""
472
 
        if file_id is None:
473
 
            raise ValueError()
 
426
        assert file_id is not None
474
427
        unique_add(self._new_id, trans_id, file_id)
475
428
        unique_add(self._r_new_id, file_id, trans_id)
476
429
 
480
433
        del self._new_id[trans_id]
481
434
        del self._r_new_id[file_id]
482
435
 
483
 
    def new_paths(self, filesystem_only=False):
484
 
        """Determine the paths of all new and changed files.
485
 
 
486
 
        :param filesystem_only: if True, only calculate values for files
487
 
            that require renames or execute bit changes.
488
 
        """
489
 
        new_ids = set()
490
 
        if filesystem_only:
491
 
            stale_ids = self._needs_rename.difference(self._new_name)
492
 
            stale_ids.difference_update(self._new_parent)
493
 
            stale_ids.difference_update(self._new_contents)
494
 
            stale_ids.difference_update(self._new_id)
495
 
            needs_rename = self._needs_rename.difference(stale_ids)
496
 
            id_sets = (needs_rename, self._new_executability)
497
 
        else:
498
 
            id_sets = (self._new_name, self._new_parent, self._new_contents,
499
 
                       self._new_id, self._new_executability)
500
 
        for id_set in id_sets:
501
 
            new_ids.update(id_set)
502
 
        return sorted(FinalPaths(self).get_paths(new_ids))
503
 
 
504
 
    def _inventory_altered(self):
505
 
        """Get the trans_ids and paths of files needing new inv entries."""
506
 
        new_ids = set()
507
 
        for id_set in [self._new_name, self._new_parent, self._new_id,
508
 
                       self._new_executability]:
509
 
            new_ids.update(id_set)
510
 
        changed_kind = set(self._removed_contents)
511
 
        changed_kind.intersection_update(self._new_contents)
512
 
        changed_kind.difference_update(new_ids)
513
 
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
514
 
                        self.final_kind(t))
515
 
        new_ids.update(changed_kind)
516
 
        return sorted(FinalPaths(self).get_paths(new_ids))
 
436
    def new_paths(self):
 
437
        """Determine the paths of all new and changed files"""
 
438
        new_ids = set()
 
439
        fp = FinalPaths(self)
 
440
        for id_set in (self._new_name, self._new_parent, self._new_contents,
 
441
                       self._new_id, self._new_executability):
 
442
            new_ids.update(id_set)
 
443
        new_paths = [(fp.get_path(t), t) for t in new_ids]
 
444
        new_paths.sort()
 
445
        return new_paths
517
446
 
518
447
    def tree_kind(self, trans_id):
519
448
        """Determine the file kind in the working tree.
555
484
        # the file is old; the old id is still valid
556
485
        if self._new_root == trans_id:
557
486
            return self._tree.get_root_id()
558
 
        return self._tree.path2id(path)
 
487
        return self._tree.inventory.path2id(path)
559
488
 
560
489
    def final_file_id(self, trans_id):
561
490
        """Determine the file id after any changes are applied, or None.
564
493
        applied.
565
494
        """
566
495
        try:
 
496
            # there is a new id for this file
 
497
            assert self._new_id[trans_id] is not None
567
498
            return self._new_id[trans_id]
568
499
        except KeyError:
569
500
            if trans_id in self._removed_id:
673
604
        try:
674
605
            children = os.listdir(self._tree.abspath(path))
675
606
        except OSError, e:
676
 
            if not (osutils._is_error_enotdir(e)
677
 
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
 
607
            if e.errno not in (errno.ENOENT, errno.ESRCH, errno.ENOTDIR):
678
608
                raise
679
609
            return
680
 
 
 
610
            
681
611
        for child in children:
682
612
            childpath = joinpath(path, child)
683
613
            if self._tree.is_control_filename(childpath):
823
753
        conflicts = []
824
754
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
825
755
                                self._removed_id))
826
 
        all_ids = self._tree.all_file_ids()
827
 
        active_tree_ids = all_ids.difference(removed_tree_ids)
 
756
        active_tree_ids = set((f for f in self._tree.inventory if
 
757
                               f not in removed_tree_ids))
828
758
        for trans_id, file_id in self._new_id.iteritems():
829
759
            if file_id in active_tree_ids:
830
760
                old_trans_id = self.trans_id_tree_file_id(file_id)
908
838
        self._limbo_files[trans_id] = limbo_name
909
839
        return limbo_name
910
840
 
911
 
    def _set_executability(self, path, trans_id):
 
841
    def _set_executability(self, path, entry, trans_id):
912
842
        """Set the executability of versioned files """
 
843
        new_executability = self._new_executability[trans_id]
 
844
        entry.executable = new_executability
913
845
        if supports_executable():
914
 
            new_executability = self._new_executability[trans_id]
915
846
            abspath = self._tree.abspath(path)
916
847
            current_mode = os.stat(abspath).st_mode
917
848
            if new_executability:
1012
943
        from_path = self._tree_id_paths.get(from_trans_id)
1013
944
        if from_versioned:
1014
945
            # get data from working tree if versioned
1015
 
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
 
946
            from_entry = self._tree.inventory[file_id]
1016
947
            from_name = from_entry.name
1017
948
            from_parent = from_entry.parent_id
1018
949
        else:
1054
985
            to_executable = False
1055
986
        return to_name, to_parent, to_kind, to_executable
1056
987
 
1057
 
    def iter_changes(self):
1058
 
        """Produce output in the same format as Tree.iter_changes.
 
988
    def _iter_changes(self):
 
989
        """Produce output in the same format as Tree._iter_changes.
1059
990
 
1060
991
        Will produce nonsensical results if invoked while inventory/filesystem
1061
992
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
1199
1130
        tree.lock_tree_write()
1200
1131
 
1201
1132
        try:
 
1133
            control_files = tree._control_files
1202
1134
            limbodir = urlutils.local_path_from_url(
1203
 
                tree._transport.abspath('limbo'))
 
1135
                control_files.controlfilename('limbo'))
1204
1136
            try:
1205
1137
                os.mkdir(limbodir)
1206
1138
            except OSError, e:
1207
1139
                if e.errno == errno.EEXIST:
1208
1140
                    raise ExistingLimbo(limbodir)
1209
1141
            deletiondir = urlutils.local_path_from_url(
1210
 
                tree._transport.abspath('pending-deletion'))
 
1142
                control_files.controlfilename('pending-deletion'))
1211
1143
            try:
1212
1144
                os.mkdir(deletiondir)
1213
1145
            except OSError, e:
1221
1153
                                   tree.case_sensitive)
1222
1154
        self._deletiondir = deletiondir
1223
1155
 
1224
 
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
 
1156
    def apply(self, no_conflicts=False, _mover=None):
1225
1157
        """Apply all changes to the inventory and filesystem.
1226
1158
 
1227
1159
        If filesystem or inventory conflicts are present, MalformedTransform
1231
1163
 
1232
1164
        :param no_conflicts: if True, the caller guarantees there are no
1233
1165
            conflicts, so no check is made.
1234
 
        :param precomputed_delta: An inventory delta to use instead of
1235
 
            calculating one.
1236
1166
        :param _mover: Supply an alternate FileMover, for testing
1237
1167
        """
1238
1168
        if not no_conflicts:
1239
1169
            conflicts = self.find_conflicts()
1240
1170
            if len(conflicts) != 0:
1241
1171
                raise MalformedTransform(conflicts=conflicts)
 
1172
        inv = self._tree.inventory
 
1173
        inventory_delta = []
1242
1174
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1243
1175
        try:
1244
 
            if precomputed_delta is None:
1245
 
                child_pb.update('Apply phase', 0, 2)
1246
 
                inventory_delta = self._generate_inventory_delta()
1247
 
                offset = 1
1248
 
            else:
1249
 
                inventory_delta = precomputed_delta
1250
 
                offset = 0
1251
1176
            if _mover is None:
1252
1177
                mover = _FileMover()
1253
1178
            else:
1254
1179
                mover = _mover
1255
1180
            try:
1256
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
1257
 
                self._apply_removals(mover)
1258
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
1259
 
                modified_paths = self._apply_insertions(mover)
 
1181
                child_pb.update('Apply phase', 0, 2)
 
1182
                self._apply_removals(inv, inventory_delta, mover)
 
1183
                child_pb.update('Apply phase', 1, 2)
 
1184
                modified_paths = self._apply_insertions(inv, inventory_delta,
 
1185
                                                        mover)
1260
1186
            except:
1261
1187
                mover.rollback()
1262
1188
                raise
1269
1195
        self.finalize()
1270
1196
        return _TransformResults(modified_paths, self.rename_count)
1271
1197
 
1272
 
    def _generate_inventory_delta(self):
1273
 
        """Generate an inventory delta for the current transform."""
1274
 
        inventory_delta = []
1275
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1276
 
        new_paths = self._inventory_altered()
1277
 
        total_entries = len(new_paths) + len(self._removed_id)
1278
 
        try:
1279
 
            for num, trans_id in enumerate(self._removed_id):
1280
 
                if (num % 10) == 0:
1281
 
                    child_pb.update('removing file', num, total_entries)
1282
 
                if trans_id == self._new_root:
1283
 
                    file_id = self._tree.get_root_id()
1284
 
                else:
1285
 
                    file_id = self.tree_file_id(trans_id)
1286
 
                # File-id isn't really being deleted, just moved
1287
 
                if file_id in self._r_new_id:
1288
 
                    continue
1289
 
                path = self._tree_id_paths[trans_id]
1290
 
                inventory_delta.append((path, None, file_id, None))
1291
 
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1292
 
                                     new_paths)
1293
 
            entries = self._tree.iter_entries_by_dir(
1294
 
                new_path_file_ids.values())
1295
 
            old_paths = dict((e.file_id, p) for p, e in entries)
1296
 
            final_kinds = {}
1297
 
            for num, (path, trans_id) in enumerate(new_paths):
1298
 
                if (num % 10) == 0:
1299
 
                    child_pb.update('adding file',
1300
 
                                    num + len(self._removed_id), total_entries)
1301
 
                file_id = new_path_file_ids[trans_id]
1302
 
                if file_id is None:
1303
 
                    continue
1304
 
                needs_entry = False
1305
 
                try:
1306
 
                    kind = self.final_kind(trans_id)
1307
 
                except NoSuchFile:
1308
 
                    kind = self._tree.stored_kind(file_id)
1309
 
                parent_trans_id = self.final_parent(trans_id)
1310
 
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1311
 
                if parent_file_id is None:
1312
 
                    parent_file_id = self.final_file_id(parent_trans_id)
1313
 
                if trans_id in self._new_reference_revision:
1314
 
                    new_entry = inventory.TreeReference(
1315
 
                        file_id,
1316
 
                        self._new_name[trans_id],
1317
 
                        self.final_file_id(self._new_parent[trans_id]),
1318
 
                        None, self._new_reference_revision[trans_id])
1319
 
                else:
1320
 
                    new_entry = inventory.make_entry(kind,
1321
 
                        self.final_name(trans_id),
1322
 
                        parent_file_id, file_id)
1323
 
                old_path = old_paths.get(new_entry.file_id)
1324
 
                new_executability = self._new_executability.get(trans_id)
1325
 
                if new_executability is not None:
1326
 
                    new_entry.executable = new_executability
1327
 
                inventory_delta.append(
1328
 
                    (old_path, path, new_entry.file_id, new_entry))
1329
 
        finally:
1330
 
            child_pb.finished()
1331
 
        return inventory_delta
1332
 
 
1333
 
    def _apply_removals(self, mover):
 
1198
    def _apply_removals(self, inv, inventory_delta, mover):
1334
1199
        """Perform tree operations that remove directory/inventory names.
1335
1200
 
1336
1201
        That is, delete files that are to be deleted, and put any files that
1337
1202
        need renaming into limbo.  This must be done in strict child-to-parent
1338
1203
        order.
1339
 
 
1340
 
        If inventory_delta is None, no inventory delta generation is performed.
1341
1204
        """
1342
1205
        tree_paths = list(self._tree_path_ids.iteritems())
1343
1206
        tree_paths.sort(reverse=True)
1359
1222
                            raise
1360
1223
                    else:
1361
1224
                        self.rename_count += 1
 
1225
                if trans_id in self._removed_id:
 
1226
                    if trans_id == self._new_root:
 
1227
                        file_id = self._tree.get_root_id()
 
1228
                    else:
 
1229
                        file_id = self.tree_file_id(trans_id)
 
1230
                    if file_id is not None:
 
1231
                        inventory_delta.append((path, None, file_id, None))
1362
1232
        finally:
1363
1233
            child_pb.finished()
1364
1234
 
1365
 
    def _apply_insertions(self, mover):
 
1235
    def _apply_insertions(self, inv, inventory_delta, mover):
1366
1236
        """Perform tree operations that insert directory/inventory names.
1367
1237
 
1368
1238
        That is, create any files that need to be created, and restore from
1369
1239
        limbo any files that needed renaming.  This must be done in strict
1370
1240
        parent-to-child order.
1371
 
 
1372
 
        If inventory_delta is None, no inventory delta is calculated, and
1373
 
        no list of modified paths is returned.
1374
1241
        """
1375
 
        new_paths = self.new_paths(filesystem_only=True)
 
1242
        new_paths = self.new_paths()
1376
1243
        modified_paths = []
1377
 
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1378
 
                                 new_paths)
1379
1244
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1245
        completed_new = []
1380
1246
        try:
1381
1247
            for num, (path, trans_id) in enumerate(new_paths):
1382
 
                if (num % 10) == 0:
1383
 
                    child_pb.update('adding file', num, len(new_paths))
1384
 
                full_path = self._tree.abspath(path)
1385
 
                if trans_id in self._needs_rename:
1386
 
                    try:
1387
 
                        mover.rename(self._limbo_name(trans_id), full_path)
1388
 
                    except OSError, e:
1389
 
                        # We may be renaming a dangling inventory id
1390
 
                        if e.errno != errno.ENOENT:
1391
 
                            raise
1392
 
                    else:
1393
 
                        self.rename_count += 1
1394
 
                if (trans_id in self._new_contents or
1395
 
                    self.path_changed(trans_id)):
 
1248
                new_entry = None
 
1249
                child_pb.update('adding file', num, len(new_paths))
 
1250
                try:
 
1251
                    kind = self._new_contents[trans_id]
 
1252
                except KeyError:
 
1253
                    kind = contents = None
 
1254
                if trans_id in self._new_contents or \
 
1255
                    self.path_changed(trans_id):
 
1256
                    full_path = self._tree.abspath(path)
 
1257
                    if trans_id in self._needs_rename:
 
1258
                        try:
 
1259
                            mover.rename(self._limbo_name(trans_id), full_path)
 
1260
                        except OSError, e:
 
1261
                            # We may be renaming a dangling inventory id
 
1262
                            if e.errno != errno.ENOENT:
 
1263
                                raise
 
1264
                        else:
 
1265
                            self.rename_count += 1
1396
1266
                    if trans_id in self._new_contents:
1397
1267
                        modified_paths.append(full_path)
 
1268
                        completed_new.append(trans_id)
 
1269
 
 
1270
                if trans_id in self._new_id:
 
1271
                    if kind is None:
 
1272
                        kind = file_kind(self._tree.abspath(path))
 
1273
                    if trans_id in self._new_reference_revision:
 
1274
                        new_entry = inventory.TreeReference(
 
1275
                            self._new_id[trans_id],
 
1276
                            self._new_name[trans_id],
 
1277
                            self.final_file_id(self._new_parent[trans_id]),
 
1278
                            None, self._new_reference_revision[trans_id])
 
1279
                    else:
 
1280
                        new_entry = inventory.make_entry(kind,
 
1281
                            self.final_name(trans_id),
 
1282
                            self.final_file_id(self.final_parent(trans_id)),
 
1283
                            self._new_id[trans_id])
 
1284
                else:
 
1285
                    if trans_id in self._new_name or trans_id in\
 
1286
                        self._new_parent or\
 
1287
                        trans_id in self._new_executability:
 
1288
                        file_id = self.final_file_id(trans_id)
 
1289
                        if file_id is not None:
 
1290
                            entry = inv[file_id]
 
1291
                            new_entry = entry.copy()
 
1292
 
 
1293
                    if trans_id in self._new_name or trans_id in\
 
1294
                        self._new_parent:
 
1295
                            if new_entry is not None:
 
1296
                                new_entry.name = self.final_name(trans_id)
 
1297
                                parent = self.final_parent(trans_id)
 
1298
                                parent_id = self.final_file_id(parent)
 
1299
                                new_entry.parent_id = parent_id
 
1300
 
1398
1301
                if trans_id in self._new_executability:
1399
 
                    self._set_executability(path, trans_id)
 
1302
                    self._set_executability(path, new_entry, trans_id)
 
1303
                if new_entry is not None:
 
1304
                    if new_entry.file_id in inv:
 
1305
                        old_path = inv.id2path(new_entry.file_id)
 
1306
                    else:
 
1307
                        old_path = None
 
1308
                    inventory_delta.append((old_path, path,
 
1309
                                            new_entry.file_id,
 
1310
                                            new_entry))
1400
1311
        finally:
1401
1312
            child_pb.finished()
1402
 
        self._new_contents.clear()
 
1313
        for trans_id in completed_new:
 
1314
            del self._new_contents[trans_id]
1403
1315
        return modified_paths
1404
1316
 
1405
1317
 
1413
1325
 
1414
1326
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1415
1327
        tree.lock_read()
1416
 
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
 
1328
        limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1417
1329
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1418
1330
 
1419
1331
    def canonical_path(self, path):
1442
1354
        except KeyError:
1443
1355
            return
1444
1356
        file_id = self.tree_file_id(parent_id)
1445
 
        if file_id is None:
1446
 
            return
1447
 
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1448
 
        children = getattr(entry, 'children', {})
1449
 
        for child in children:
 
1357
        for child in self._tree.inventory[file_id].children.iterkeys():
1450
1358
            childpath = joinpath(path, child)
1451
1359
            yield self.trans_id_tree_path(childpath)
1452
1360
 
1453
1361
 
1454
 
class _PreviewTree(tree.Tree):
 
1362
class _PreviewTree(object):
1455
1363
    """Partial implementation of Tree to support show_diff_trees"""
1456
1364
 
1457
1365
    def __init__(self, transform):
1458
1366
        self._transform = transform
1459
 
        self._final_paths = FinalPaths(transform)
1460
 
        self.__by_parent = None
1461
 
        self._parent_ids = []
1462
 
        self._all_children_cache = {}
1463
 
        self._path2trans_id_cache = {}
1464
 
        self._final_name_cache = {}
1465
 
 
1466
 
    def _changes(self, file_id):
1467
 
        for changes in self._transform.iter_changes():
1468
 
            if changes[0] == file_id:
1469
 
                return changes
1470
 
 
1471
 
    def _content_change(self, file_id):
1472
 
        """Return True if the content of this file changed"""
1473
 
        changes = self._changes(file_id)
1474
 
        # changes[2] is true if the file content changed.  See
1475
 
        # InterTree.iter_changes.
1476
 
        return (changes is not None and changes[2])
1477
 
 
1478
 
    def _get_repository(self):
1479
 
        repo = getattr(self._transform._tree, '_repository', None)
1480
 
        if repo is None:
1481
 
            repo = self._transform._tree.branch.repository
1482
 
        return repo
1483
 
 
1484
 
    def _iter_parent_trees(self):
1485
 
        for revision_id in self.get_parent_ids():
1486
 
            try:
1487
 
                yield self.revision_tree(revision_id)
1488
 
            except errors.NoSuchRevisionInTree:
1489
 
                yield self._get_repository().revision_tree(revision_id)
1490
 
 
1491
 
    def _get_file_revision(self, file_id, vf, tree_revision):
1492
 
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1493
 
                       self._iter_parent_trees()]
1494
 
        vf.add_lines((file_id, tree_revision), parent_keys,
1495
 
                     self.get_file(file_id).readlines())
1496
 
        repo = self._get_repository()
1497
 
        base_vf = repo.texts
1498
 
        if base_vf not in vf.fallback_versionedfiles:
1499
 
            vf.fallback_versionedfiles.append(base_vf)
1500
 
        return tree_revision
1501
 
 
1502
 
    def _stat_limbo_file(self, file_id):
1503
 
        trans_id = self._transform.trans_id_file_id(file_id)
1504
 
        name = self._transform._limbo_name(trans_id)
1505
 
        return os.lstat(name)
1506
 
 
1507
 
    @property
1508
 
    def _by_parent(self):
1509
 
        if self.__by_parent is None:
1510
 
            self.__by_parent = self._transform.by_parent()
1511
 
        return self.__by_parent
1512
 
 
1513
 
    def _comparison_data(self, entry, path):
1514
 
        kind, size, executable, link_or_sha1 = self.path_content_summary(path)
1515
 
        if kind == 'missing':
1516
 
            kind = None
1517
 
            executable = False
1518
 
        else:
1519
 
            file_id = self._transform.final_file_id(self._path2trans_id(path))
1520
 
            executable = self.is_executable(file_id, path)
1521
 
        return kind, executable, None
1522
1367
 
1523
1368
    def lock_read(self):
1524
1369
        # Perhaps in theory, this should lock the TreeTransform?
1527
1372
    def unlock(self):
1528
1373
        pass
1529
1374
 
1530
 
    @property
1531
 
    def inventory(self):
1532
 
        """This Tree does not use inventory as its backing data."""
1533
 
        raise NotImplementedError(_PreviewTree.inventory)
1534
 
 
1535
 
    def get_root_id(self):
1536
 
        return self._transform.final_file_id(self._transform.root)
1537
 
 
1538
 
    def all_file_ids(self):
1539
 
        tree_ids = set(self._transform._tree.all_file_ids())
1540
 
        tree_ids.difference_update(self._transform.tree_file_id(t)
1541
 
                                   for t in self._transform._removed_id)
1542
 
        tree_ids.update(self._transform._new_id.values())
1543
 
        return tree_ids
1544
 
 
1545
 
    def __iter__(self):
1546
 
        return iter(self.all_file_ids())
1547
 
 
1548
 
    def has_id(self, file_id):
1549
 
        if file_id in self._transform._r_new_id:
1550
 
            return True
1551
 
        elif file_id in self._transform._removed_id:
1552
 
            return False
1553
 
        else:
1554
 
            return self._transform._tree.has_id(file_id)
1555
 
 
1556
 
    def _path2trans_id(self, path):
1557
 
        # We must not use None here, because that is a valid value to store.
1558
 
        trans_id = self._path2trans_id_cache.get(path, object)
1559
 
        if trans_id is not object:
1560
 
            return trans_id
1561
 
        segments = splitpath(path)
1562
 
        cur_parent = self._transform.root
1563
 
        for cur_segment in segments:
1564
 
            for child in self._all_children(cur_parent):
1565
 
                final_name = self._final_name_cache.get(child)
1566
 
                if final_name is None:
1567
 
                    final_name = self._transform.final_name(child)
1568
 
                    self._final_name_cache[child] = final_name
1569
 
                if final_name == cur_segment:
1570
 
                    cur_parent = child
1571
 
                    break
1572
 
            else:
1573
 
                self._path2trans_id_cache[path] = None
1574
 
                return None
1575
 
        self._path2trans_id_cache[path] = cur_parent
1576
 
        return cur_parent
1577
 
 
1578
 
    def path2id(self, path):
1579
 
        return self._transform.final_file_id(self._path2trans_id(path))
1580
 
 
1581
 
    def id2path(self, file_id):
1582
 
        trans_id = self._transform.trans_id_file_id(file_id)
1583
 
        try:
1584
 
            return self._final_paths._determine_path(trans_id)
1585
 
        except NoFinalPath:
1586
 
            raise errors.NoSuchId(self, file_id)
1587
 
 
1588
 
    def _all_children(self, trans_id):
1589
 
        children = self._all_children_cache.get(trans_id)
1590
 
        if children is not None:
1591
 
            return children
1592
 
        children = set(self._transform.iter_tree_children(trans_id))
1593
 
        # children in the _new_parent set are provided by _by_parent.
1594
 
        children.difference_update(self._transform._new_parent.keys())
1595
 
        children.update(self._by_parent.get(trans_id, []))
1596
 
        self._all_children_cache[trans_id] = children
1597
 
        return children
1598
 
 
1599
 
    def iter_children(self, file_id):
1600
 
        trans_id = self._transform.trans_id_file_id(file_id)
1601
 
        for child_trans_id in self._all_children(trans_id):
1602
 
            yield self._transform.final_file_id(child_trans_id)
1603
 
 
1604
 
    def extras(self):
1605
 
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
1606
 
                              in self._transform._tree.extras())
1607
 
        possible_extras.update(self._transform._new_contents)
1608
 
        possible_extras.update(self._transform._removed_id)
1609
 
        for trans_id in possible_extras:
1610
 
            if self._transform.final_file_id(trans_id) is None:
1611
 
                yield self._final_paths._determine_path(trans_id)
1612
 
 
1613
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids):
1614
 
        for trans_id, parent_file_id in ordered_entries:
1615
 
            file_id = self._transform.final_file_id(trans_id)
1616
 
            if file_id is None:
1617
 
                continue
1618
 
            if (specific_file_ids is not None
1619
 
                and file_id not in specific_file_ids):
1620
 
                continue
1621
 
            try:
1622
 
                kind = self._transform.final_kind(trans_id)
1623
 
            except NoSuchFile:
1624
 
                kind = self._transform._tree.stored_kind(file_id)
1625
 
            new_entry = inventory.make_entry(
1626
 
                kind,
1627
 
                self._transform.final_name(trans_id),
1628
 
                parent_file_id, file_id)
1629
 
            yield new_entry, trans_id
1630
 
 
1631
 
    def _list_files_by_dir(self):
1632
 
        todo = [ROOT_PARENT]
1633
 
        ordered_ids = []
1634
 
        while len(todo) > 0:
1635
 
            parent = todo.pop()
1636
 
            parent_file_id = self._transform.final_file_id(parent)
1637
 
            children = list(self._all_children(parent))
1638
 
            paths = dict(zip(children, self._final_paths.get_paths(children)))
1639
 
            children.sort(key=paths.get)
1640
 
            todo.extend(reversed(children))
1641
 
            for trans_id in children:
1642
 
                ordered_ids.append((trans_id, parent_file_id))
1643
 
        return ordered_ids
1644
 
 
1645
 
    def iter_entries_by_dir(self, specific_file_ids=None):
1646
 
        # This may not be a maximally efficient implementation, but it is
1647
 
        # reasonably straightforward.  An implementation that grafts the
1648
 
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1649
 
        # might be more efficient, but requires tricky inferences about stack
1650
 
        # position.
1651
 
        ordered_ids = self._list_files_by_dir()
1652
 
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1653
 
                                                      specific_file_ids):
1654
 
            yield unicode(self._final_paths.get_path(trans_id)), entry
1655
 
 
1656
 
    def list_files(self, include_root=False):
1657
 
        """See Tree.list_files."""
1658
 
        # XXX This should behave like WorkingTree.list_files, but is really
1659
 
        # more like RevisionTree.list_files.
1660
 
        for path, entry in self.iter_entries_by_dir():
1661
 
            if entry.name == '' and not include_root:
1662
 
                continue
1663
 
            yield path, 'V', entry.kind, entry.file_id, entry
 
1375
    def _iter_changes(self, from_tree, include_unchanged=False,
 
1376
                      specific_files=None, pb=None, extra_trees=None,
 
1377
                      require_versioned=True, want_unversioned=False):
 
1378
        """See InterTree._iter_changes.
 
1379
 
 
1380
        This implementation does not support include_unchanged, specific_files,
 
1381
        or want_unversioned.  extra_trees, require_versioned, and pb are
 
1382
        ignored.
 
1383
        """
 
1384
        if from_tree is not self._transform._tree:
 
1385
            raise ValueError('from_tree must be transform source tree.')
 
1386
        if include_unchanged:
 
1387
            raise ValueError('include_unchanged is not supported')
 
1388
        if specific_files is not None:
 
1389
            raise ValueError('specific_files is not supported')
 
1390
        if want_unversioned:
 
1391
            raise ValueError('want_unversioned is not supported')
 
1392
        return self._transform._iter_changes()
1664
1393
 
1665
1394
    def kind(self, file_id):
1666
1395
        trans_id = self._transform.trans_id_file_id(file_id)
1667
1396
        return self._transform.final_kind(trans_id)
1668
1397
 
1669
 
    def stored_kind(self, file_id):
1670
 
        trans_id = self._transform.trans_id_file_id(file_id)
1671
 
        try:
1672
 
            return self._transform._new_contents[trans_id]
1673
 
        except KeyError:
1674
 
            return self._transform._tree.stored_kind(file_id)
1675
 
 
1676
1398
    def get_file_mtime(self, file_id, path=None):
1677
1399
        """See Tree.get_file_mtime"""
1678
 
        if not self._content_change(file_id):
1679
 
            return self._transform._tree.get_file_mtime(file_id, path)
1680
 
        return self._stat_limbo_file(file_id).st_mtime
1681
 
 
1682
 
    def _file_size(self, entry, stat_value):
1683
 
        return self.get_file_size(entry.file_id)
1684
 
 
1685
 
    def get_file_size(self, file_id):
1686
 
        """See Tree.get_file_size"""
1687
 
        if self.kind(file_id) == 'file':
1688
 
            return self._transform._tree.get_file_size(file_id)
1689
 
        else:
1690
 
            return None
1691
 
 
1692
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
1693
 
        trans_id = self._transform.trans_id_file_id(file_id)
1694
 
        kind = self._transform._new_contents.get(trans_id)
1695
 
        if kind is None:
1696
 
            return self._transform._tree.get_file_sha1(file_id)
1697
 
        if kind == 'file':
1698
 
            fileobj = self.get_file(file_id)
1699
 
            try:
1700
 
                return sha_file(fileobj)
1701
 
            finally:
1702
 
                fileobj.close()
1703
 
 
1704
 
    def is_executable(self, file_id, path=None):
1705
 
        if file_id is None:
1706
 
            return False
1707
 
        trans_id = self._transform.trans_id_file_id(file_id)
1708
 
        try:
1709
 
            return self._transform._new_executability[trans_id]
1710
 
        except KeyError:
1711
 
            try:
1712
 
                return self._transform._tree.is_executable(file_id, path)
1713
 
            except OSError, e:
1714
 
                if e.errno == errno.ENOENT:
1715
 
                    return False
1716
 
                raise
1717
 
            except errors.NoSuchId:
1718
 
                return False
1719
 
 
1720
 
    def path_content_summary(self, path):
1721
 
        trans_id = self._path2trans_id(path)
1722
 
        tt = self._transform
1723
 
        tree_path = tt._tree_id_paths.get(trans_id)
1724
 
        kind = tt._new_contents.get(trans_id)
1725
 
        if kind is None:
1726
 
            if tree_path is None or trans_id in tt._removed_contents:
1727
 
                return 'missing', None, None, None
1728
 
            summary = tt._tree.path_content_summary(tree_path)
1729
 
            kind, size, executable, link_or_sha1 = summary
1730
 
        else:
1731
 
            link_or_sha1 = None
1732
 
            limbo_name = tt._limbo_name(trans_id)
1733
 
            if trans_id in tt._new_reference_revision:
1734
 
                kind = 'tree-reference'
1735
 
            if kind == 'file':
1736
 
                statval = os.lstat(limbo_name)
1737
 
                size = statval.st_size
1738
 
                if not supports_executable():
1739
 
                    executable = None
1740
 
                else:
1741
 
                    executable = statval.st_mode & S_IEXEC
1742
 
            else:
1743
 
                size = None
1744
 
                executable = None
1745
 
            if kind == 'symlink':
1746
 
                link_or_sha1 = os.readlink(limbo_name)
1747
 
        if supports_executable():
1748
 
            executable = tt._new_executability.get(trans_id, executable)
1749
 
        return kind, size, executable, link_or_sha1
1750
 
 
1751
 
    def iter_changes(self, from_tree, include_unchanged=False,
1752
 
                      specific_files=None, pb=None, extra_trees=None,
1753
 
                      require_versioned=True, want_unversioned=False):
1754
 
        """See InterTree.iter_changes.
1755
 
 
1756
 
        This has a fast path that is only used when the from_tree matches
1757
 
        the transform tree, and no fancy options are supplied.
1758
 
        """
1759
 
        if (from_tree is not self._transform._tree or include_unchanged or
1760
 
            specific_files or want_unversioned):
1761
 
            return tree.InterTree(from_tree, self).iter_changes(
1762
 
                include_unchanged=include_unchanged,
1763
 
                specific_files=specific_files,
1764
 
                pb=pb,
1765
 
                extra_trees=extra_trees,
1766
 
                require_versioned=require_versioned,
1767
 
                want_unversioned=want_unversioned)
1768
 
        if want_unversioned:
1769
 
            raise ValueError('want_unversioned is not supported')
1770
 
        return self._transform.iter_changes()
1771
 
 
1772
 
    def get_file(self, file_id, path=None):
 
1400
        trans_id = self._transform.trans_id_file_id(file_id)
 
1401
        name = self._transform._limbo_name(trans_id)
 
1402
        return os.stat(name).st_mtime
 
1403
 
 
1404
    def get_file(self, file_id):
1773
1405
        """See Tree.get_file"""
1774
 
        if not self._content_change(file_id):
1775
 
            return self._transform._tree.get_file(file_id, path)
1776
1406
        trans_id = self._transform.trans_id_file_id(file_id)
1777
1407
        name = self._transform._limbo_name(trans_id)
1778
1408
        return open(name, 'rb')
1779
1409
 
1780
 
    def annotate_iter(self, file_id,
1781
 
                      default_revision=_mod_revision.CURRENT_REVISION):
1782
 
        changes = self._changes(file_id)
1783
 
        if changes is None:
1784
 
            get_old = True
1785
 
        else:
1786
 
            changed_content, versioned, kind = (changes[2], changes[3],
1787
 
                                                changes[6])
1788
 
            if kind[1] is None:
1789
 
                return None
1790
 
            get_old = (kind[0] == 'file' and versioned[0])
1791
 
        if get_old:
1792
 
            old_annotation = self._transform._tree.annotate_iter(file_id,
1793
 
                default_revision=default_revision)
1794
 
        else:
1795
 
            old_annotation = []
1796
 
        if changes is None:
1797
 
            return old_annotation
1798
 
        if not changed_content:
1799
 
            return old_annotation
1800
 
        return annotate.reannotate([old_annotation],
1801
 
                                   self.get_file(file_id).readlines(),
1802
 
                                   default_revision)
1803
 
 
1804
 
    def get_symlink_target(self, file_id):
1805
 
        """See Tree.get_symlink_target"""
1806
 
        if not self._content_change(file_id):
1807
 
            return self._transform._tree.get_symlink_target(file_id)
1808
 
        trans_id = self._transform.trans_id_file_id(file_id)
1809
 
        name = self._transform._limbo_name(trans_id)
1810
 
        return os.readlink(name)
1811
 
 
1812
 
    def walkdirs(self, prefix=''):
1813
 
        pending = [self._transform.root]
1814
 
        while len(pending) > 0:
1815
 
            parent_id = pending.pop()
1816
 
            children = []
1817
 
            subdirs = []
1818
 
            prefix = prefix.rstrip('/')
1819
 
            parent_path = self._final_paths.get_path(parent_id)
1820
 
            parent_file_id = self._transform.final_file_id(parent_id)
1821
 
            for child_id in self._all_children(parent_id):
1822
 
                path_from_root = self._final_paths.get_path(child_id)
1823
 
                basename = self._transform.final_name(child_id)
1824
 
                file_id = self._transform.final_file_id(child_id)
1825
 
                try:
1826
 
                    kind = self._transform.final_kind(child_id)
1827
 
                    versioned_kind = kind
1828
 
                except NoSuchFile:
1829
 
                    kind = 'unknown'
1830
 
                    versioned_kind = self._transform._tree.stored_kind(file_id)
1831
 
                if versioned_kind == 'directory':
1832
 
                    subdirs.append(child_id)
1833
 
                children.append((path_from_root, basename, kind, None,
1834
 
                                 file_id, versioned_kind))
1835
 
            children.sort()
1836
 
            if parent_path.startswith(prefix):
1837
 
                yield (parent_path, parent_file_id), children
1838
 
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1839
 
                                  reverse=True))
1840
 
 
1841
 
    def get_parent_ids(self):
1842
 
        return self._parent_ids
1843
 
 
1844
 
    def set_parent_ids(self, parent_ids):
1845
 
        self._parent_ids = parent_ids
1846
 
 
1847
 
    def get_revision_tree(self, revision_id):
1848
 
        return self._transform._tree.get_revision_tree(revision_id)
 
1410
    def paths2ids(self, specific_files, trees=None, require_versioned=False):
 
1411
        """See Tree.paths2ids"""
 
1412
        return 'not_empty'
1849
1413
 
1850
1414
 
1851
1415
def joinpath(parent, child):
1883
1447
            self._known_paths[trans_id] = self._determine_path(trans_id)
1884
1448
        return self._known_paths[trans_id]
1885
1449
 
1886
 
    def get_paths(self, trans_ids):
1887
 
        return [(self.get_path(t), t) for t in trans_ids]
1888
 
 
1889
 
 
1890
1450
 
1891
1451
def topology_sorted_ids(tree):
1892
1452
    """Determine the topological order of the ids in a tree"""
1895
1455
    return file_ids
1896
1456
 
1897
1457
 
1898
 
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
1899
 
               delta_from_tree=False):
 
1458
def build_tree(tree, wt, accelerator_tree=None):
1900
1459
    """Create working tree for a branch, using a TreeTransform.
1901
1460
    
1902
1461
    This function should be used on empty trees, having a tree root at most.
1915
1474
    :param accelerator_tree: A tree which can be used for retrieving file
1916
1475
        contents more quickly than tree itself, i.e. a workingtree.  tree
1917
1476
        will be used for cases where accelerator_tree's content is different.
1918
 
    :param hardlink: If true, hard-link files to accelerator_tree, where
1919
 
        possible.  accelerator_tree must implement abspath, i.e. be a
1920
 
        working tree.
1921
 
    :param delta_from_tree: If true, build_tree may use the input Tree to
1922
 
        generate the inventory delta.
1923
1477
    """
1924
1478
    wt.lock_tree_write()
1925
1479
    try:
1928
1482
            if accelerator_tree is not None:
1929
1483
                accelerator_tree.lock_read()
1930
1484
            try:
1931
 
                return _build_tree(tree, wt, accelerator_tree, hardlink,
1932
 
                                   delta_from_tree)
 
1485
                return _build_tree(tree, wt, accelerator_tree)
1933
1486
            finally:
1934
1487
                if accelerator_tree is not None:
1935
1488
                    accelerator_tree.unlock()
1939
1492
        wt.unlock()
1940
1493
 
1941
1494
 
1942
 
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
 
1495
def _build_tree(tree, wt, accelerator_tree):
1943
1496
    """See build_tree."""
1944
 
    for num, _unused in enumerate(wt.all_file_ids()):
1945
 
        if num > 0:  # more than just a root
1946
 
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1947
 
    existing_files = set()
1948
 
    for dir, files in wt.walkdirs():
1949
 
        existing_files.update(f[0] for f in files)
 
1497
    if len(wt.inventory) > 1:  # more than just a root
 
1498
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1950
1499
    file_trans_id = {}
1951
1500
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1952
1501
    pp = ProgressPhase("Build phase", 2, top_pb)
1970
1519
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1971
1520
        try:
1972
1521
            deferred_contents = []
1973
 
            num = 0
1974
 
            total = len(tree.inventory)
1975
 
            if delta_from_tree:
1976
 
                precomputed_delta = []
1977
 
            else:
1978
 
                precomputed_delta = None
1979
1522
            for num, (tree_path, entry) in \
1980
1523
                enumerate(tree.inventory.iter_entries_by_dir()):
1981
 
                pb.update("Building tree", num - len(deferred_contents), total)
 
1524
                pb.update("Building tree", num - len(deferred_contents),
 
1525
                          len(tree.inventory))
1982
1526
                if entry.parent_id is None:
1983
1527
                    continue
1984
1528
                reparent = False
1985
1529
                file_id = entry.file_id
1986
 
                if delta_from_tree:
1987
 
                    precomputed_delta.append((None, tree_path, file_id, entry))
1988
 
                if tree_path in existing_files:
1989
 
                    target_path = wt.abspath(tree_path)
 
1530
                target_path = wt.abspath(tree_path)
 
1531
                try:
1990
1532
                    kind = file_kind(target_path)
 
1533
                except NoSuchFile:
 
1534
                    pass
 
1535
                else:
1991
1536
                    if kind == "directory":
1992
1537
                        try:
1993
1538
                            bzrdir.BzrDir.open(target_path)
2001
1546
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2002
1547
                        if kind == 'directory':
2003
1548
                            reparent = True
 
1549
                if entry.parent_id not in file_trans_id:
 
1550
                    raise AssertionError(
 
1551
                        'entry %s parent id %r is not in file_trans_id %r'
 
1552
                        % (entry, entry.parent_id, file_trans_id))
2004
1553
                parent_id = file_trans_id[entry.parent_id]
2005
1554
                if entry.kind == 'file':
2006
1555
                    # We *almost* replicate new_by_entry, so that we can defer
2007
1556
                    # getting the file text, and get them all at once.
2008
1557
                    trans_id = tt.create_path(entry.name, parent_id)
2009
1558
                    file_trans_id[file_id] = trans_id
2010
 
                    tt.version_file(file_id, trans_id)
2011
 
                    executable = tree.is_executable(file_id, tree_path)
2012
 
                    if executable:
 
1559
                    tt.version_file(entry.file_id, trans_id)
 
1560
                    executable = tree.is_executable(entry.file_id, tree_path)
 
1561
                    if executable is not None:
2013
1562
                        tt.set_executability(executable, trans_id)
2014
 
                    deferred_contents.append((file_id, trans_id))
 
1563
                    deferred_contents.append((entry.file_id, trans_id))
2015
1564
                else:
2016
1565
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2017
1566
                                                          tree)
2019
1568
                    new_trans_id = file_trans_id[file_id]
2020
1569
                    old_parent = tt.trans_id_tree_path(tree_path)
2021
1570
                    _reparent_children(tt, old_parent, new_trans_id)
2022
 
            offset = num + 1 - len(deferred_contents)
2023
 
            _create_files(tt, tree, deferred_contents, pb, offset,
2024
 
                          accelerator_tree, hardlink)
 
1571
            for num, (trans_id, bytes) in enumerate(
 
1572
                _iter_files_bytes_accelerated(tree, accelerator_tree,
 
1573
                                              deferred_contents)):
 
1574
                tt.create_file(bytes, trans_id)
 
1575
                pb.update('Adding file contents',
 
1576
                          (num + len(tree.inventory) - len(deferred_contents)),
 
1577
                          len(tree.inventory))
2025
1578
        finally:
2026
1579
            pb.finished()
2027
1580
        pp.next_phase()
2028
1581
        divert_trans = set(file_trans_id[f] for f in divert)
2029
1582
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2030
1583
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2031
 
        if len(raw_conflicts) > 0:
2032
 
            precomputed_delta = None
2033
1584
        conflicts = cook_conflicts(raw_conflicts, tt)
2034
1585
        for conflict in conflicts:
2035
1586
            warning(conflict)
2037
1588
            wt.add_conflicts(conflicts)
2038
1589
        except errors.UnsupportedOperation:
2039
1590
            pass
2040
 
        result = tt.apply(no_conflicts=True,
2041
 
                          precomputed_delta=precomputed_delta)
 
1591
        result = tt.apply(no_conflicts=True)
2042
1592
    finally:
2043
1593
        tt.finalize()
2044
1594
        top_pb.finished()
2045
1595
    return result
2046
1596
 
2047
1597
 
2048
 
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2049
 
                  hardlink):
2050
 
    total = len(desired_files) + offset
 
1598
def _iter_files_bytes_accelerated(tree, accelerator_tree, desired_files):
2051
1599
    if accelerator_tree is None:
2052
1600
        new_desired_files = desired_files
2053
1601
    else:
2054
 
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
 
1602
        iter = accelerator_tree._iter_changes(tree, include_unchanged=True)
2055
1603
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2056
 
                         in iter if not (c or e[0] != e[1]))
 
1604
                         in iter if not c)
2057
1605
        new_desired_files = []
2058
 
        count = 0
2059
 
        for file_id, trans_id in desired_files:
 
1606
        for file_id, identifier in desired_files:
2060
1607
            accelerator_path = unchanged.get(file_id)
2061
1608
            if accelerator_path is None:
2062
 
                new_desired_files.append((file_id, trans_id))
 
1609
                new_desired_files.append((file_id, identifier))
2063
1610
                continue
2064
 
            pb.update('Adding file contents', count + offset, total)
2065
 
            if hardlink:
2066
 
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2067
 
                                   trans_id)
2068
 
            else:
2069
 
                contents = accelerator_tree.get_file(file_id, accelerator_path)
2070
 
                try:
2071
 
                    tt.create_file(contents, trans_id)
2072
 
                finally:
2073
 
                    contents.close()
2074
 
            count += 1
2075
 
        offset += count
2076
 
    for count, (trans_id, contents) in enumerate(tree.iter_files_bytes(
2077
 
                                                 new_desired_files)):
2078
 
        tt.create_file(contents, trans_id)
2079
 
        pb.update('Adding file contents', count + offset, total)
 
1611
            contents = accelerator_tree.get_file(file_id, accelerator_path)
 
1612
            try:
 
1613
                want_new = False
 
1614
                contents_bytes = (contents.read(),)
 
1615
            finally:
 
1616
                contents.close()
 
1617
            yield identifier, contents_bytes
 
1618
    for result in tree.iter_files_bytes(new_desired_files):
 
1619
        yield result
2080
1620
 
2081
1621
 
2082
1622
def _reparent_children(tt, old_parent, new_parent):
2087
1627
    by_parent = tt.by_parent()
2088
1628
    for child in by_parent[old_parent]:
2089
1629
        tt.adjust_path(tt.final_name(child), new_parent, child)
2090
 
    return by_parent[old_parent]
2091
1630
 
2092
1631
def _content_match(tree, entry, file_id, kind, target_path):
2093
1632
    if entry.kind != kind:
2107
1646
    new_conflicts = set()
2108
1647
    for c_type, conflict in ((c[0], c) for c in conflicts):
2109
1648
        # Anything but a 'duplicate' would indicate programmer error
2110
 
        if c_type != 'duplicate':
2111
 
            raise AssertionError(c_type)
 
1649
        assert c_type == 'duplicate', c_type
2112
1650
        # Now figure out which is new and which is old
2113
1651
        if tt.new_contents(conflict[1]):
2114
1652
            new_file = conflict[1]
2154
1692
        raise errors.BadFileKindError(name, kind)
2155
1693
 
2156
1694
 
2157
 
@deprecated_function(deprecated_in((1, 9, 0)))
2158
1695
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2159
 
    """Create new file contents according to an inventory entry.
2160
 
 
2161
 
    DEPRECATED.  Use create_from_tree instead.
2162
 
    """
 
1696
    """Create new file contents according to an inventory entry."""
2163
1697
    if entry.kind == "file":
2164
1698
        if lines is None:
2165
1699
            lines = tree.get_file(entry.file_id).readlines()
2170
1704
        tt.create_directory(trans_id)
2171
1705
 
2172
1706
 
2173
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2174
 
    """Create new file contents according to tree contents."""
2175
 
    kind = tree.kind(file_id)
2176
 
    if kind == 'directory':
2177
 
        tt.create_directory(trans_id)
2178
 
    elif kind == "file":
2179
 
        if bytes is None:
2180
 
            tree_file = tree.get_file(file_id)
2181
 
            try:
2182
 
                bytes = tree_file.readlines()
2183
 
            finally:
2184
 
                tree_file.close()
2185
 
        tt.create_file(bytes, trans_id)
2186
 
    elif kind == "symlink":
2187
 
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2188
 
    else:
2189
 
        raise AssertionError('Unknown kind %r' % kind)
2190
 
 
2191
 
 
2192
1707
def create_entry_executability(tt, entry, trans_id):
2193
1708
    """Set the executability of a trans_id according to an inventory entry"""
2194
1709
    if entry.kind == "file":
2195
1710
        tt.set_executability(entry.executable, trans_id)
2196
1711
 
2197
1712
 
 
1713
@deprecated_function(zero_fifteen)
 
1714
def find_interesting(working_tree, target_tree, filenames):
 
1715
    """Find the ids corresponding to specified filenames.
 
1716
    
 
1717
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
 
1718
    """
 
1719
    working_tree.lock_read()
 
1720
    try:
 
1721
        target_tree.lock_read()
 
1722
        try:
 
1723
            return working_tree.paths2ids(filenames, [target_tree])
 
1724
        finally:
 
1725
            target_tree.unlock()
 
1726
    finally:
 
1727
        working_tree.unlock()
 
1728
 
 
1729
 
 
1730
@deprecated_function(zero_ninety)
 
1731
def change_entry(tt, file_id, working_tree, target_tree, 
 
1732
                 trans_id_file_id, backups, trans_id, by_parent):
 
1733
    """Replace a file_id's contents with those from a target tree."""
 
1734
    if file_id is None and target_tree is None:
 
1735
        # skip the logic altogether in the deprecation test
 
1736
        return
 
1737
    e_trans_id = trans_id_file_id(file_id)
 
1738
    entry = target_tree.inventory[file_id]
 
1739
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
 
1740
                                                           working_tree)
 
1741
    if contents_mod:
 
1742
        mode_id = e_trans_id
 
1743
        if has_contents:
 
1744
            if not backups:
 
1745
                tt.delete_contents(e_trans_id)
 
1746
            else:
 
1747
                parent_trans_id = trans_id_file_id(entry.parent_id)
 
1748
                backup_name = get_backup_name(entry, by_parent,
 
1749
                                              parent_trans_id, tt)
 
1750
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
 
1751
                tt.unversion_file(e_trans_id)
 
1752
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
 
1753
                tt.version_file(file_id, e_trans_id)
 
1754
                trans_id[file_id] = e_trans_id
 
1755
        create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
 
1756
        create_entry_executability(tt, entry, e_trans_id)
 
1757
 
 
1758
    elif meta_mod:
 
1759
        tt.set_executability(entry.executable, e_trans_id)
 
1760
    if tt.final_name(e_trans_id) != entry.name:
 
1761
        adjust_path  = True
 
1762
    else:
 
1763
        parent_id = tt.final_parent(e_trans_id)
 
1764
        parent_file_id = tt.final_file_id(parent_id)
 
1765
        if parent_file_id != entry.parent_id:
 
1766
            adjust_path = True
 
1767
        else:
 
1768
            adjust_path = False
 
1769
    if adjust_path:
 
1770
        parent_trans_id = trans_id_file_id(entry.parent_id)
 
1771
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
 
1772
 
 
1773
 
2198
1774
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2199
1775
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2200
1776
 
2245
1821
    tt = TreeTransform(working_tree, pb)
2246
1822
    try:
2247
1823
        pp = ProgressPhase("Revert phase", 3, pb)
2248
 
        conflicts, merge_modified = _prepare_revert_transform(
2249
 
            working_tree, target_tree, tt, filenames, backups, pp)
 
1824
        pp.next_phase()
 
1825
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1826
        try:
 
1827
            merge_modified = _alter_files(working_tree, target_tree, tt,
 
1828
                                          child_pb, filenames, backups)
 
1829
        finally:
 
1830
            child_pb.finished()
 
1831
        pp.next_phase()
 
1832
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1833
        try:
 
1834
            raw_conflicts = resolve_conflicts(tt, child_pb,
 
1835
                lambda t, c: conflict_pass(t, c, target_tree))
 
1836
        finally:
 
1837
            child_pb.finished()
 
1838
        conflicts = cook_conflicts(raw_conflicts, tt)
2250
1839
        if change_reporter:
2251
1840
            change_reporter = delta._ChangeReporter(
2252
1841
                unversioned_filter=working_tree.is_ignored)
2253
 
            delta.report_changes(tt.iter_changes(), change_reporter)
 
1842
            delta.report_changes(tt._iter_changes(), change_reporter)
2254
1843
        for conflict in conflicts:
2255
1844
            warning(conflict)
2256
1845
        pp.next_phase()
2263
1852
    return conflicts
2264
1853
 
2265
1854
 
2266
 
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2267
 
                              backups, pp, basis_tree=None,
2268
 
                              merge_modified=None):
2269
 
    pp.next_phase()
2270
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2271
 
    try:
2272
 
        if merge_modified is None:
2273
 
            merge_modified = working_tree.merge_modified()
2274
 
        merge_modified = _alter_files(working_tree, target_tree, tt,
2275
 
                                      child_pb, filenames, backups,
2276
 
                                      merge_modified, basis_tree)
2277
 
    finally:
2278
 
        child_pb.finished()
2279
 
    pp.next_phase()
2280
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2281
 
    try:
2282
 
        raw_conflicts = resolve_conflicts(tt, child_pb,
2283
 
            lambda t, c: conflict_pass(t, c, target_tree))
2284
 
    finally:
2285
 
        child_pb.finished()
2286
 
    conflicts = cook_conflicts(raw_conflicts, tt)
2287
 
    return conflicts, merge_modified
2288
 
 
2289
 
 
2290
1855
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2291
 
                 backups, merge_modified, basis_tree=None):
2292
 
    if basis_tree is not None:
2293
 
        basis_tree.lock_read()
2294
 
    change_list = target_tree.iter_changes(working_tree,
 
1856
                 backups):
 
1857
    merge_modified = working_tree.merge_modified()
 
1858
    change_list = target_tree._iter_changes(working_tree,
2295
1859
        specific_files=specific_files, pb=pb)
2296
 
    if target_tree.get_root_id() is None:
 
1860
    if target_tree.inventory.root is None:
2297
1861
        skip_root = True
2298
1862
    else:
2299
1863
        skip_root = False
 
1864
    basis_tree = None
2300
1865
    try:
2301
1866
        deferred_files = []
2302
1867
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2338
1903
                        # contents
2339
1904
                        mode_id = trans_id
2340
1905
                        trans_id = new_trans_id
2341
 
                if kind[1] in ('directory', 'tree-reference'):
 
1906
                if kind[1] == 'directory':
2342
1907
                    tt.create_directory(trans_id)
2343
 
                    if kind[1] == 'tree-reference':
2344
 
                        revision = target_tree.get_reference_revision(file_id,
2345
 
                                                                      path[1])
2346
 
                        tt.set_tree_reference(revision, trans_id)
2347
1908
                elif kind[1] == 'symlink':
2348
1909
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
2349
1910
                                      trans_id)
2363
1924
                    # preserve the execute bit when backing up
2364
1925
                    if keep_content and executable[0] == executable[1]:
2365
1926
                        tt.set_executability(executable[1], trans_id)
2366
 
                elif kind[1] is not None:
2367
 
                    raise AssertionError(kind[1])
 
1927
                else:
 
1928
                    assert kind[1] is None
2368
1929
            if versioned == (False, True):
2369
1930
                tt.version_file(file_id, trans_id)
2370
1931
            if versioned == (True, False):
2371
1932
                tt.unversion_file(trans_id)
2372
 
            if (name[1] is not None and
 
1933
            if (name[1] is not None and 
2373
1934
                (name[0] != name[1] or parent[0] != parent[1])):
2374
 
                if name[1] == '' and parent[1] is None:
2375
 
                    parent_trans = ROOT_PARENT
2376
 
                else:
2377
 
                    parent_trans = tt.trans_id_file_id(parent[1])
2378
 
                tt.adjust_path(name[1], parent_trans, trans_id)
 
1935
                tt.adjust_path(
 
1936
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
2379
1937
            if executable[0] != executable[1] and kind[1] == "file":
2380
1938
                tt.set_executability(executable[1], trans_id)
2381
1939
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2444
2002
                new_conflicts.add(('deleting parent', 'Not deleting', 
2445
2003
                                   trans_id))
2446
2004
            except KeyError:
2447
 
                create = True
 
2005
                tt.create_directory(trans_id)
 
2006
                new_conflicts.add((c_type, 'Created directory', trans_id))
2448
2007
                try:
2449
2008
                    tt.final_name(trans_id)
2450
2009
                except NoFinalPath:
2451
2010
                    if path_tree is not None:
2452
2011
                        file_id = tt.final_file_id(trans_id)
2453
 
                        if file_id is None:
2454
 
                            file_id = tt.inactive_file_id(trans_id)
2455
2012
                        entry = path_tree.inventory[file_id]
2456
 
                        # special-case the other tree root (move its
2457
 
                        # children to current root)
2458
 
                        if entry.parent_id is None:
2459
 
                            create=False
2460
 
                            moved = _reparent_transform_children(
2461
 
                                tt, trans_id, tt.root)
2462
 
                            for child in moved:
2463
 
                                new_conflicts.add((c_type, 'Moved to root',
2464
 
                                                   child))
2465
 
                        else:
2466
 
                            parent_trans_id = tt.trans_id_file_id(
2467
 
                                entry.parent_id)
2468
 
                            tt.adjust_path(entry.name, parent_trans_id,
2469
 
                                           trans_id)
2470
 
                if create:
2471
 
                    tt.create_directory(trans_id)
2472
 
                    new_conflicts.add((c_type, 'Created directory', trans_id))
 
2013
                        parent_trans_id = tt.trans_id_file_id(entry.parent_id)
 
2014
                        tt.adjust_path(entry.name, parent_trans_id, trans_id)
2473
2015
        elif c_type == 'unversioned parent':
2474
 
            file_id = tt.inactive_file_id(conflict[1])
2475
 
            # special-case the other tree root (move its children instead)
2476
 
            if path_tree and file_id in path_tree:
2477
 
                if path_tree.inventory[file_id].parent_id is None:
2478
 
                    continue
2479
 
            tt.version_file(file_id, conflict[1])
 
2016
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
2480
2017
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
2481
2018
        elif c_type == 'non-directory parent':
2482
2019
            parent_id = conflict[1]
2486
2023
            new_parent_id = tt.new_directory(parent_name + '.new',
2487
2024
                parent_parent, parent_file_id)
2488
2025
            _reparent_transform_children(tt, parent_id, new_parent_id)
2489
 
            if parent_file_id is not None:
2490
 
                tt.unversion_file(parent_id)
 
2026
            tt.unversion_file(parent_id)
2491
2027
            new_conflicts.add((c_type, 'Created directory', new_parent_id))
2492
 
        elif c_type == 'versioning no contents':
2493
 
            tt.cancel_versioning(conflict[1])
2494
2028
    return new_conflicts
2495
2029
 
2496
2030