~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Ian Clatworthy
  • Date: 2008-03-27 07:51:10 UTC
  • mto: (3311.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3312.
  • Revision ID: ian.clatworthy@canonical.com-20080327075110-afgd7x03ybju06ez
Reduce evangelism in the User Guide

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
 
    multiparent,
30
 
    osutils,
31
 
    revision as _mod_revision,
 
28
    inventory
32
29
    )
33
 
from bzrlib.util import bencode
34
30
""")
35
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
32
                           ReusingTransform, NotVersionedError, CantMoveRoot,
37
33
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
34
                           UnableCreateSymlink)
39
35
from bzrlib.inventory import InventoryEntry
40
 
from bzrlib.osutils import (
41
 
    delete_any,
42
 
    file_kind,
43
 
    has_symlinks,
44
 
    lexists,
45
 
    pathjoin,
46
 
    sha_file,
47
 
    splitpath,
48
 
    supports_executable,
49
 
)
 
36
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
 
37
                            delete_any, has_symlinks)
50
38
from bzrlib.progress import DummyProgress, ProgressPhase
51
39
from bzrlib.symbol_versioning import (
52
40
        deprecated_function,
53
 
        deprecated_in,
 
41
        zero_fifteen,
 
42
        zero_ninety,
54
43
        )
55
44
from bzrlib.trace import mutter, warning
56
45
from bzrlib import tree
132
121
        # Cache of relpath results, to speed up canonical_path
133
122
        self._relpaths = {}
134
123
        # The trans_id that will be used as the tree root
135
 
        root_id = tree.get_root_id()
136
 
        if root_id is not None:
137
 
            self._new_root = self.trans_id_tree_file_id(root_id)
138
 
        else:
139
 
            self._new_root = None
 
124
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
140
125
        # Indictor of whether the transform has been applied
141
126
        self._done = False
142
127
        # A progress bar
203
188
        previous_name = self._new_name.get(trans_id)
204
189
        self._new_name[trans_id] = name
205
190
        self._new_parent[trans_id] = parent
206
 
        if parent == ROOT_PARENT:
207
 
            if self._new_root is not None:
208
 
                raise ValueError("Cannot have multiple roots.")
209
 
            self._new_root = trans_id
210
191
        if (trans_id in self._limbo_files and
211
192
            trans_id not in self._needs_rename):
212
193
            self._rename_in_limbo([trans_id])
269
250
        This reflects only files that already exist, not ones that will be
270
251
        added by transactions.
271
252
        """
272
 
        if inventory_id is None:
273
 
            raise ValueError('None is not a valid file id')
274
253
        path = self._tree.id2path(inventory_id)
275
254
        return self.trans_id_tree_path(path)
276
255
 
280
259
        a transaction has been unversioned, it is deliberately still returned.
281
260
        (this will likely lead to an unversioned parent conflict.)
282
261
        """
283
 
        if file_id is None:
284
 
            raise ValueError('None is not a valid file id')
285
262
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
286
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]
287
268
        else:
288
 
            try:
289
 
                self._tree.iter_entries_by_dir([file_id]).next()
290
 
            except StopIteration:
291
 
                if file_id in self._non_present_ids:
292
 
                    return self._non_present_ids[file_id]
293
 
                else:
294
 
                    trans_id = self._assign_id()
295
 
                    self._non_present_ids[file_id] = trans_id
296
 
                    return trans_id
297
 
            else:
298
 
                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
299
272
 
300
273
    def canonical_path(self, path):
301
274
        """Get the canonical tree-relative path"""
374
347
        try:
375
348
            mode = os.stat(self._tree.abspath(old_path)).st_mode
376
349
        except OSError, e:
377
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
378
 
                # Either old_path doesn't exist, or the parent of the
379
 
                # target is not a directory (but will be one eventually)
380
 
                # Either way, we know it doesn't exist *right now*
381
 
                # See also bug #248448
 
350
            if e.errno == errno.ENOENT:
382
351
                return
383
352
            else:
384
353
                raise
471
440
 
472
441
    def version_file(self, file_id, trans_id):
473
442
        """Schedule a file to become versioned."""
474
 
        if file_id is None:
475
 
            raise ValueError()
 
443
        assert file_id is not None
476
444
        unique_add(self._new_id, trans_id, file_id)
477
445
        unique_add(self._r_new_id, file_id, trans_id)
478
446
 
482
450
        del self._new_id[trans_id]
483
451
        del self._r_new_id[file_id]
484
452
 
485
 
    def new_paths(self, filesystem_only=False):
486
 
        """Determine the paths of all new and changed files.
487
 
 
488
 
        :param filesystem_only: if True, only calculate values for files
489
 
            that require renames or execute bit changes.
490
 
        """
491
 
        new_ids = set()
492
 
        if filesystem_only:
493
 
            stale_ids = self._needs_rename.difference(self._new_name)
494
 
            stale_ids.difference_update(self._new_parent)
495
 
            stale_ids.difference_update(self._new_contents)
496
 
            stale_ids.difference_update(self._new_id)
497
 
            needs_rename = self._needs_rename.difference(stale_ids)
498
 
            id_sets = (needs_rename, self._new_executability)
499
 
        else:
500
 
            id_sets = (self._new_name, self._new_parent, self._new_contents,
501
 
                       self._new_id, self._new_executability)
502
 
        for id_set in id_sets:
503
 
            new_ids.update(id_set)
504
 
        return sorted(FinalPaths(self).get_paths(new_ids))
505
 
 
506
 
    def _inventory_altered(self):
507
 
        """Get the trans_ids and paths of files needing new inv entries."""
508
 
        new_ids = set()
509
 
        for id_set in [self._new_name, self._new_parent, self._new_id,
510
 
                       self._new_executability]:
511
 
            new_ids.update(id_set)
512
 
        changed_kind = set(self._removed_contents)
513
 
        changed_kind.intersection_update(self._new_contents)
514
 
        changed_kind.difference_update(new_ids)
515
 
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
516
 
                        self.final_kind(t))
517
 
        new_ids.update(changed_kind)
518
 
        return sorted(FinalPaths(self).get_paths(new_ids))
 
453
    def new_paths(self):
 
454
        """Determine the paths of all new and changed files"""
 
455
        new_ids = set()
 
456
        fp = FinalPaths(self)
 
457
        for id_set in (self._new_name, self._new_parent, self._new_contents,
 
458
                       self._new_id, self._new_executability):
 
459
            new_ids.update(id_set)
 
460
        new_paths = [(fp.get_path(t), t) for t in new_ids]
 
461
        new_paths.sort()
 
462
        return new_paths
519
463
 
520
464
    def tree_kind(self, trans_id):
521
465
        """Determine the file kind in the working tree.
557
501
        # the file is old; the old id is still valid
558
502
        if self._new_root == trans_id:
559
503
            return self._tree.get_root_id()
560
 
        return self._tree.path2id(path)
 
504
        return self._tree.inventory.path2id(path)
561
505
 
562
506
    def final_file_id(self, trans_id):
563
507
        """Determine the file id after any changes are applied, or None.
566
510
        applied.
567
511
        """
568
512
        try:
 
513
            # there is a new id for this file
 
514
            assert self._new_id[trans_id] is not None
569
515
            return self._new_id[trans_id]
570
516
        except KeyError:
571
517
            if trans_id in self._removed_id:
675
621
        try:
676
622
            children = os.listdir(self._tree.abspath(path))
677
623
        except OSError, e:
