~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Jelmer Vernooij
  • Date: 2011-05-08 11:20:31 UTC
  • mto: This revision was merged to the branch mainline in revision 5844.
  • Revision ID: jelmer@samba.org-20110508112031-dqa1cw1gjhqsj2zr
Move vf-specific gather_stats bits to vf_repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
19
19
from stat import S_ISREG, S_IEXEC
20
20
import time
21
21
 
22
 
from bzrlib.lazy_import import lazy_import
23
 
lazy_import(globals(), """
 
22
from bzrlib import (
 
23
    errors,
 
24
    lazy_import,
 
25
    registry,
 
26
    trace,
 
27
    tree,
 
28
    )
 
29
lazy_import.lazy_import(globals(), """
24
30
from bzrlib import (
25
31
    annotate,
26
32
    bencode,
33
39
    osutils,
34
40
    revision as _mod_revision,
35
41
    ui,
 
42
    urlutils,
36
43
    )
37
44
""")
38
45
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
40
47
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
41
48
                           UnableCreateSymlink)
42
49
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
43
 
from bzrlib.inventory import InventoryEntry
44
50
from bzrlib.osutils import (
45
51
    delete_any,
46
52
    file_kind,
47
53
    has_symlinks,
48
 
    lexists,
49
54
    pathjoin,
50
55
    sha_file,
51
56
    splitpath,
52
57
    supports_executable,
53
 
)
 
58
    )
54
59
from bzrlib.progress import ProgressPhase
55
60
from bzrlib.symbol_versioning import (
56
 
        deprecated_function,
57
 
        deprecated_in,
58
 
        )
59
 
from bzrlib.trace import mutter, warning
60
 
from bzrlib import tree
61
 
import bzrlib.ui
62
 
import bzrlib.urlutils as urlutils
 
61
    deprecated_function,
 
62
    deprecated_in,
 
63
    deprecated_method,
 
64
    )
63
65
 
64
66
 
65
67
ROOT_PARENT = "root-parent"
66
68
 
67
 
 
68
69
def unique_add(map, key, value):
69
70
    if key in map:
70
71
        raise DuplicateKey(key=key)
71
72
    map[key] = value
72
73
 
73
74
 
 
75
 
74
76
class _TransformResults(object):
75
77
    def __init__(self, modified_paths, rename_count):
76
78
        object.__init__(self)
100
102
        self._new_parent = {}
101
103
        # mapping of trans_id with new contents -> new file_kind
102
104
        self._new_contents = {}
 
105
        # mapping of trans_id => (sha1 of content, stat_value)
 
106
        self._observed_sha1s = {}
103
107
        # Set of trans_ids whose contents will be removed
104
108
        self._removed_contents = set()
105
109
        # Mapping of trans_id -> new execute-bit value
124
128
            self._new_root = self.trans_id_tree_file_id(root_id)
125
129
        else:
126
130
            self._new_root = None
127
 
        # Indictor of whether the transform has been applied
 
131
        # Indicator of whether the transform has been applied
128
132
        self._done = False
129
133
        # A progress bar
130
134
        self._pb = pb
522
526
        for trans_id in self._removed_id:
523
527
            file_id = self.tree_file_id(trans_id)
524
528
            if file_id is not None:
 
529
                # XXX: This seems like something that should go via a different
 
530
                #      indirection.
525
531
                if self._tree.inventory[file_id].kind == 'directory':
526
532
                    parents.append(trans_id)
527
533
            elif self.tree_kind(trans_id) == 'directory':
531
537
            # ensure that all children are registered with the transaction
532
538
            list(self.iter_tree_children(parent_id))
533
539
 
 
540
    @deprecated_method(deprecated_in((2, 3, 0)))
534
541
    def has_named_child(self, by_parent, parent_id, name):
535
 
        try:
536
 
            children = by_parent[parent_id]
537
 
        except KeyError:
538
 
            children = []
539
 
        for child in children:
 
542
        return self._has_named_child(
 
543
            name, parent_id, known_children=by_parent.get(parent_id, []))
 
544
 
 
545
    def _has_named_child(self, name, parent_id, known_children):
 
546
        """Does a parent already have a name child.
 
547
 
 
548
        :param name: The searched for name.
 
549
 
 
550
        :param parent_id: The parent for which the check is made.
 
551
 
 
552
        :param known_children: The already known children. This should have
 
553
            been recently obtained from `self.by_parent.get(parent_id)`
 
554
            (or will be if None is passed).
 
555
        """
 
556
        if known_children is None:
 
557
            known_children = self.by_parent().get(parent_id, [])
 
558
        for child in known_children:
540
559
            if self.final_name(child) == name:
541
560
                return True
542
 
        try:
543
 
            path = self._tree_id_paths[parent_id]
544
 
        except KeyError:
 
561
        parent_path = self._tree_id_paths.get(parent_id, None)
 
562
        if parent_path is None:
 
563
            # No parent... no children
545
564
            return False
546
 
        childpath = joinpath(path, name)
547
 
        child_id = self._tree_path_ids.get(childpath)
 
565
        child_path = joinpath(parent_path, name)
 
566
        child_id = self._tree_path_ids.get(child_path, None)
548
567
        if child_id is None:
549
 
            return lexists(self._tree.abspath(childpath))
 
568
            # Not known by the tree transform yet, check the filesystem
 
569
            return osutils.lexists(self._tree.abspath(child_path))
550
570
        else:
551
 
            if self.final_parent(child_id) != parent_id:
552
 
                return False
553
 
            if child_id in self._removed_contents:
554
 
                # XXX What about dangling file-ids?
555
 
                return False
556
 
            else:
557
 
                return True
 
571
            raise AssertionError('child_id is missing: %s, %s, %s'
 
572
                                 % (name, parent_id, child_id))
 
573
 
 
574
    def _available_backup_name(self, name, target_id):
 
575
        """Find an available backup name.
 
576
 
 
577
        :param name: The basename of the file.
 
578
 
 
579
        :param target_id: The directory trans_id where the backup should 
 
580
            be placed.
 
581
        """
 
582
        known_children = self.by_parent().get(target_id, [])
 
583
        return osutils.available_backup_name(
 
584
            name,
 
585
            lambda base: self._has_named_child(
 
586
                base, target_id, known_children))
558
587
 
559
588
    def _parent_loops(self):
560
589
        """No entry should be its own ancestor"""
599
628
            if kind is None:
600
629
                conflicts.append(('versioning no contents', trans_id))
601
630
                continue
602
 
            if not InventoryEntry.versionable_kind(kind):
 
631
            if not inventory.InventoryEntry.versionable_kind(kind):
603
632
                conflicts.append(('versioning bad kind', trans_id, kind))
604
633
        return conflicts
605
634
 
637
666
        if (self._new_name, self._new_parent) == ({}, {}):
638
667
            return conflicts
639
668
        for children in by_parent.itervalues():
640
 
            name_ids = [(self.final_name(t), t) for t in children]
641
 
            if not self._case_sensitive_target:
642
 
                name_ids = [(n.lower(), t) for n, t in name_ids]
 
669
            name_ids = []
 
670
            for child_tid in children:
 
671
                name = self.final_name(child_tid)
 
672
                if name is not None:
 
673
                    # Keep children only if they still exist in the end
 
674
                    if not self._case_sensitive_target:
 
675
                        name = name.lower()
 
676
                    name_ids.append((name, child_tid))
643
677
            name_ids.sort()
644
678
            last_name = None
645
679
            last_trans_id = None
669
703
        return conflicts
670
704
 
671
705
    def _parent_type_conflicts(self, by_parent):
672
 
        """parents must have directory 'contents'."""
 
706
        """Children must have a directory parent"""
673
707
        conflicts = []
674
708
        for parent_id, children in by_parent.iteritems():
675
709
            if parent_id is ROOT_PARENT:
676
710
                continue
677
 
            if not self._any_contents(children):
 
711
            no_children = True
 
712
            for child_id in children:
 
713
                if self.final_kind(child_id) is not None:
 
714
                    no_children = False
 
715
                    break
 
716
            if no_children:
678
717
                continue
 
718
            # There is at least a child, so we need an existing directory to
 
719
            # contain it.
679
720
            kind = self.final_kind(parent_id)
680
721
            if kind is None:
 
722
                # The directory will be deleted
681
723
                conflicts.append(('missing parent', parent_id))
682
724
            elif kind != "directory":
 
725
                # Meh, we need a *directory* to put something in it
683
726
                conflicts.append(('non-directory parent', parent_id))
684
727
        return conflicts
685
728
 
686
 
    def _any_contents(self, trans_ids):
687
 
        """Return true if any of the trans_ids, will have contents."""
688
 
        for trans_id in trans_ids:
689
 
            if self.final_kind(trans_id) is not None:
690
 
                return True
691
 
        return False
692
 
 
693
729
    def _set_executability(self, path, trans_id):
694
730
        """Set the executability of versioned files """
695
731
        if supports_executable():
717
753
        return trans_id
718
754
 
719
755
    def new_file(self, name, parent_id, contents, file_id=None,
720
 
                 executable=None):
 
756
                 executable=None, sha1=None):
721
757
        """Convenience method to create files.
722
758
 
723
759
        name is the name of the file to create.
730
766
        trans_id = self._new_entry(name, parent_id, file_id)
731
767
        # TODO: rather than scheduling a set_executable call,
732
768
        # have create_file create the file with the right mode.
733
 
        self.create_file(contents, trans_id)
 
769
        self.create_file(contents, trans_id, sha1=sha1)
734
770
        if executable is not None:
735
771
            self.set_executability(executable, trans_id)
736
772
        return trans_id
759
795
        self.create_symlink(target, trans_id)
760
796
        return trans_id
761
797
 
 
798
    def new_orphan(self, trans_id, parent_id):
 
799
        """Schedule an item to be orphaned.
 
800
 
 
801
        When a directory is about to be removed, its children, if they are not
 
802
        versioned are moved out of the way: they don't have a parent anymore.
 
803
 
 
804
        :param trans_id: The trans_id of the existing item.
 
805
        :param parent_id: The parent trans_id of the item.
 
806
        """
 
807
        raise NotImplementedError(self.new_orphan)
 
808
 
 
809
    def _get_potential_orphans(self, dir_id):
 
810
        """Find the potential orphans in a directory.
 
811
 
 
812
        A directory can't be safely deleted if there are versioned files in it.
 
813
        If all the contained files are unversioned then they can be orphaned.
 
814
 
 
815
        The 'None' return value means that the directory contains at least one
 
816
        versioned file and should not be deleted.
 
817
 
 
818
        :param dir_id: The directory trans id.
 
819
 
 
820
        :return: A list of the orphan trans ids or None if at least one
 
821
             versioned file is present.
 
822
        """
 
823
        orphans = []
 
824
        # Find the potential orphans, stop if one item should be kept
 
825
        for child_tid in self.by_parent()[dir_id]:
 
826
            if child_tid in self._removed_contents:
 
827
                # The child is removed as part of the transform. Since it was
 
828
                # versioned before, it's not an orphan
 
829
                continue
 
830
            elif self.final_file_id(child_tid) is None:
 
831
                # The child is not versioned
 
832
                orphans.append(child_tid)
 
833
            else:
 
834
                # We have a versioned file here, searching for orphans is
 
835
                # meaningless.
 
836
                orphans = None
 
837
                break
 
838
        return orphans
 
839
 
762
840
    def _affected_ids(self):
763
841
        """Return the set of transform ids affected by the transform"""
764
842
        trans_ids = set(self._removed_id)
1170
1248
            descendants.update(self._limbo_descendants(descendant))
1171
1249
        return descendants
1172
1250
 
1173
 
    def create_file(self, contents, trans_id, mode_id=None):
 
1251
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1174
1252
        """Schedule creation of a new file.
1175
1253
 
1176
 
        See also new_file.
1177
 
 
1178
 
        Contents is an iterator of strings, all of which will be written
1179
 
        to the target destination.
1180
 
 
1181
 
        New file takes the permissions of any existing file with that id,
1182
 
        unless mode_id is specified.
 
1254
        :seealso: new_file.
 
1255
 
 
1256
        :param contents: an iterator of strings, all of which will be written
 
1257
            to the target destination.
 
1258
        :param trans_id: TreeTransform handle
 
1259
        :param mode_id: If not None, force the mode of the target file to match
 
1260
            the mode of the object referenced by mode_id.
 
1261
            Otherwise, we will try to preserve mode bits of an existing file.
 
1262
        :param sha1: If the sha1 of this content is already known, pass it in.
 
1263
            We can use it to prevent future sha1 computations.
1183
1264
        """
1184
1265
        name = self._limbo_name(trans_id)
1185
1266
        f = open(name, 'wb')
1192
1273
                f.close()
1193
1274
                os.unlink(name)
1194
1275
                raise
1195
 
 
1196
1276
            f.writelines(contents)
1197
1277
        finally:
1198
1278
            f.close()
1199
1279
        self._set_mtime(name)
1200
1280
        self._set_mode(trans_id, mode_id, S_ISREG)
 
1281
        # It is unfortunate we have to use lstat instead of fstat, but we just
 
1282
        # used utime and chmod on the file, so we need the accurate final
 
1283
        # details.
 
1284
        if sha1 is not None:
 
1285
            self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1201
1286
 
1202
1287
    def _read_file_chunks(self, trans_id):
1203
1288
        cur_file = open(self._limbo_name(trans_id), 'rb')
1262
1347
    def cancel_creation(self, trans_id):
1263
1348
        """Cancel the creation of new file contents."""
1264
1349
        del self._new_contents[trans_id]
 
1350
        if trans_id in self._observed_sha1s:
 
1351
            del self._observed_sha1s[trans_id]
1265
1352
        children = self._limbo_children.get(trans_id)
1266
1353
        # if this is a limbo directory with children, move them before removing
1267
1354
        # the directory
1271
1358
            del self._limbo_children_names[trans_id]
1272
1359
        delete_any(self._limbo_name(trans_id))
1273
1360
 
 
1361
    def new_orphan(self, trans_id, parent_id):
 
1362
        # FIXME: There is no tree config, so we use the branch one (it's weird
 
1363
        # to define it this way as orphaning can only occur in a working tree,
 
1364
        # but that's all we have (for now). It will find the option in
 
1365
        # locations.conf or bazaar.conf though) -- vila 20100916
 
1366
        conf = self._tree.branch.get_config()
 
1367
        conf_var_name = 'bzr.transform.orphan_policy'
 
1368
        orphan_policy = conf.get_user_option(conf_var_name)
 
1369
        default_policy = orphaning_registry.default_key
 
1370
        if orphan_policy is None:
 
1371
            orphan_policy = default_policy
 
1372
        if orphan_policy not in orphaning_registry:
 
1373
            trace.warning('%s (from %s) is not a known policy, defaulting '
 
1374
                'to %s' % (orphan_policy, conf_var_name, default_policy))
 
1375
            orphan_policy = default_policy
 
1376
        handle_orphan = orphaning_registry.get(orphan_policy)
 
1377
        handle_orphan(self, trans_id, parent_id)
 
1378
 
 
1379
 
 
1380
class OrphaningError(errors.BzrError):
 
1381
 
 
1382
    # Only bugs could lead to such exception being seen by the user
 
1383
    internal_error = True
 
1384
    _fmt = "Error while orphaning %s in %s directory"
 
1385
 
 
1386
    def __init__(self, orphan, parent):
 
1387
        errors.BzrError.__init__(self)
 
1388
        self.orphan = orphan
 
1389
        self.parent = parent
 
1390
 
 
1391
 
 
1392
class OrphaningForbidden(OrphaningError):
 
1393
 
 
1394
    _fmt = "Policy: %s doesn't allow creating orphans."
 
1395
 
 
1396
    def __init__(self, policy):
 
1397
        errors.BzrError.__init__(self)
 
1398
        self.policy = policy
 
1399
 
 
1400
 
 
1401
def move_orphan(tt, orphan_id, parent_id):
 
1402
    """See TreeTransformBase.new_orphan.
 
1403
 
 
1404
    This creates a new orphan in the `bzr-orphans` dir at the root of the
 
1405
    `TreeTransform`.
 
1406
 
 
1407
    :param tt: The TreeTransform orphaning `trans_id`.
 
1408
 
 
1409
    :param orphan_id: The trans id that should be orphaned.
 
1410
 
 
1411
    :param parent_id: The orphan parent trans id.
 
1412
    """
 
1413
    # Add the orphan dir if it doesn't exist
 
1414
    orphan_dir_basename = 'bzr-orphans'
 
1415
    od_id = tt.trans_id_tree_path(orphan_dir_basename)
 
1416
    if tt.final_kind(od_id) is None:
 
1417
        tt.create_directory(od_id)
 
1418
    parent_path = tt._tree_id_paths[parent_id]
 
1419
    # Find a name that doesn't exist yet in the orphan dir
 
1420
    actual_name = tt.final_name(orphan_id)
 
1421
    new_name = tt._available_backup_name(actual_name, od_id)
 
1422
    tt.adjust_path(new_name, od_id, orphan_id)
 
1423
    trace.warning('%s has been orphaned in %s'
 
1424
                  % (joinpath(parent_path, actual_name), orphan_dir_basename))
 
1425
 
 
1426
 
 
1427
def refuse_orphan(tt, orphan_id, parent_id):
 
1428
    """See TreeTransformBase.new_orphan.
 
1429
 
 
1430
    This refuses to create orphan, letting the caller handle the conflict.
 
1431
    """
 
1432
    raise OrphaningForbidden('never')
 
1433
 
 
1434
 
 
1435
orphaning_registry = registry.Registry()
 
1436
orphaning_registry.register(
 
1437
    'conflict', refuse_orphan,
 
1438
    'Leave orphans in place and create a conflict on the directory.')
 
1439
orphaning_registry.register(
 
1440
    'move', move_orphan,
 
1441
    'Move orphans into the bzr-orphans directory.')
 
1442
orphaning_registry._set_default_key('conflict')
 
1443
 
1274
1444
 
1275
1445
class TreeTransform(DiskTreeTransform):
1276
1446
    """Represent a tree transformation.
1514
1684
        """
1515
1685
        if not no_conflicts:
1516
1686
            self._check_malformed()
1517
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1687
        child_pb = ui.ui_factory.nested_progress_bar()
1518
1688
        try:
1519
1689
            if precomputed_delta is None:
1520
1690
                child_pb.update('Apply phase', 0, 2)
1540
1710
        finally:
1541
1711
            child_pb.finished()
1542
1712
        self._tree.apply_inventory_delta(inventory_delta)
 
1713
        self._apply_observed_sha1s()
1543
1714
        self._done = True
1544
1715
        self.finalize()
1545
1716
        return _TransformResults(modified_paths, self.rename_count)
1547
1718
    def _generate_inventory_delta(self):
1548
1719
        """Generate an inventory delta for the current transform."""
1549
1720
        inventory_delta = []
1550
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1721
        child_pb = ui.ui_factory.nested_progress_bar()
1551
1722
        new_paths = self._inventory_altered()
1552
1723
        total_entries = len(new_paths) + len(self._removed_id)
1553
1724
        try:
1615
1786
        """
1616
1787
        tree_paths = list(self._tree_path_ids.iteritems())
1617
1788
        tree_paths.sort(reverse=True)
1618
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1789
        child_pb = ui.ui_factory.nested_progress_bar()
1619
1790
        try:
1620
1791
            for num, data in enumerate(tree_paths):
1621
1792
                path, trans_id = data
1650
1821
        modified_paths = []
1651
1822
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1652
1823
                                 new_paths)
1653
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1824
        child_pb = ui.ui_factory.nested_progress_bar()
1654
1825
        try:
1655
1826
            for num, (path, trans_id) in enumerate(new_paths):
1656
1827
                if (num % 10) == 0:
1665
1836
                            raise
1666
1837
                    else:
1667
1838
                        self.rename_count += 1
 
1839
                    # TODO: if trans_id in self._observed_sha1s, we should
 
1840
                    #       re-stat the final target, since ctime will be
 
1841
                    #       updated by the change.
1668
1842
                if (trans_id in self._new_contents or
1669
1843
                    self.path_changed(trans_id)):
1670
1844
                    if trans_id in self._new_contents:
1671
1845
                        modified_paths.append(full_path)
1672
1846
                if trans_id in self._new_executability:
1673
1847
                    self._set_executability(path, trans_id)
 
1848
                if trans_id in self._observed_sha1s:
 
1849
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
 
1850
                    st = osutils.lstat(full_path)
 
1851
                    self._observed_sha1s[trans_id] = (o_sha1, st)
1674
1852
        finally:
1675
1853
            child_pb.finished()
1676
1854
        self._new_contents.clear()
1677
1855
        return modified_paths
1678
1856
 
 
1857
    def _apply_observed_sha1s(self):
 
1858
        """After we have finished renaming everything, update observed sha1s
 
1859
 
 
1860
        This has to be done after self._tree.apply_inventory_delta, otherwise
 
1861
        it doesn't know anything about the files we are updating. Also, we want
 
1862
        to do this as late as possible, so that most entries end up cached.
 
1863
        """
 
1864
        # TODO: this doesn't update the stat information for directories. So
 
1865
        #       the first 'bzr status' will still need to rewrite
 
1866
        #       .bzr/checkout/dirstate. However, we at least don't need to
 
1867
        #       re-read all of the files.
 
1868
        # TODO: If the operation took a while, we could do a time.sleep(3) here
 
1869
        #       to allow the clock to tick over and ensure we won't have any
 
1870
        #       problems. (we could observe start time, and finish time, and if
 
1871
        #       it is less than eg 10% overhead, add a sleep call.)
 
1872
        paths = FinalPaths(self)
 
1873
        for trans_id, observed in self._observed_sha1s.iteritems():
 
1874
            path = paths.get_path(trans_id)
 
1875
            # We could get the file_id, but dirstate prefers to use the path
 
1876
            # anyway, and it is 'cheaper' to determine.
 
1877
            # file_id = self._new_id[trans_id]
 
1878
            self._tree._observed_sha1(None, path, observed)
 
1879
 
1679
1880
 
1680
1881
class TransformPreview(DiskTreeTransform):
1681
1882
    """A TreeTransform for generating preview trees.
1697
1898
        path = self._tree_id_paths.get(trans_id)
1698
1899
        if path is None:
1699
1900
            return None
1700
 
        file_id = self._tree.path2id(path)
1701
 
        try:
1702
 
            return self._tree.kind(file_id)
1703
 
        except errors.NoSuchFile:
1704
 
            return None
 
1901
        kind = self._tree.path_content_summary(path)[0]
 
1902
        if kind == 'missing':
 
1903
            kind = None
 
1904
        return kind
1705
1905
 
1706
1906
    def _set_mode(self, trans_id, mode_id, typefunc):
1707
1907
        """Set the mode of new file contents.
1727
1927
            childpath = joinpath(path, child)
1728
1928
            yield self.trans_id_tree_path(childpath)
1729
1929
 
1730
 
 
1731
 
class _PreviewTree(tree.Tree):
 
1930
    def new_orphan(self, trans_id, parent_id):
 
1931
        raise NotImplementedError(self.new_orphan)
 
1932
 
 
1933
 
 
1934
class _PreviewTree(tree.InventoryTree):
1732
1935
    """Partial implementation of Tree to support show_diff_trees"""
1733
1936
 
1734
1937
    def __init__(self, transform):
1763
1966
                yield self._get_repository().revision_tree(revision_id)
1764
1967
 
1765
1968
    def _get_file_revision(self, file_id, vf, tree_revision):
1766
 
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
 
1969
        parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
1767
1970
                       self._iter_parent_trees()]
1768
1971
        vf.add_lines((file_id, tree_revision), parent_keys,
1769
1972
                     self.get_file_lines(file_id))
1773
1976
            vf.fallback_versionedfiles.append(base_vf)
1774
1977
        return tree_revision
1775
1978
 
1776
 
    def _stat_limbo_file(self, file_id):
1777
 
        trans_id = self._transform.trans_id_file_id(file_id)
 
1979
    def _stat_limbo_file(self, file_id=None, trans_id=None):
 
1980
        if trans_id is None:
 
1981
            trans_id = self._transform.trans_id_file_id(file_id)
1778
1982
        name = self._transform._limbo_name(trans_id)
1779
1983
        return os.lstat(name)
1780
1984
 
1995
2199
 
1996
2200
    def get_file_size(self, file_id):
1997
2201
        """See Tree.get_file_size"""
 
2202
        trans_id = self._transform.trans_id_file_id(file_id)
 
2203
        kind = self._transform.final_kind(trans_id)
 
2204
        if kind != 'file':
 
2205
            return None
 
2206
        if trans_id in self._transform._new_contents:
 
2207
            return self._stat_limbo_file(trans_id=trans_id).st_size
1998
2208
        if self.kind(file_id) == 'file':
1999
2209
            return self._transform._tree.get_file_size(file_id)
2000
2210
        else:
2028
2238
            except errors.NoSuchId:
2029
2239
                return False
2030
2240
 
 
2241
    def has_filename(self, path):
 
2242
        trans_id = self._path2trans_id(path)
 
2243
        if trans_id in self._transform._new_contents:
 
2244
            return True
 
2245
        elif trans_id in self._transform._removed_contents:
 
2246
            return False
 
2247
        else:
 
2248
            return self._transform._tree.has_filename(path)
 
2249
 
2031
2250
    def path_content_summary(self, path):
2032
2251
        trans_id = self._path2trans_id(path)
2033
2252
        tt = self._transform
2265
2484
        if num > 0:  # more than just a root
2266
2485
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2267
2486
    file_trans_id = {}
2268
 
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2487
    top_pb = ui.ui_factory.nested_progress_bar()
2269
2488
    pp = ProgressPhase("Build phase", 2, top_pb)
2270
2489
    if tree.inventory.root is not None:
2271
2490
        # This is kind of a hack: we should be altering the root
2284
2503
        pp.next_phase()
2285
2504
        file_trans_id[wt.get_root_id()] = \
2286
2505
            tt.trans_id_tree_file_id(wt.get_root_id())
2287
 
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2506
        pb = ui.ui_factory.nested_progress_bar()
2288
2507
        try:
2289
2508
            deferred_contents = []
2290
2509
            num = 0
2337
2556
                    executable = tree.is_executable(file_id, tree_path)
2338
2557
                    if executable:
2339
2558
                        tt.set_executability(executable, trans_id)
2340
 
                    trans_data = (trans_id, tree_path)
 
2559
                    trans_data = (trans_id, tree_path, entry.text_sha1)
2341
2560
                    deferred_contents.append((file_id, trans_data))
2342
2561
                else:
2343
2562
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2359
2578
            precomputed_delta = None
2360
2579
        conflicts = cook_conflicts(raw_conflicts, tt)
2361
2580
        for conflict in conflicts:
2362
 
            warning(conflict)
 
2581
            trace.warning(conflict)
2363
2582
        try:
2364
2583
            wt.add_conflicts(conflicts)
2365
2584
        except errors.UnsupportedOperation:
2388
2607
        unchanged = dict(unchanged)
2389
2608
        new_desired_files = []
2390
2609
        count = 0
2391
 
        for file_id, (trans_id, tree_path) in desired_files:
 
2610
        for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2392
2611
            accelerator_path = unchanged.get(file_id)
2393
2612
            if accelerator_path is None:
2394
 
                new_desired_files.append((file_id, (trans_id, tree_path)))
 
2613
                new_desired_files.append((file_id,
 
2614
                    (trans_id, tree_path, text_sha1)))
2395
2615
                continue
2396
2616
            pb.update('Adding file contents', count + offset, total)
2397
2617
            if hardlink:
2404
2624
                    contents = filtered_output_bytes(contents, filters,
2405
2625
                        ContentFilterContext(tree_path, tree))
2406
2626
                try:
2407
 
                    tt.create_file(contents, trans_id)
 
2627
                    tt.create_file(contents, trans_id, sha1=text_sha1)
2408
2628
                finally:
2409
2629
                    try:
2410
2630
                        contents.close()
2413
2633
                        pass
2414
2634
            count += 1
2415
2635
        offset += count
2416
 
    for count, ((trans_id, tree_path), contents) in enumerate(
 
2636
    for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2417
2637
            tree.iter_files_bytes(new_desired_files)):
2418
2638
        if wt.supports_content_filtering():
2419
2639
            filters = wt._content_filter_stack(tree_path)
2420
2640
            contents = filtered_output_bytes(contents, filters,
2421
2641
                ContentFilterContext(tree_path, tree))
2422
 
        tt.create_file(contents, trans_id)
 
2642
        tt.create_file(contents, trans_id, sha1=text_sha1)
2423
2643
        pb.update('Adding file contents', count + offset, total)
2424
2644
 
2425
2645
 
2427
2647
    for child in tt.iter_tree_children(old_parent):
2428
2648
        tt.adjust_path(tt.final_name(child), new_parent, child)
2429
2649
 
 
2650
 
2430
2651
def _reparent_transform_children(tt, old_parent, new_parent):
2431
2652
    by_parent = tt.by_parent()
2432
2653
    for child in by_parent[old_parent]:
2433
2654
        tt.adjust_path(tt.final_name(child), new_parent, child)
2434
2655
    return by_parent[old_parent]
2435
2656
 
 
2657
 
2436
2658
def _content_match(tree, entry, file_id, kind, target_path):
2437
2659
    if entry.kind != kind:
2438
2660
        return False
2538
2760
        tt.set_executability(entry.executable, trans_id)
2539
2761
 
2540
2762
 
 
2763
@deprecated_function(deprecated_in((2, 3, 0)))
2541
2764
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2542
2765
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2543
2766
 
2544
2767
 
 
2768
@deprecated_function(deprecated_in((2, 3, 0)))
2545
2769
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2546
2770
    """Produce a backup-style name that appears to be available"""
2547
2771
    def name_gen():
2596
2820
                unversioned_filter=working_tree.is_ignored)
2597
2821
            delta.report_changes(tt.iter_changes(), change_reporter)
2598
2822
        for conflict in conflicts:
2599
 
            warning(conflict)
 
2823
            trace.warning(conflict)
2600
2824
        pp.next_phase()
2601
2825
        tt.apply()
2602
2826
        working_tree.set_merge_modified(merge_modified)
2610
2834
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2611
2835
                              backups, pp, basis_tree=None,
2612
2836
                              merge_modified=None):
2613
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2837
    child_pb = ui.ui_factory.nested_progress_bar()
2614
2838
    try:
2615
2839
        if merge_modified is None:
2616
2840
            merge_modified = working_tree.merge_modified()
2619
2843
                                      merge_modified, basis_tree)
2620
2844
    finally:
2621
2845
        child_pb.finished()
2622
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2846
    child_pb = ui.ui_factory.nested_progress_bar()
2623
2847
    try:
2624
2848
        raw_conflicts = resolve_conflicts(tt, child_pb,
2625
2849
            lambda t, c: conflict_pass(t, c, target_tree))
2668
2892
                        tt.delete_contents(trans_id)
2669
2893
                    elif kind[1] is not None:
2670
2894
                        parent_trans_id = tt.trans_id_file_id(parent[0])
2671
 
                        by_parent = tt.by_parent()
2672
 
                        backup_name = _get_backup_name(name[0], by_parent,
2673
 
                                                       parent_trans_id, tt)
 
2895
                        backup_name = tt._available_backup_name(
 
2896
                            name[0], parent_trans_id)
2674
2897
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
2675
2898
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
2676
2899
                        if versioned == (True, True):
2799
3022
 
2800
3023
        elif c_type == 'missing parent':
2801
3024
            trans_id = conflict[1]
2802
 
            try:
2803
 
                tt.cancel_deletion(trans_id)
2804
 
                new_conflicts.add(('deleting parent', 'Not deleting',
2805
 
                                   trans_id))
2806
 
            except KeyError:
 
3025
            if trans_id in tt._removed_contents:
 
3026
                cancel_deletion = True
 
3027
                orphans = tt._get_potential_orphans(trans_id)
 
3028
                if orphans:
 
3029
                    cancel_deletion = False
 
3030
                    # All children are orphans
 
3031
                    for o in orphans:
 
3032
                        try:
 
3033
                            tt.new_orphan(o, trans_id)
 
3034
                        except OrphaningError:
 
3035
                            # Something bad happened so we cancel the directory
 
3036
                            # deletion which will leave it in place with a
 
3037
                            # conflict. The user can deal with it from there.
 
3038
                            # Note that this also catch the case where we don't
 
3039
                            # want to create orphans and leave the directory in
 
3040
                            # place.
 
3041
                            cancel_deletion = True
 
3042
                            break
 
3043
                if cancel_deletion:
 
3044
                    # Cancel the directory deletion
 
3045
                    tt.cancel_deletion(trans_id)
 
3046
                    new_conflicts.add(('deleting parent', 'Not deleting',
 
3047
                                       trans_id))
 
3048
            else:
2807
3049
                create = True
2808
3050
                try:
2809
3051
                    tt.final_name(trans_id)
2812
3054
                        file_id = tt.final_file_id(trans_id)
2813
3055
                        if file_id is None:
2814
3056
                            file_id = tt.inactive_file_id(trans_id)
2815
 
                        entry = path_tree.inventory[file_id]
 
3057
                        _, entry = path_tree.iter_entries_by_dir(
 
3058
                            [file_id]).next()
2816
3059
                        # special-case the other tree root (move its
2817
3060
                        # children to current root)
2818
3061
                        if entry.parent_id is None:
2834
3077
            file_id = tt.inactive_file_id(conflict[1])
2835
3078
            # special-case the other tree root (move its children instead)
2836
3079
            if path_tree and file_id in path_tree:
2837
 
                if path_tree.inventory[file_id].parent_id is None:
 
3080
                if path_tree.path2id('') == file_id:
 
3081
                    # This is the root entry, skip it
2838
3082
                    continue
2839
3083
            tt.version_file(file_id, conflict[1])
2840
3084
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
2917
3161
            try:
2918
3162
                os.rename(to, from_)
2919
3163
            except OSError, e:
2920
 
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)                
 
3164
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
2921
3165
        # after rollback, don't reuse _FileMover
2922
3166
        past_renames = None
2923
3167
        pending_deletions = None