678
 
            if not (osutils._is_error_enotdir(e)
679
 
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
 
624
            if e.errno not in (errno.ENOENT, errno.ESRCH, errno.ENOTDIR):
680
625
                raise
681
626
            return
682
 
 
 
627
            
683
628
        for child in children:
684
629
            childpath = joinpath(path, child)
685
630
            if self._tree.is_control_filename(childpath):
910
855
        self._limbo_files[trans_id] = limbo_name
911
856
        return limbo_name
912
857
 
913
 
    def _set_executability(self, path, trans_id):
 
858
    def _set_executability(self, path, entry, trans_id):
914
859
        """Set the executability of versioned files """
 
860
        new_executability = self._new_executability[trans_id]
 
861
        entry.executable = new_executability
915
862
        if supports_executable():
916
 
            new_executability = self._new_executability[trans_id]
917
863
            abspath = self._tree.abspath(path)
918
864
            current_mode = os.stat(abspath).st_mode
919
865
            if new_executability:
1014
960
        from_path = self._tree_id_paths.get(from_trans_id)
1015
961
        if from_versioned:
1016
962
            # get data from working tree if versioned
1017
 
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
 
963
            from_entry = self._tree.inventory[file_id]
1018
964
            from_name = from_entry.name
1019
965
            from_parent = from_entry.parent_id
1020
966
        else:
1126
1072
        """
1127
1073
        return _PreviewTree(self)
1128
1074
 
1129
 
    def _text_parent(self, trans_id):
1130
 
        file_id = self.tree_file_id(trans_id)
1131
 
        try:
1132
 
            if file_id is None or self._tree.kind(file_id) != 'file':
1133
 
                return None
1134
 
        except errors.NoSuchFile:
1135
 
            return None
1136
 
        return file_id
1137
 
 
1138
 
    def _get_parents_texts(self, trans_id):
1139
 
        """Get texts for compression parents of this file."""
1140
 
        file_id = self._text_parent(trans_id)
1141
 
        if file_id is None:
1142
 
            return ()
1143
 
        return (self._tree.get_file_text(file_id),)
1144
 
 
1145
 
    def _get_parents_lines(self, trans_id):
1146
 
        """Get lines for compression parents of this file."""
1147
 
        file_id = self._text_parent(trans_id)
1148
 
        if file_id is None:
1149
 
            return ()
1150
 
        return (self._tree.get_file_lines(file_id),)
1151
 
 
1152
 
    def serialize(self, serializer):
1153
 
        """Serialize this TreeTransform.
1154
 
 
1155
 
        :param serializer: A Serialiser like pack.ContainerSerializer.
1156
 
        """
1157
 
        new_name = dict((k, v.encode('utf-8')) for k, v in
1158
 
                        self._new_name.items())
1159
 
        new_executability = dict((k, int(v)) for k, v in
1160
 
                                 self._new_executability.items())
1161
 
        tree_path_ids = dict((k.encode('utf-8'), v)
1162
 
                             for k, v in self._tree_path_ids.items())
1163
 
        attribs = {
1164
 
            '_id_number': self._id_number,
1165
 
            '_new_name': new_name,
1166
 
            '_new_parent': self._new_parent,
1167
 
            '_new_executability': new_executability,
1168
 
            '_new_id': self._new_id,
1169
 
            '_tree_path_ids': tree_path_ids,
1170
 
            '_removed_id': list(self._removed_id),
1171
 
            '_removed_contents': list(self._removed_contents),
1172
 
            '_non_present_ids': self._non_present_ids,
1173
 
            }
1174
 
        yield serializer.bytes_record(bencode.bencode(attribs),
1175
 
                                      (('attribs',),))
1176
 
        for trans_id, kind in self._new_contents.items():
1177
 
            if kind == 'file':
1178
 
                cur_file = open(self._limbo_name(trans_id), 'rb')
1179
 
                try:
1180
 
                    lines = osutils.split_lines(cur_file.read())
1181
 
                finally:
1182
 
                    cur_file.close()
1183
 
                parents = self._get_parents_lines(trans_id)
1184
 
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1185
 
                content = ''.join(mpdiff.to_patch())
1186
 
            if kind == 'directory':
1187
 
                content = ''
1188
 
            if kind == 'symlink':
1189
 
                content = os.readlink(self._limbo_name(trans_id))
1190
 
            yield serializer.bytes_record(content, ((trans_id, kind),))
1191
 
 
1192
 
 
1193
 
    def deserialize(self, records):
1194
 
        """Deserialize a stored TreeTransform.
1195
 
 
1196
 
        :param records: An iterable of (names, content) tuples, as per
1197
 
            pack.ContainerPushParser.
1198
 
        """
1199
 
        names, content = records.next()
1200
 
        attribs = bencode.bdecode(content)
1201
 
        self._id_number = attribs['_id_number']
1202
 
        self._new_name = dict((k, v.decode('utf-8'))
1203
 
                            for k, v in attribs['_new_name'].items())
1204
 
        self._new_parent = attribs['_new_parent']
1205
 
        self._new_executability = dict((k, bool(v)) for k, v in
1206
 
            attribs['_new_executability'].items())
1207
 
        self._new_id = attribs['_new_id']
1208
 
        self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1209
 
        self._tree_path_ids = {}
1210
 
        self._tree_id_paths = {}
1211
 
        for bytepath, trans_id in attribs['_tree_path_ids'].items():
1212
 
            path = bytepath.decode('utf-8')
1213
 
            self._tree_path_ids[path] = trans_id
1214
 
            self._tree_id_paths[trans_id] = path
1215
 
        self._removed_id = set(attribs['_removed_id'])
1216
 
        self._removed_contents = set(attribs['_removed_contents'])
1217
 
        self._non_present_ids = attribs['_non_present_ids']
1218
 
        for ((trans_id, kind),), content in records:
1219
 
            if kind == 'file':
1220
 
                mpdiff = multiparent.MultiParent.from_patch(content)
1221
 
                lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1222
 
                self.create_file(lines, trans_id)
1223
 
            if kind == 'directory':
1224
 
                self.create_directory(trans_id)
1225
 
            if kind == 'symlink':
1226
 
                self.create_symlink(content.decode('utf-8'), trans_id)
1227
 
 
1228
1075
 
1229
1076
class TreeTransform(TreeTransformBase):
1230
1077
    """Represent a tree transformation.
1300
1147
        tree.lock_tree_write()
1301
1148
 
1302
1149
        try:
 
1150
            control_files = tree._control_files
1303
1151
            limbodir = urlutils.local_path_from_url(
1304
 
                tree._transport.abspath('limbo'))
 
1152
                control_files.controlfilename('limbo'))
1305
1153
            try:
1306
1154
                os.mkdir(limbodir)
1307
1155
            except OSError, e:
1308
1156
                if e.errno == errno.EEXIST:
1309
1157
                    raise ExistingLimbo(limbodir)
1310
1158
            deletiondir = urlutils.local_path_from_url(
1311
 
                tree._transport.abspath('pending-deletion'))
 
1159
                control_files.controlfilename('pending-deletion'))
1312
1160
            try:
1313
1161
                os.mkdir(deletiondir)
1314
1162
            except OSError, e:
1322
1170
                                   tree.case_sensitive)
1323
1171
        self._deletiondir = deletiondir
1324
1172
 
1325
 
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
 
1173
    def apply(self, no_conflicts=False, _mover=None):
1326
1174
        """Apply all changes to the inventory and filesystem.
1327
1175
 
1328
1176
        If filesystem or inventory conflicts are present, MalformedTransform
1332
1180
 
1333
1181
        :param no_conflicts: if True, the caller guarantees there are no
1334
1182
            conflicts, so no check is made.
1335
 
        :param precomputed_delta: An inventory delta to use instead of
1336
 
            calculating one.
1337
1183
        :param _mover: Supply an alternate FileMover, for testing
1338
1184
        """
1339
1185
        if not no_conflicts:
1340
1186
            conflicts = self.find_conflicts()
1341
1187
            if len(conflicts) != 0:
1342
1188
                raise MalformedTransform(conflicts=conflicts)
 
1189
        inventory_delta = []
1343
1190
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1344
1191
        try:
1345
 
            if precomputed_delta is None:
1346
 
                child_pb.update('Apply phase', 0, 2)
1347
 
                inventory_delta = self._generate_inventory_delta()
1348
 
                offset = 1
1349
 
            else:
1350
 
                inventory_delta = precomputed_delta
1351
 
                offset = 0
1352
1192
            if _mover is None:
1353
1193
                mover = _FileMover()
1354
1194
            else:
1355
1195
                mover = _mover
1356
1196
            try:
1357
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
1358
 
                self._apply_removals(mover)
1359
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
1360
 
                modified_paths = self._apply_insertions(mover)
 
1197
                child_pb.update('Apply phase', 0, 2)
 
1198
                self._apply_removals(inventory_delta, mover)
 
1199
                child_pb.update('Apply phase', 1, 2)
 
1200
                modified_paths = self._apply_insertions(inventory_delta, mover)
1361
1201
            except:
1362
1202
                mover.rollback()
1363
1203
                raise
1370
1210
        self.finalize()
1371
1211
        return _TransformResults(modified_paths, self.rename_count)
1372
1212
 
1373
 
    def _generate_inventory_delta(self):
1374
 
        """Generate an inventory delta for the current transform."""
1375
 
        inventory_delta = []
1376
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1377
 
        new_paths = self._inventory_altered()
1378
 
        total_entries = len(new_paths) + len(self._removed_id)
1379
 
        try:
1380
 
            for num, trans_id in enumerate(self._removed_id):
1381
 
                if (num % 10) == 0:
1382
 
                    child_pb.update('removing file', num, total_entries)
1383
 
                if trans_id == self._new_root:
1384
 
                    file_id = self._tree.get_root_id()
1385
 
                else:
1386
 
                    file_id = self.tree_file_id(trans_id)
1387
 
                # File-id isn't really being deleted, just moved
1388
 
                if file_id in self._r_new_id:
1389
 
                    continue
1390
 
                path = self._tree_id_paths[trans_id]
1391
 
                inventory_delta.append((path, None, file_id, None))
1392
 
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1393
 
                                     new_paths)
1394
 
            entries = self._tree.iter_entries_by_dir(
1395
 
                new_path_file_ids.values())
1396
 
            old_paths = dict((e.file_id, p) for p, e in entries)
1397
 
            final_kinds = {}
1398
 
            for num, (path, trans_id) in enumerate(new_paths):
1399
 
                if (num % 10) == 0:
1400
 
                    child_pb.update('adding file',
1401
 
                                    num + len(self._removed_id), total_entries)
1402
 
                file_id = new_path_file_ids[trans_id]
1403
 
                if file_id is None:
1404
 
                    continue
1405
 
                needs_entry = False
1406
 
                try:
1407
 
                    kind = self.final_kind(trans_id)
1408
 
                except NoSuchFile:
1409
 
                    kind = self._tree.stored_kind(file_id)
1410
 
                parent_trans_id = self.final_parent(trans_id)
1411
 
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1412
 
                if parent_file_id is None:
1413
 
                    parent_file_id = self.final_file_id(parent_trans_id)
1414
 
                if trans_id in self._new_reference_revision:
1415
 
                    new_entry = inventory.TreeReference(
1416
 
                        file_id,
1417
 
                        self._new_name[trans_id],
1418
 
                        self.final_file_id(self._new_parent[trans_id]),
1419
 
                        None, self._new_reference_revision[trans_id])
1420
 
                else:
1421
 
                    new_entry = inventory.make_entry(kind,
1422
 
                        self.final_name(trans_id),
1423
 
                        parent_file_id, file_id)
1424
 
                old_path = old_paths.get(new_entry.file_id)
1425
 
                new_executability = self._new_executability.get(trans_id)
1426
 
                if new_executability is not None:
1427
 
                    new_entry.executable = new_executability
1428
 
                inventory_delta.append(
1429
 
                    (old_path, path, new_entry.file_id, new_entry))
1430
 
        finally:
1431
 
            child_pb.finished()
1432
 
        return inventory_delta
1433
 
 
1434
 
    def _apply_removals(self, mover):
 
1213
    def _apply_removals(self, inventory_delta, mover):
1435
1214
        """Perform tree operations that remove directory/inventory names.
1436
1215
 
1437
1216
        That is, delete files that are to be deleted, and put any files that
1438
1217
        need renaming into limbo.  This must be done in strict child-to-parent
1439
1218
        order.
1440
 
 
1441
 
        If inventory_delta is None, no inventory delta generation is performed.
1442
1219
        """
1443
1220
        tree_paths = list(self._tree_path_ids.iteritems())
1444
1221
        tree_paths.sort(reverse=True)
1460
1237
                            raise
1461
1238
                    else:
1462
1239
                        self.rename_count += 1
 
1240
                if trans_id in self._removed_id:
 
1241
                    if trans_id == self._new_root:
 
1242
                        file_id = self._tree.get_root_id()
 
1243
                    else:
 
1244
                        file_id = self.tree_file_id(trans_id)
 
1245
                    assert file_id is not None
 
1246
                    # File-id isn't really being deleted, just moved
 
1247
                    if file_id in self._r_new_id:
 
1248
                        continue
 
1249
                    inventory_delta.append((path, None, file_id, None))
1463
1250
        finally:
1464
1251
            child_pb.finished()
1465
1252
 
1466
 
    def _apply_insertions(self, mover):
 
1253
    def _apply_insertions(self, inventory_delta, mover):
1467
1254
        """Perform tree operations that insert directory/inventory names.
1468
1255
 
1469
1256
        That is, create any files that need to be created, and restore from
1470
1257
        limbo any files that needed renaming.  This must be done in strict
1471
1258
        parent-to-child order.
1472
 
 
1473
 
        If inventory_delta is None, no inventory delta is calculated, and
1474
 
        no list of modified paths is returned.
1475
1259
        """
1476
 
        new_paths = self.new_paths(filesystem_only=True)
 
1260
        new_paths = self.new_paths()
1477
1261
        modified_paths = []
1478
 
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1479
 
                                 new_paths)
1480
1262
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1263
        completed_new = []
1481
1264
        try:
1482
1265
            for num, (path, trans_id) in enumerate(new_paths):
1483
 
                if (num % 10) == 0:
1484
 
                    child_pb.update('adding file', num, len(new_paths))
1485
 
                full_path = self._tree.abspath(path)
1486
 
                if trans_id in self._needs_rename:
1487
 
                    try:
1488
 
                        mover.rename(self._limbo_name(trans_id), full_path)
1489
 
                    except OSError, e:
1490
 
                        # We may be renaming a dangling inventory id
1491
 
                        if e.errno != errno.ENOENT:
1492
 
                            raise
1493
 
                    else:
1494
 
                        self.rename_count += 1
1495
 
                if (trans_id in self._new_contents or
1496
 
                    self.path_changed(trans_id)):
 
1266
                new_entry = None
 
1267
                child_pb.update('adding file', num, len(new_paths))
 
1268
                if trans_id in self._new_contents or \
 
1269
                    self.path_changed(trans_id):
 
1270
                    full_path = self._tree.abspath(path)
 
1271
                    if trans_id in self._needs_rename:
 
1272
                        try:
 
1273
                            mover.rename(self._limbo_name(trans_id), full_path)
 
1274
                        except OSError, e:
 
1275
                            # We may be renaming a dangling inventory id
 
1276
                            if e.errno != errno.ENOENT:
 
1277
                                raise
 
1278
                        else:
 
1279
                            self.rename_count += 1
1497
1280
                    if trans_id in self._new_contents:
1498
1281
                        modified_paths.append(full_path)
 
1282
                        completed_new.append(trans_id)
 
1283
                file_id = self.final_file_id(trans_id)
 
1284
                if file_id is not None and (trans_id in self._new_id or
 
1285
                    trans_id in self._new_name or trans_id in self._new_parent
 
1286
                    or trans_id in self._new_executability):
 
1287
                    try:
 
1288
                        kind = self.final_kind(trans_id)
 
1289
                    except NoSuchFile:
 
1290
                        kind = self._tree.stored_kind(file_id)
 
1291
                    if trans_id in self._new_reference_revision:
 
1292
                        new_entry = inventory.TreeReference(
 
1293
                            self.final_file_id(trans_id),
 
1294
                            self._new_name[trans_id],
 
1295
                            self.final_file_id(self._new_parent[trans_id]),
 
1296
                            None, self._new_reference_revision[trans_id])
 
1297
                    else:
 
1298
                        new_entry = inventory.make_entry(kind,
 
1299
                            self.final_name(trans_id),
 
1300
                            self.final_file_id(self.final_parent(trans_id)),
 
1301
                            self.final_file_id(trans_id))
 
1302
                    try:
 
1303
                        old_path = self._tree.id2path(new_entry.file_id)
 
1304
                    except errors.NoSuchId:
 
1305
                        old_path = None
 
1306
                    inventory_delta.append((old_path, path, new_entry.file_id,
 
1307
                                            new_entry))
 
1308
 
1499
1309
                if trans_id in self._new_executability:
1500
 
                    self._set_executability(path, trans_id)
 
1310
                    self._set_executability(path, new_entry, trans_id)
1501
1311
        finally:
1502
1312
            child_pb.finished()
1503
 
        self._new_contents.clear()
 
1313
        for trans_id in completed_new:
 
1314
            del self._new_contents[trans_id]
1504
1315
        return modified_paths
1505
1316
 
1506
1317
 
1514
1325
 
1515
1326
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1516
1327
        tree.lock_read()
1517
 
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
 
1328
        limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1518
1329
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1519
1330
 
1520
1331
    def canonical_path(self, path):
1543
1354
        except KeyError:
1544
1355
            return
1545
1356
        file_id = self.tree_file_id(parent_id)
1546
 
        if file_id is None:
1547
 
            return
1548
 
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1549
 
        children = getattr(entry, 'children', {})
1550
 
        for child in children:
 
1357
        for child in self._tree.inventory[file_id].children.iterkeys():
1551
1358
            childpath = joinpath(path, child)
1552
1359
            yield self.trans_id_tree_path(childpath)
1553
1360
 
1554
1361
 
1555
 
class _PreviewTree(tree.Tree):
 
1362
class _PreviewTree(object):
1556
1363
    """Partial implementation of Tree to support show_diff_trees"""
1557
1364
 
1558
1365
    def __init__(self, transform):
1559
1366
        self._transform = transform
1560
 
        self._final_paths = FinalPaths(transform)
1561
 
        self.__by_parent = None
1562
 
        self._parent_ids = []
1563
 
        self._all_children_cache = {}
1564
 
        self._path2trans_id_cache = {}
1565
 
        self._final_name_cache = {}
1566
 
 
1567
 
    def _changes(self, file_id):
1568
 
        for changes in self._transform.iter_changes():
1569
 
            if changes[0] == file_id:
1570
 
                return changes
1571
 
 
1572
 
    def _content_change(self, file_id):
1573
 
        """Return True if the content of this file changed"""
1574
 
        changes = self._changes(file_id)
1575
 
        # changes[2] is true if the file content changed.  See
1576
 
        # InterTree.iter_changes.
1577
 
        return (changes is not None and changes[2])
1578
 
 
1579
 
    def _get_repository(self):
1580
 
        repo = getattr(self._transform._tree, '_repository', None)
1581
 
        if repo is None:
1582
 
            repo = self._transform._tree.branch.repository
1583
 
        return repo
1584
 
 
1585
 
    def _iter_parent_trees(self):
1586
 
        for revision_id in self.get_parent_ids():
1587
 
            try:
1588
 
                yield self.revision_tree(revision_id)
1589
 
            except errors.NoSuchRevisionInTree:
1590
 
                yield self._get_repository().revision_tree(revision_id)
1591
 
 
1592
 
    def _get_file_revision(self, file_id, vf, tree_revision):
1593
 
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1594
 
                       self._iter_parent_trees()]
1595
 
        vf.add_lines((file_id, tree_revision), parent_keys,
1596
 
                     self.get_file(file_id).readlines())
1597
 
        repo = self._get_repository()
1598
 
        base_vf = repo.texts
1599
 
        if base_vf not in vf.fallback_versionedfiles:
1600
 
            vf.fallback_versionedfiles.append(base_vf)
1601
 
        return tree_revision
1602
 
 
1603
 
    def _stat_limbo_file(self, file_id):
1604
 
        trans_id = self._transform.trans_id_file_id(file_id)
1605
 
        name = self._transform._limbo_name(trans_id)
1606
 
        return os.lstat(name)
1607
 
 
1608
 
    @property
1609
 
    def _by_parent(self):
1610
 
        if self.__by_parent is None:
1611
 
            self.__by_parent = self._transform.by_parent()
1612
 
        return self.__by_parent
1613
 
 
1614
 
    def _comparison_data(self, entry, path):
1615
 
        kind, size, executable, link_or_sha1 = self.path_content_summary(path)
1616
 
        if kind == 'missing':
1617
 
            kind = None
1618
 
            executable = False
1619
 
        else:
1620
 
            file_id = self._transform.final_file_id(self._path2trans_id(path))
1621
 
            executable = self.is_executable(file_id, path)
1622
 
        return kind, executable, None
1623
1367
 
1624
1368
    def lock_read(self):
1625
1369
        # Perhaps in theory, this should lock the TreeTransform?
1628
1372
    def unlock(self):
1629
1373
        pass
1630
1374
 
1631
 
    @property
1632
 
    def inventory(self):
1633
 
        """This Tree does not use inventory as its backing data."""
1634
 
        raise NotImplementedError(_PreviewTree.inventory)
1635
 
 
1636
 
    def get_root_id(self):
1637
 
        return self._transform.final_file_id(self._transform.root)
1638
 
 
1639
 
    def all_file_ids(self):
1640
 
        tree_ids = set(self._transform._tree.all_file_ids())
1641
 
        tree_ids.difference_update(self._transform.tree_file_id(t)
1642
 
                                   for t in self._transform._removed_id)
1643
 
        tree_ids.update(self._transform._new_id.values())
1644
 
        return tree_ids
1645
 
 
1646
 
    def __iter__(self):
1647
 
        return iter(self.all_file_ids())
1648
 
 
1649
 
    def has_id(self, file_id):
1650
 
        if file_id in self._transform._r_new_id:
1651
 
            return True
1652
 
        elif file_id in self._transform._removed_id:
1653
 
            return False
1654
 
        else:
1655
 
            return self._transform._tree.has_id(file_id)
1656
 
 
1657
 
    def _path2trans_id(self, path):
1658
 
        # We must not use None here, because that is a valid value to store.
1659
 
        trans_id = self._path2trans_id_cache.get(path, object)
1660
 
        if trans_id is not object:
1661
 
            return trans_id
1662
 
        segments = splitpath(path)
1663
 
        cur_parent = self._transform.root
1664
 
        for cur_segment in segments:
1665
 
            for child in self._all_children(cur_parent):
1666
 
                final_name = self._final_name_cache.get(child)
1667
 
                if final_name is None:
1668
 
                    final_name = self._transform.final_name(child)
1669
 
                    self._final_name_cache[child] = final_name
1670
 
                if final_name == cur_segment:
1671
 
                    cur_parent = child
1672
 
                    break
1673
 
            else:
1674
 
                self._path2trans_id_cache[path] = None
1675
 
                return None
1676
 
        self._path2trans_id_cache[path] = cur_parent
1677
 
        return cur_parent
1678
 
 
1679
 
    def path2id(self, path):
1680
 
        return self._transform.final_file_id(self._path2trans_id(path))
1681
 
 
1682
 
    def id2path(self, file_id):
1683
 
        trans_id = self._transform.trans_id_file_id(file_id)
1684
 
        try:
1685
 
            return self._final_paths._determine_path(trans_id)
1686
 
        except NoFinalPath:
1687
 
            raise errors.NoSuchId(self, file_id)
1688
 
 
1689
 
    def _all_children(self, trans_id):
1690
 
        children = self._all_children_cache.get(trans_id)
1691
 
        if children is not None:
1692
 
            return children
1693
 
        children = set(self._transform.iter_tree_children(trans_id))
1694
 
        # children in the _new_parent set are provided by _by_parent.
1695
 
        children.difference_update(self._transform._new_parent.keys())
1696
 
        children.update(self._by_parent.get(trans_id, []))
1697
 
        self._all_children_cache[trans_id] = children
1698
 
        return children
1699
 
 
1700
 
    def iter_children(self, file_id):
1701
 
        trans_id = self._transform.trans_id_file_id(file_id)
1702
 
        for child_trans_id in self._all_children(trans_id):
1703
 
            yield self._transform.final_file_id(child_trans_id)
1704
 
 
1705
 
    def extras(self):
1706
 
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
1707
 
                              in self._transform._tree.extras())
1708
 
        possible_extras.update(self._transform._new_contents)
1709
 
        possible_extras.update(self._transform._removed_id)
1710
 
        for trans_id in possible_extras:
1711
 
            if self._transform.final_file_id(trans_id) is None:
1712
 
                yield self._final_paths._determine_path(trans_id)
1713
 
 
1714
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids):
1715
 
        for trans_id, parent_file_id in ordered_entries:
1716
 
            file_id = self._transform.final_file_id(trans_id)
1717
 
            if file_id is None:
1718
 
                continue
1719
 
            if (specific_file_ids is not None
1720
 
                and file_id not in specific_file_ids):
1721
 
                continue
1722
 
            try:
1723
 
                kind = self._transform.final_kind(trans_id)
1724
 
            except NoSuchFile:
1725
 
                kind = self._transform._tree.stored_kind(file_id)
1726
 
            new_entry = inventory.make_entry(
1727
 
                kind,
1728
 
                self._transform.final_name(trans_id),
1729
 
                parent_file_id, file_id)
1730
 
            yield new_entry, trans_id
1731
 
 
1732
 
    def _list_files_by_dir(self):
1733
 
        todo = [ROOT_PARENT]
1734
 
        ordered_ids = []
1735
 
        while len(todo) > 0:
1736
 
            parent = todo.pop()
1737
 
            parent_file_id = self._transform.final_file_id(parent)
1738
 
            children = list(self._all_children(parent))
1739
 
            paths = dict(zip(children, self._final_paths.get_paths(children)))
1740
 
            children.sort(key=paths.get)
1741
 
            todo.extend(reversed(children))
1742
 
            for trans_id in children:
1743
 
                ordered_ids.append((trans_id, parent_file_id))
1744
 
        return ordered_ids
1745
 
 
1746
 
    def iter_entries_by_dir(self, specific_file_ids=None):
1747
 
        # This may not be a maximally efficient implementation, but it is
1748
 
        # reasonably straightforward.  An implementation that grafts the
1749
 
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1750
 
        # might be more efficient, but requires tricky inferences about stack
1751
 
        # position.
1752
 
        ordered_ids = self._list_files_by_dir()
1753
 
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1754
 
                                                      specific_file_ids):
1755
 
            yield unicode(self._final_paths.get_path(trans_id)), entry
1756
 
 
1757
 
    def list_files(self, include_root=False):
1758
 
        """See Tree.list_files."""
1759
 
        # XXX This should behave like WorkingTree.list_files, but is really
1760
 
        # more like RevisionTree.list_files.
1761
 
        for path, entry in self.iter_entries_by_dir():
1762
 
            if entry.name == '' and not include_root:
1763
 
                continue
1764
 
            yield path, 'V', entry.kind, entry.file_id, entry
1765
 
 
1766
 
    def kind(self, file_id):
1767
 
        trans_id = self._transform.trans_id_file_id(file_id)
1768
 
        return self._transform.final_kind(trans_id)
1769
 
 
1770
 
    def stored_kind(self, file_id):
1771
 
        trans_id = self._transform.trans_id_file_id(file_id)
1772
 
        try:
1773
 
            return self._transform._new_contents[trans_id]
1774
 
        except KeyError:
1775
 
            return self._transform._tree.stored_kind(file_id)
1776
 
 
1777
 
    def get_file_mtime(self, file_id, path=None):
1778
 
        """See Tree.get_file_mtime"""
1779
 
        if not self._content_change(file_id):
1780
 
            return self._transform._tree.get_file_mtime(file_id, path)
1781
 
        return self._stat_limbo_file(file_id).st_mtime
1782
 
 
1783
 
    def _file_size(self, entry, stat_value):
1784
 
        return self.get_file_size(entry.file_id)
1785
 
 
1786
 
    def get_file_size(self, file_id):
1787
 
        """See Tree.get_file_size"""
1788
 
        if self.kind(file_id) == 'file':
1789
 
            return self._transform._tree.get_file_size(file_id)
1790
 
        else:
1791
 
            return None
1792
 
 
1793
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
1794
 
        trans_id = self._transform.trans_id_file_id(file_id)
1795
 
        kind = self._transform._new_contents.get(trans_id)
1796
 
        if kind is None:
1797
 
            return self._transform._tree.get_file_sha1(file_id)
1798
 
        if kind == 'file':
1799
 
            fileobj = self.get_file(file_id)
1800
 
            try:
1801
 
                return sha_file(fileobj)
1802
 
            finally:
1803
 
                fileobj.close()
1804
 
 
1805
 
    def is_executable(self, file_id, path=None):
1806
 
        if file_id is None:
1807
 
            return False
1808
 
        trans_id = self._transform.trans_id_file_id(file_id)
1809
 
        try:
1810
 
            return self._transform._new_executability[trans_id]
1811
 
        except KeyError:
1812
 
            try:
1813
 
                return self._transform._tree.is_executable(file_id, path)
1814
 
            except OSError, e:
1815
 
                if e.errno == errno.ENOENT:
1816
 
                    return False
1817
 
                raise
1818
 
            except errors.NoSuchId:
1819
 
                return False
1820
 
 
1821
 
    def path_content_summary(self, path):
1822
 
        trans_id = self._path2trans_id(path)
1823
 
        tt = self._transform
1824
 
        tree_path = tt._tree_id_paths.get(trans_id)
1825
 
        kind = tt._new_contents.get(trans_id)
1826
 
        if kind is None:
1827
 
            if tree_path is None or trans_id in tt._removed_contents:
1828
 
                return 'missing', None, None, None
1829
 
            summary = tt._tree.path_content_summary(tree_path)
1830
 
            kind, size, executable, link_or_sha1 = summary
1831
 
        else:
1832
 
            link_or_sha1 = None
1833
 
            limbo_name = tt._limbo_name(trans_id)
1834
 
            if trans_id in tt._new_reference_revision:
1835
 
                kind = 'tree-reference'
1836
 
            if kind == 'file':
1837
 
                statval = os.lstat(limbo_name)
1838
 
                size = statval.st_size
1839
 
                if not supports_executable():
1840
 
                    executable = None
1841
 
                else:
1842
 
                    executable = statval.st_mode & S_IEXEC
1843
 
            else:
1844
 
                size = None
1845
 
                executable = None
1846
 
            if kind == 'symlink':
1847
 
                link_or_sha1 = os.readlink(limbo_name)
1848
 
        if supports_executable():
1849
 
            executable = tt._new_executability.get(trans_id, executable)
1850
 
        return kind, size, executable, link_or_sha1
1851
 
 
1852
1375
    def iter_changes(self, from_tree, include_unchanged=False,
1853
1376
                      specific_files=None, pb=None, extra_trees=None,
1854
1377
                      require_versioned=True, want_unversioned=False):
1855
1378
        """See InterTree.iter_changes.
1856
1379
 
1857
 
        This has a fast path that is only used when the from_tree matches
1858
 
        the transform tree, and no fancy options are supplied.
 
1380
        This implementation does not support include_unchanged, specific_files,
 
1381
        or want_unversioned.  extra_trees, require_versioned, and pb are
 
1382
        ignored.
1859
1383
        """
1860
 
        if (from_tree is not self._transform._tree or include_unchanged or
1861
 
            specific_files or want_unversioned):
1862
 
            return tree.InterTree(from_tree, self).iter_changes(
1863
 
                include_unchanged=include_unchanged,
1864
 
                specific_files=specific_files,
1865
 
                pb=pb,
1866
 
                extra_trees=extra_trees,
1867
 
                require_versioned=require_versioned,
1868
 
                want_unversioned=want_unversioned)
 
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')
1869
1390
        if want_unversioned:
1870
1391
            raise ValueError('want_unversioned is not supported')
1871
1392
        return self._transform.iter_changes()
1872
1393
 
1873
 
    def get_file(self, file_id, path=None):
 
1394
    def kind(self, file_id):
 
1395
        trans_id = self._transform.trans_id_file_id(file_id)
 
1396
        return self._transform.final_kind(trans_id)
 
1397
 
 
1398
    def get_file_mtime(self, file_id, path=None):
 
1399
        """See Tree.get_file_mtime"""
 
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):
1874
1405
        """See Tree.get_file"""
1875
 
        if not self._content_change(file_id):
1876
 
            return self._transform._tree.get_file(file_id, path)
1877
1406
        trans_id = self._transform.trans_id_file_id(file_id)
1878
1407
        name = self._transform._limbo_name(trans_id)
1879
1408
        return open(name, 'rb')
1880
1409
 
1881
 
    def annotate_iter(self, file_id,
1882
 
                      default_revision=_mod_revision.CURRENT_REVISION):
1883
 
        changes = self._changes(file_id)
1884
 
        if changes is None:
1885
 
            get_old = True
1886
 
        else:
1887
 
            changed_content, versioned, kind = (changes[2], changes[3],
1888
 
                                                changes[6])
1889
 
            if kind[1] is None:
1890
 
                return None
1891
 
            get_old = (kind[0] == 'file' and versioned[0])
1892
 
        if get_old:
1893
 
            old_annotation = self._transform._tree.annotate_iter(file_id,
1894
 
                default_revision=default_revision)
1895
 
        else:
1896
 
            old_annotation = []
1897
 
        if changes is None:
1898
 
            return old_annotation
1899
 
        if not changed_content:
1900
 
            return old_annotation
1901
 
        return annotate.reannotate([old_annotation],
1902
 
                                   self.get_file(file_id).readlines(),
1903
 
                                   default_revision)
1904
 
 
1905
1410
    def get_symlink_target(self, file_id):
1906
1411
        """See Tree.get_symlink_target"""
1907
 
        if not self._content_change(file_id):
1908
 
            return self._transform._tree.get_symlink_target(file_id)
1909
1412
        trans_id = self._transform.trans_id_file_id(file_id)
1910
1413
        name = self._transform._limbo_name(trans_id)
1911
1414
        return os.readlink(name)
1912
1415
 
1913
 
    def walkdirs(self, prefix=''):
1914
 
        pending = [self._transform.root]
1915
 
        while len(pending) > 0:
1916
 
            parent_id = pending.pop()
1917
 
            children = []
1918
 
            subdirs = []
1919
 
            prefix = prefix.rstrip('/')
1920
 
            parent_path = self._final_paths.get_path(parent_id)
1921
 
            parent_file_id = self._transform.final_file_id(parent_id)
1922
 
            for child_id in self._all_children(parent_id):
1923
 
                path_from_root = self._final_paths.get_path(child_id)
1924
 
                basename = self._transform.final_name(child_id)
1925
 
                file_id = self._transform.final_file_id(child_id)
1926
 
                try:
1927
 
                    kind = self._transform.final_kind(child_id)
1928
 
                    versioned_kind = kind
1929
 
                except NoSuchFile:
1930
 
                    kind = 'unknown'
1931
 
                    versioned_kind = self._transform._tree.stored_kind(file_id)
1932
 
                if versioned_kind == 'directory':
1933
 
                    subdirs.append(child_id)
1934
 
                children.append((path_from_root, basename, kind, None,
1935
 
                                 file_id, versioned_kind))
1936
 
            children.sort()
1937
 
            if parent_path.startswith(prefix):
1938
 
                yield (parent_path, parent_file_id), children
1939
 
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1940
 
                                  reverse=True))
1941
 
 
1942
 
    def get_parent_ids(self):
1943
 
        return self._parent_ids
1944
 
 
1945
 
    def set_parent_ids(self, parent_ids):
1946
 
        self._parent_ids = parent_ids
1947
 
 
1948
 
    def get_revision_tree(self, revision_id):
1949
 
        return self._transform._tree.get_revision_tree(revision_id)
 
1416
    def paths2ids(self, specific_files, trees=None, require_versioned=False):
 
1417
        """See Tree.paths2ids"""
 
1418
        return 'not_empty'
1950
1419
 
1951
1420
 
1952
1421
def joinpath(parent, child):
1984
1453
            self._known_paths[trans_id] = self._determine_path(trans_id)
1985
1454
        return self._known_paths[trans_id]
1986
1455
 
1987
 
    def get_paths(self, trans_ids):
1988
 
        return [(self.get_path(t), t) for t in trans_ids]
1989
 
 
1990
 
 
1991
1456
 
1992
1457
def topology_sorted_ids(tree):
1993
1458
    """Determine the topological order of the ids in a tree"""
1996
1461
    return file_ids
1997
1462
 
1998
1463
 
1999
 
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
2000
 
               delta_from_tree=False):
 
1464
def build_tree(tree, wt, accelerator_tree=None, hardlink=False):
2001
1465
    """Create working tree for a branch, using a TreeTransform.
2002
1466
    
2003
1467
    This function should be used on empty trees, having a tree root at most.
2019
1483
    :param hardlink: If true, hard-link files to accelerator_tree, where
2020
1484
        possible.  accelerator_tree must implement abspath, i.e. be a
2021
1485
        working tree.
2022
 
    :param delta_from_tree: If true, build_tree may use the input Tree to
2023
 
        generate the inventory delta.
2024
1486
    """
2025
1487
    wt.lock_tree_write()
2026
1488
    try:
2029
1491
            if accelerator_tree is not None:
2030
1492
                accelerator_tree.lock_read()
2031
1493
            try:
2032
 
                return _build_tree(tree, wt, accelerator_tree, hardlink,
2033
 
                                   delta_from_tree)
 
1494
                return _build_tree(tree, wt, accelerator_tree, hardlink)
2034
1495
            finally:
2035
1496
                if accelerator_tree is not None:
2036
1497
                    accelerator_tree.unlock()
2040
1501
        wt.unlock()
2041
1502
 
2042
1503
 
2043
 
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
 
1504
def _build_tree(tree, wt, accelerator_tree, hardlink):
2044
1505
    """See build_tree."""
2045
1506
    for num, _unused in enumerate(wt.all_file_ids()):
2046
1507
        if num > 0:  # more than just a root
2047
1508
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2048
 
    existing_files = set()
2049
 
    for dir, files in wt.walkdirs():
2050
 
        existing_files.update(f[0] for f in files)
2051
1509
    file_trans_id = {}
2052
1510
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2053
1511
    pp = ProgressPhase("Build phase", 2, top_pb)
2072
1530
        try:
2073
1531
            deferred_contents = []
2074
1532
            num = 0
2075
 
            total = len(tree.inventory)
2076
 
            if delta_from_tree:
2077
 
                precomputed_delta = []
2078
 
            else:
2079
 
                precomputed_delta = None
2080
1533
            for num, (tree_path, entry) in \
2081
1534
                enumerate(tree.inventory.iter_entries_by_dir()):
2082
 
                pb.update("Building tree", num - len(deferred_contents), total)
 
1535
                pb.update("Building tree", num - len(deferred_contents),
 
1536
                          len(tree.inventory))
2083
1537
                if entry.parent_id is None:
2084
1538
                    continue
2085
1539
                reparent = False
2086
1540
                file_id = entry.file_id
2087
 
                if delta_from_tree:
2088
 
                    precomputed_delta.append((None, tree_path, file_id, entry))
2089
 
                if tree_path in existing_files:
2090
 
                    target_path = wt.abspath(tree_path)
 
1541
                target_path = wt.abspath(tree_path)
 
1542
                try:
2091
1543
                    kind = file_kind(target_path)
 
1544
                except NoSuchFile:
 
1545
                    pass
 
1546
                else:
2092
1547
                    if kind == "directory":
2093
1548
                        try:
2094
1549
                            bzrdir.BzrDir.open(target_path)
2102
1557
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2103
1558
                        if kind == 'directory':
2104
1559
                            reparent = True
 
1560
                if entry.parent_id not in file_trans_id:
 
1561
                    raise AssertionError(
 
1562
                        'entry %s parent id %r is not in file_trans_id %r'
 
1563
                        % (entry, entry.parent_id, file_trans_id))
2105
1564
                parent_id = file_trans_id[entry.parent_id]
2106
1565
                if entry.kind == 'file':
2107
1566
                    # We *almost* replicate new_by_entry, so that we can defer
2108
1567
                    # getting the file text, and get them all at once.
2109
1568
                    trans_id = tt.create_path(entry.name, parent_id)
2110
1569
                    file_trans_id[file_id] = trans_id
2111
 
                    tt.version_file(file_id, trans_id)
2112
 
                    executable = tree.is_executable(file_id, tree_path)
2113
 
                    if executable:
 
1570
                    tt.version_file(entry.file_id, trans_id)
 
1571
                    executable = tree.is_executable(entry.file_id, tree_path)
 
1572
                    if executable is not None:
2114
1573
                        tt.set_executability(executable, trans_id)
2115
 
                    deferred_contents.append((file_id, trans_id))
 
1574
                    deferred_contents.append((entry.file_id, trans_id))
2116
1575
                else:
2117
1576
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2118
1577
                                                          tree)
2129
1588
        divert_trans = set(file_trans_id[f] for f in divert)
2130
1589
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2131
1590
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2132
 
        if len(raw_conflicts) > 0:
2133
 
            precomputed_delta = None
2134
1591
        conflicts = cook_conflicts(raw_conflicts, tt)
2135
1592
        for conflict in conflicts:
2136
1593
            warning(conflict)
2138
1595
            wt.add_conflicts(conflicts)
2139
1596
        except errors.UnsupportedOperation:
2140
1597
            pass
2141
 
        result = tt.apply(no_conflicts=True,
2142
 
                          precomputed_delta=precomputed_delta)
 
1598
        result = tt.apply(no_conflicts=True)
2143
1599
    finally:
2144
1600
        tt.finalize()
2145
1601
        top_pb.finished()
2188
1644
    by_parent = tt.by_parent()
2189
1645
    for child in by_parent[old_parent]:
2190
1646
        tt.adjust_path(tt.final_name(child), new_parent, child)
2191
 
    return by_parent[old_parent]
2192
1647
 
2193
1648
def _content_match(tree, entry, file_id, kind, target_path):
2194
1649
    if entry.kind != kind:
2208
1663
    new_conflicts = set()
2209
1664
    for c_type, conflict in ((c[0], c) for c in conflicts):
2210
1665
        # Anything but a 'duplicate' would indicate programmer error
2211
 
        if c_type != 'duplicate':
2212
 
            raise AssertionError(c_type)
 
1666
        assert c_type == 'duplicate', c_type
2213
1667
        # Now figure out which is new and which is old
2214
1668
        if tt.new_contents(conflict[1]):
2215
1669
            new_file = conflict[1]
2255
1709
        raise errors.BadFileKindError(name, kind)
2256
1710
 
2257
1711
 
2258
 
@deprecated_function(deprecated_in((1, 9, 0)))
2259
1712
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2260
 
    """Create new file contents according to an inventory entry.
2261
 
 
2262
 
    DEPRECATED.  Use create_from_tree instead.
2263
 
    """
 
1713
    """Create new file contents according to an inventory entry."""
2264
1714
    if entry.kind == "file":
2265
1715
        if lines is None:
2266
1716
            lines = tree.get_file(entry.file_id).readlines()
2271
1721
        tt.create_directory(trans_id)
2272
1722
 
2273
1723
 
2274
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2275
 
    """Create new file contents according to tree contents."""
2276
 
    kind = tree.kind(file_id)
2277
 
    if kind == 'directory':
2278
 
        tt.create_directory(trans_id)
2279
 
    elif kind == "file":
2280
 
        if bytes is None:
2281
 
            tree_file = tree.get_file(file_id)
2282
 
            try:
2283
 
                bytes = tree_file.readlines()
2284
 
            finally:
2285
 
                tree_file.close()
2286
 
        tt.create_file(bytes, trans_id)
2287
 
    elif kind == "symlink":
2288
 
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2289
 
    else:
2290
 
        raise AssertionError('Unknown kind %r' % kind)
2291
 
 
2292
 
 
2293
1724
def create_entry_executability(tt, entry, trans_id):
2294
1725
    """Set the executability of a trans_id according to an inventory entry"""
2295
1726
    if entry.kind == "file":
2296
1727
        tt.set_executability(entry.executable, trans_id)
2297
1728
 
2298
1729
 
 
1730
@deprecated_function(zero_fifteen)
 
1731
def find_interesting(working_tree, target_tree, filenames):
 
1732
    """Find the ids corresponding to specified filenames.
 
1733
    
 
1734
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
 
1735
    """
 
1736
    working_tree.lock_read()
 
1737
    try:
 
1738
        target_tree.lock_read()
 
1739
        try:
 
1740
            return working_tree.paths2ids(filenames, [target_tree])
 
1741
        finally:
 
1742
            target_tree.unlock()
 
1743
    finally:
 
1744
        working_tree.unlock()
 
1745
 
 
1746
 
 
1747
@deprecated_function(zero_ninety)
 
1748
def change_entry(tt, file_id, working_tree, target_tree, 
 
1749
                 trans_id_file_id, backups, trans_id, by_parent):
 
1750
    """Replace a file_id's contents with those from a target tree."""
 
1751
    if file_id is None and target_tree is None:
 
1752
        # skip the logic altogether in the deprecation test
 
1753
        return
 
1754
    e_trans_id = trans_id_file_id(file_id)
 
1755
    entry = target_tree.inventory[file_id]
 
1756
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
 
1757
                                                           working_tree)
 
1758
    if contents_mod:
 
1759
        mode_id = e_trans_id
 
1760
        if has_contents:
 
1761
            if not backups:
 
1762
                tt.delete_contents(e_trans_id)
 
1763
            else:
 
1764
                parent_trans_id = trans_id_file_id(entry.parent_id)
 
1765
                backup_name = get_backup_name(entry, by_parent,
 
1766
                                              parent_trans_id, tt)
 
1767
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
 
1768
                tt.unversion_file(e_trans_id)
 
1769
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
 
1770
                tt.version_file(file_id, e_trans_id)
 
1771
                trans_id[file_id] = e_trans_id
 
1772
        create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
 
1773
        create_entry_executability(tt, entry, e_trans_id)
 
1774
 
 
1775
    elif meta_mod:
 
1776
        tt.set_executability(entry.executable, e_trans_id)
 
1777
    if tt.final_name(e_trans_id) != entry.name:
 
1778
        adjust_path  = True
 
1779
    else:
 
1780
        parent_id = tt.final_parent(e_trans_id)
 
1781
        parent_file_id = tt.final_file_id(parent_id)
 
1782
        if parent_file_id != entry.parent_id:
 
1783
            adjust_path = True
 
1784
        else:
 
1785
            adjust_path = False
 
1786
    if adjust_path:
 
1787
        parent_trans_id = trans_id_file_id(entry.parent_id)
 
1788
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
 
1789
 
 
1790
 
2299
1791
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2300
1792
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2301
1793
 
2346
1838
    tt = TreeTransform(working_tree, pb)
2347
1839
    try:
2348
1840
        pp = ProgressPhase("Revert phase", 3, pb)
2349
 
        conflicts, merge_modified = _prepare_revert_transform(
2350
 
            working_tree, target_tree, tt, filenames, backups, pp)
 
1841
        pp.next_phase()
 
1842
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1843
        try:
 
1844
            merge_modified = _alter_files(working_tree, target_tree, tt,
 
1845
                                          child_pb, filenames, backups)
 
1846
        finally:
 
1847
            child_pb.finished()
 
1848
        pp.next_phase()
 
1849
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1850
        try:
 
1851
            raw_conflicts = resolve_conflicts(tt, child_pb,
 
1852
                lambda t, c: conflict_pass(t, c, target_tree))
 
1853
        finally:
 
1854
            child_pb.finished()
 
1855
        conflicts = cook_conflicts(raw_conflicts, tt)
2351
1856
        if change_reporter:
2352
1857
            change_reporter = delta._ChangeReporter(
2353
1858
                unversioned_filter=working_tree.is_ignored)
2364
1869
    return conflicts
2365
1870
 
2366
1871
 
2367
 
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2368
 
                              backups, pp, basis_tree=None,
2369
 
                              merge_modified=None):
2370
 
    pp.next_phase()
2371
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2372
 
    try:
2373
 
        if merge_modified is None:
2374
 
            merge_modified = working_tree.merge_modified()
2375
 
        merge_modified = _alter_files(working_tree, target_tree, tt,
2376
 
                                      child_pb, filenames, backups,
2377
 
                                      merge_modified, basis_tree)
2378
 
    finally:
2379
 
        child_pb.finished()
2380
 
    pp.next_phase()
2381
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2382
 
    try:
2383
 
        raw_conflicts = resolve_conflicts(tt, child_pb,
2384
 
            lambda t, c: conflict_pass(t, c, target_tree))
2385
 
    finally:
2386
 
        child_pb.finished()
2387
 
    conflicts = cook_conflicts(raw_conflicts, tt)
2388
 
    return conflicts, merge_modified
2389
 
 
2390
 
 
2391
1872
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2392
 
                 backups, merge_modified, basis_tree=None):
2393
 
    if basis_tree is not None:
2394
 
        basis_tree.lock_read()
 
1873
                 backups):
 
1874
    merge_modified = working_tree.merge_modified()
2395
1875
    change_list = target_tree.iter_changes(working_tree,
2396
1876
        specific_files=specific_files, pb=pb)
2397
 
    if target_tree.get_root_id() is None:
 
1877
    if target_tree.inventory.root is None:
2398
1878
        skip_root = True
2399
1879
    else:
2400
1880
        skip_root = False
 
1881
    basis_tree = None
2401
1882
    try:
2402
1883
        deferred_files = []
2403
1884
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2439
1920
                        # contents
2440
1921
                        mode_id = trans_id
2441
1922
                        trans_id = new_trans_id
2442
 
                if kind[1] in ('directory', 'tree-reference'):
 
1923
                if kind[1] == 'directory':
2443
1924
                    tt.create_directory(trans_id)
2444
 
                    if kind[1] == 'tree-reference':
2445
 
                        revision = target_tree.get_reference_revision(file_id,
2446
 
                                                                      path[1])
2447
 
                        tt.set_tree_reference(revision, trans_id)
2448
1925
                elif kind[1] == 'symlink':
2449
1926
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
2450
1927
                                      trans_id)
2464
1941
                    # preserve the execute bit when backing up
2465
1942
                    if keep_content and executable[0] == executable[1]:
2466
1943
                        tt.set_executability(executable[1], trans_id)
2467
 
                elif kind[1] is not None:
2468
 
                    raise AssertionError(kind[1])
 
1944
                else:
 
1945
                    assert kind[1] is None
2469
1946
            if versioned == (False, True):
2470
1947
                tt.version_file(file_id, trans_id)
2471
1948
            if versioned == (True, False):
2472
1949
                tt.unversion_file(trans_id)
2473
 
            if (name[1] is not None and
 
1950
            if (name[1] is not None and 
2474
1951
                (name[0] != name[1] or parent[0] != parent[1])):
2475
 
                if name[1] == '' and parent[1] is None:
2476
 
                    parent_trans = ROOT_PARENT
2477
 
                else:
2478
 
                    parent_trans = tt.trans_id_file_id(parent[1])
2479
 
                tt.adjust_path(name[1], parent_trans, trans_id)
 
1952
                tt.adjust_path(
 
1953
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
2480
1954
            if executable[0] != executable[1] and kind[1] == "file":
2481
1955
                tt.set_executability(executable[1], trans_id)
2482
1956
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2545
2019
                new_conflicts.add(('deleting parent', 'Not deleting', 
2546
2020
                                   trans_id))
2547
2021
            except KeyError:
2548
 
                create = True
 
2022
                tt.create_directory(trans_id)
 
2023
                new_conflicts.add((c_type, 'Created directory', trans_id))
2549
2024
                try:
2550
2025
                    tt.final_name(trans_id)
2551
2026
                except NoFinalPath:
2552
2027
                    if path_tree is not None:
2553
2028
                        file_id = tt.final_file_id(trans_id)
2554
 
                        if file_id is None:
2555
 
                            file_id = tt.inactive_file_id(trans_id)
2556
2029
                        entry = path_tree.inventory[file_id]
2557
 
                        # special-case the other tree root (move its
2558
 
                        # children to current root)
2559
 
                        if entry.parent_id is None:
2560
 
                            create=False
2561
 
                            moved = _reparent_transform_children(
2562
 
                                tt, trans_id, tt.root)
2563
 
                            for child in moved:
2564
 
                                new_conflicts.add((c_type, 'Moved to root',
2565
 
                                                   child))
2566
 
                        else:
2567
 
                            parent_trans_id = tt.trans_id_file_id(
2568
 
                                entry.parent_id)
2569
 
                            tt.adjust_path(entry.name, parent_trans_id,
2570
 
                                           trans_id)
2571
 
                if create:
2572
 
                    tt.create_directory(trans_id)
2573
 
                    new_conflicts.add((c_type, 'Created directory', trans_id))
 
2030
                        parent_trans_id = tt.trans_id_file_id(entry.parent_id)
 
2031
                        tt.adjust_path(entry.name, parent_trans_id, trans_id)
2574
2032
        elif c_type == 'unversioned parent':
2575
 
            file_id = tt.inactive_file_id(conflict[1])
2576
 
            # special-case the other tree root (move its children instead)
2577
 
            if path_tree and file_id in path_tree:
2578
 
                if path_tree.inventory[file_id].parent_id is None:
2579
 
                    continue
2580
 
            tt.version_file(file_id, conflict[1])
 
2033
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
2581
2034
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
2582
2035
        elif c_type == 'non-directory parent':
2583
2036
            parent_id = conflict[1]
2590
2043
            if parent_file_id is not None:
2591
2044
                tt.unversion_file(parent_id)
2592
2045
            new_conflicts.add((c_type, 'Created directory', new_parent_id))
2593
 
        elif c_type == 'versioning no contents':
2594
 
            tt.cancel_versioning(conflict[1])
2595
2046
    return new_conflicts
2596
2047
 
2597
2048