~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-20 15:06:17 UTC
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110420150617-i41caxgemg32tq1r
Start adding tests that _worth_saving_limit works as expected.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import os
20
18
import errno
21
19
from stat import S_ISREG, S_IEXEC
22
20
import time
23
21
 
24
22
from bzrlib import (
25
 
    config as _mod_config,
26
23
    errors,
27
24
    lazy_import,
28
25
    registry,
33
30
from bzrlib import (
34
31
    annotate,
35
32
    bencode,
36
 
    controldir,
 
33
    bzrdir,
37
34
    commit,
38
 
    conflicts,
39
35
    delta,
 
36
    errors,
40
37
    inventory,
41
38
    multiparent,
42
39
    osutils,
44
41
    ui,
45
42
    urlutils,
46
43
    )
47
 
from bzrlib.i18n import gettext
48
44
""")
49
 
from bzrlib.errors import (DuplicateKey, MalformedTransform,
 
45
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
50
46
                           ReusingTransform, CantMoveRoot,
51
 
                           ImmortalLimbo, NoFinalPath,
 
47
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
52
48
                           UnableCreateSymlink)
53
49
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
54
 
from bzrlib.mutabletree import MutableTree
55
50
from bzrlib.osutils import (
56
51
    delete_any,
57
52
    file_kind,
59
54
    pathjoin,
60
55
    sha_file,
61
56
    splitpath,
 
57
    supports_executable,
62
58
    )
63
59
from bzrlib.progress import ProgressPhase
64
60
from bzrlib.symbol_versioning import (
141
137
        # A counter of how many files have been renamed
142
138
        self.rename_count = 0
143
139
 
144
 
    def __enter__(self):
145
 
        """Support Context Manager API."""
146
 
        return self
147
 
 
148
 
    def __exit__(self, exc_type, exc_val, exc_tb):
149
 
        """Support Context Manager API."""
150
 
        self.finalize()
151
 
 
152
140
    def finalize(self):
153
141
        """Release the working tree lock, if held.
154
142
 
157
145
        """
158
146
        if self._tree is None:
159
147
            return
160
 
        for hook in MutableTree.hooks['post_transform']:
161
 
            hook(self._tree, self)
162
148
        self._tree.unlock()
163
149
        self._tree = None
164
150
 
231
217
        This means that the old root trans-id becomes obsolete, so it is
232
218
        recommended only to invoke this after the root trans-id has become
233
219
        irrelevant.
234
 
 
235
220
        """
236
 
        new_roots = [k for k, v in self._new_parent.iteritems() if v ==
 
221
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
237
222
                     ROOT_PARENT]
238
223
        if len(new_roots) < 1:
239
224
            return
243
228
            self._new_root = new_roots[0]
244
229
            return
245
230
        old_new_root = new_roots[0]
 
231
        # TODO: What to do if a old_new_root is present, but self._new_root is
 
232
        #       not listed as being removed? This code explicitly unversions
 
233
        #       the old root and versions it with the new file_id. Though that
 
234
        #       seems like an incomplete delta
 
235
 
246
236
        # unversion the new root's directory.
247
 
        if self.final_kind(self._new_root) is None:
248
 
            file_id = self.final_file_id(old_new_root)
249
 
        else:
250
 
            file_id = self.final_file_id(self._new_root)
 
237
        file_id = self.final_file_id(old_new_root)
251
238
        if old_new_root in self._new_id:
252
239
            self.cancel_versioning(old_new_root)
253
240
        else:
257
244
        if (self.tree_file_id(self._new_root) is not None and
258
245
            self._new_root not in self._removed_id):
259
246
            self.unversion_file(self._new_root)
260
 
        if file_id is not None:
261
 
            self.version_file(file_id, self._new_root)
 
247
        self.version_file(file_id, self._new_root)
262
248
 
263
249
        # Now move children of new root into old root directory.
264
250
        # Ensure all children are registered with the transaction, but don't
398
384
        return sorted(FinalPaths(self).get_paths(new_ids))
399
385
 
400
386
    def _inventory_altered(self):
401
 
        """Determine which trans_ids need new Inventory entries.
402
 
 
403
 
        An new entry is needed when anything that would be reflected by an
404
 
        inventory entry changes, including file name, file_id, parent file_id,
405
 
        file kind, and the execute bit.
406
 
 
407
 
        Some care is taken to return entries with real changes, not cases
408
 
        where the value is deleted and then restored to its original value,
409
 
        but some actually unchanged values may be returned.
410
 
 
411
 
        :returns: A list of (path, trans_id) for all items requiring an
412
 
            inventory change. Ordered by path.
413
 
        """
414
 
        changed_ids = set()
415
 
        # Find entries whose file_ids are new (or changed).
416
 
        new_file_id = set(t for t in self._new_id
417
 
                          if self._new_id[t] != self.tree_file_id(t))
418
 
        for id_set in [self._new_name, self._new_parent, new_file_id,
 
387
        """Get the trans_ids and paths of files needing new inv entries."""
 
388
        new_ids = set()
 
389
        for id_set in [self._new_name, self._new_parent, self._new_id,
419
390
                       self._new_executability]:
420
 
            changed_ids.update(id_set)
421
 
        # removing implies a kind change
 
391
            new_ids.update(id_set)
422
392
        changed_kind = set(self._removed_contents)
423
 
        # so does adding
424
393
        changed_kind.intersection_update(self._new_contents)
425
 
        # Ignore entries that are already known to have changed.
426
 
        changed_kind.difference_update(changed_ids)
427
 
        #  to keep only the truly changed ones
 
394
        changed_kind.difference_update(new_ids)
428
395
        changed_kind = (t for t in changed_kind
429
396
                        if self.tree_kind(t) != self.final_kind(t))
430
 
        # all kind changes will alter the inventory
431
 
        changed_ids.update(changed_kind)
432
 
        # To find entries with changed parent_ids, find parents which existed,
433
 
        # but changed file_id.
434
 
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
435
 
        # Now add all their children to the set.
436
 
        for parent_trans_id in new_file_id:
437
 
            changed_ids.update(self.iter_tree_children(parent_trans_id))
438
 
        return sorted(FinalPaths(self).get_paths(changed_ids))
 
397
        new_ids.update(changed_kind)
 
398
        return sorted(FinalPaths(self).get_paths(new_ids))
439
399
 
440
400
    def final_kind(self, trans_id):
441
401
        """Determine the final file kind, after any changes applied.
566
526
        for trans_id in self._removed_id:
567
527
            file_id = self.tree_file_id(trans_id)
568
528
            if file_id is not None:
569
 
                if self._tree.stored_kind(file_id) == 'directory':
 
529
                # XXX: This seems like something that should go via a different
 
530
                #      indirection.
 
531
                if self._tree.inventory[file_id].kind == 'directory':
570
532
                    parents.append(trans_id)
571
533
            elif self.tree_kind(trans_id) == 'directory':
572
534
                parents.append(trans_id)
575
537
            # ensure that all children are registered with the transaction
576
538
            list(self.iter_tree_children(parent_id))
577
539
 
 
540
    @deprecated_method(deprecated_in((2, 3, 0)))
 
541
    def has_named_child(self, by_parent, parent_id, name):
 
542
        return self._has_named_child(
 
543
            name, parent_id, known_children=by_parent.get(parent_id, []))
 
544
 
578
545
    def _has_named_child(self, name, parent_id, known_children):
579
546
        """Does a parent already have a name child.
580
547
 
624
591
        for trans_id in self._new_parent:
625
592
            seen = set()
626
593
            parent_id = trans_id
627
 
            while parent_id != ROOT_PARENT:
 
594
            while parent_id is not ROOT_PARENT:
628
595
                seen.add(parent_id)
629
596
                try:
630
597
                    parent_id = self.final_parent(parent_id)
640
607
        """If parent directories are versioned, children must be versioned."""
641
608
        conflicts = []
642
609
        for parent_id, children in by_parent.iteritems():
643
 
            if parent_id == ROOT_PARENT:
 
610
            if parent_id is ROOT_PARENT:
644
611
                continue
645
612
            if self.final_file_id(parent_id) is not None:
646
613
                continue
739
706
        """Children must have a directory parent"""
740
707
        conflicts = []
741
708
        for parent_id, children in by_parent.iteritems():
742
 
            if parent_id == ROOT_PARENT:
 
709
            if parent_id is ROOT_PARENT:
743
710
                continue
744
711
            no_children = True
745
712
            for child_id in children:
761
728
 
762
729
    def _set_executability(self, path, trans_id):
763
730
        """Set the executability of versioned files """
764
 
        if self._tree._supports_executable():
 
731
        if supports_executable():
765
732
            new_executability = self._new_executability[trans_id]
766
733
            abspath = self._tree.abspath(path)
767
734
            current_mode = os.stat(abspath).st_mode
776
743
                    to_mode |= 0010 & ~umask
777
744
            else:
778
745
                to_mode = current_mode & ~0111
779
 
            osutils.chmod_if_possible(abspath, to_mode)
 
746
            os.chmod(abspath, to_mode)
780
747
 
781
748
    def _new_entry(self, name, parent_id, file_id):
782
749
        """Helper function to create a new filesystem entry."""
1187
1154
        self._deletiondir = None
1188
1155
        # A mapping of transform ids to their limbo filename
1189
1156
        self._limbo_files = {}
1190
 
        self._possibly_stale_limbo_files = set()
1191
1157
        # A mapping of transform ids to a set of the transform ids of children
1192
1158
        # that their limbo directory has
1193
1159
        self._limbo_children = {}
1206
1172
        if self._tree is None:
1207
1173
            return
1208
1174
        try:
1209
 
            limbo_paths = self._limbo_files.values() + list(
1210
 
                self._possibly_stale_limbo_files)
1211
 
            limbo_paths = sorted(limbo_paths, reverse=True)
1212
 
            for path in limbo_paths:
1213
 
                try:
1214
 
                    delete_any(path)
1215
 
                except OSError, e:
1216
 
                    if e.errno != errno.ENOENT:
1217
 
                        raise
1218
 
                    # XXX: warn? perhaps we just got interrupted at an
1219
 
                    # inconvenient moment, but perhaps files are disappearing
1220
 
                    # from under us?
 
1175
            entries = [(self._limbo_name(t), t, k) for t, k in
 
1176
                       self._new_contents.iteritems()]
 
1177
            entries.sort(reverse=True)
 
1178
            for path, trans_id, kind in entries:
 
1179
                delete_any(path)
1221
1180
            try:
1222
1181
                delete_any(self._limbodir)
1223
1182
            except OSError:
1231
1190
        finally:
1232
1191
            TreeTransformBase.finalize(self)
1233
1192
 
1234
 
    def _limbo_supports_executable(self):
1235
 
        """Check if the limbo path supports the executable bit."""
1236
 
        # FIXME: Check actual file system capabilities of limbodir
1237
 
        return osutils.supports_executable()
1238
 
 
1239
1193
    def _limbo_name(self, trans_id):
1240
1194
        """Generate the limbo name of a file"""
1241
1195
        limbo_name = self._limbo_files.get(trans_id)
1277
1231
        entries from _limbo_files, because they are now stale.
1278
1232
        """
1279
1233
        for trans_id in trans_ids:
1280
 
            old_path = self._limbo_files[trans_id]
1281
 
            self._possibly_stale_limbo_files.add(old_path)
1282
 
            del self._limbo_files[trans_id]
 
1234
            old_path = self._limbo_files.pop(trans_id)
1283
1235
            if trans_id not in self._new_contents:
1284
1236
                continue
1285
1237
            new_path = self._limbo_name(trans_id)
1286
1238
            os.rename(old_path, new_path)
1287
 
            self._possibly_stale_limbo_files.remove(old_path)
1288
1239
            for descendant in self._limbo_descendants(trans_id):
1289
1240
                desc_path = self._limbo_files[descendant]
1290
1241
                desc_path = new_path + desc_path[len(old_path):]
1314
1265
        name = self._limbo_name(trans_id)
1315
1266
        f = open(name, 'wb')
1316
1267
        try:
1317
 
            unique_add(self._new_contents, trans_id, 'file')
 
1268
            try:
 
1269
                unique_add(self._new_contents, trans_id, 'file')
 
1270
            except:
 
1271
                # Clean up the file, it never got registered so
 
1272
                # TreeTransform.finalize() won't clean it up.
 
1273
                f.close()
 
1274
                os.unlink(name)
 
1275
                raise
1318
1276
            f.writelines(contents)
1319
1277
        finally:
1320
1278
            f.close()
1401
1359
        delete_any(self._limbo_name(trans_id))
1402
1360
 
1403
1361
    def new_orphan(self, trans_id, parent_id):
1404
 
        conf = self._tree.get_config_stack()
1405
 
        handle_orphan = conf.get('bzr.transform.orphan_policy')
 
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)
1406
1377
        handle_orphan(self, trans_id, parent_id)
1407
1378
 
1408
1379
 
1471
1442
orphaning_registry._set_default_key('conflict')
1472
1443
 
1473
1444
 
1474
 
opt_transform_orphan = _mod_config.RegistryOption(
1475
 
    'bzr.transform.orphan_policy', orphaning_registry,
1476
 
    help='Policy for orphaned files during transform operations.',
1477
 
    invalid='warning')
1478
 
 
1479
 
 
1480
1445
class TreeTransform(DiskTreeTransform):
1481
1446
    """Represent a tree transformation.
1482
1447
 
1553
1518
        try:
1554
1519
            limbodir = urlutils.local_path_from_url(
1555
1520
                tree._transport.abspath('limbo'))
1556
 
            osutils.ensure_empty_directory_exists(
1557
 
                limbodir,
1558
 
                errors.ExistingLimbo)
 
1521
            try:
 
1522
                os.mkdir(limbodir)
 
1523
            except OSError, e:
 
1524
                if e.errno == errno.EEXIST:
 
1525
                    raise ExistingLimbo(limbodir)
1559
1526
            deletiondir = urlutils.local_path_from_url(
1560
1527
                tree._transport.abspath('pending-deletion'))
1561
 
            osutils.ensure_empty_directory_exists(
1562
 
                deletiondir,
1563
 
                errors.ExistingPendingDeletion)
 
1528
            try:
 
1529
                os.mkdir(deletiondir)
 
1530
            except OSError, e:
 
1531
                if e.errno == errno.EEXIST:
 
1532
                    raise errors.ExistingPendingDeletion(deletiondir)
1564
1533
        except:
1565
1534
            tree.unlock()
1566
1535
            raise
1629
1598
            else:
1630
1599
                raise
1631
1600
        if typefunc(mode):
1632
 
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
 
1601
            os.chmod(self._limbo_name(trans_id), mode)
1633
1602
 
1634
1603
    def iter_tree_children(self, parent_id):
1635
1604
        """Iterate through the entry's tree children, if any"""
1713
1682
            calculating one.
1714
1683
        :param _mover: Supply an alternate FileMover, for testing
1715
1684
        """
1716
 
        for hook in MutableTree.hooks['pre_transform']:
1717
 
            hook(self._tree, self)
1718
1685
        if not no_conflicts:
1719
1686
            self._check_malformed()
1720
1687
        child_pb = ui.ui_factory.nested_progress_bar()
1721
1688
        try:
1722
1689
            if precomputed_delta is None:
1723
 
                child_pb.update(gettext('Apply phase'), 0, 2)
 
1690
                child_pb.update('Apply phase', 0, 2)
1724
1691
                inventory_delta = self._generate_inventory_delta()
1725
1692
                offset = 1
1726
1693
            else:
1731
1698
            else:
1732
1699
                mover = _mover
1733
1700
            try:
1734
 
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
 
1701
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
1735
1702
                self._apply_removals(mover)
1736
 
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
 
1703
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
1737
1704
                modified_paths = self._apply_insertions(mover)
1738
1705
            except:
1739
1706
                mover.rollback()
1742
1709
                mover.apply_deletions()
1743
1710
        finally:
1744
1711
            child_pb.finished()
1745
 
        if self.final_file_id(self.root) is None:
1746
 
            inventory_delta = [e for e in inventory_delta if e[0] != '']
1747
1712
        self._tree.apply_inventory_delta(inventory_delta)
1748
1713
        self._apply_observed_sha1s()
1749
1714
        self._done = True
1759
1724
        try:
1760
1725
            for num, trans_id in enumerate(self._removed_id):
1761
1726
                if (num % 10) == 0:
1762
 
                    child_pb.update(gettext('removing file'), num, total_entries)
 
1727
                    child_pb.update('removing file', num, total_entries)
1763
1728
                if trans_id == self._new_root:
1764
1729
                    file_id = self._tree.get_root_id()
1765
1730
                else:
1777
1742
            final_kinds = {}
1778
1743
            for num, (path, trans_id) in enumerate(new_paths):
1779
1744
                if (num % 10) == 0:
1780
 
                    child_pb.update(gettext('adding file'),
 
1745
                    child_pb.update('adding file',
1781
1746
                                    num + len(self._removed_id), total_entries)
1782
1747
                file_id = new_path_file_ids[trans_id]
1783
1748
                if file_id is None:
1823
1788
        tree_paths.sort(reverse=True)
1824
1789
        child_pb = ui.ui_factory.nested_progress_bar()
1825
1790
        try:
1826
 
            for num, (path, trans_id) in enumerate(tree_paths):
1827
 
                # do not attempt to move root into a subdirectory of itself.
1828
 
                if path == '':
1829
 
                    continue
1830
 
                child_pb.update(gettext('removing file'), num, len(tree_paths))
 
1791
            for num, data in enumerate(tree_paths):
 
1792
                path, trans_id = data
 
1793
                child_pb.update('removing file', num, len(tree_paths))
1831
1794
                full_path = self._tree.abspath(path)
1832
1795
                if trans_id in self._removed_contents:
1833
1796
                    delete_path = os.path.join(self._deletiondir, trans_id)
1862
1825
        try:
1863
1826
            for num, (path, trans_id) in enumerate(new_paths):
1864
1827
                if (num % 10) == 0:
1865
 
                    child_pb.update(gettext('adding file'), num, len(new_paths))
 
1828
                    child_pb.update('adding file', num, len(new_paths))
1866
1829
                full_path = self._tree.abspath(path)
1867
1830
                if trans_id in self._needs_rename:
1868
1831
                    try:
1888
1851
                    self._observed_sha1s[trans_id] = (o_sha1, st)
1889
1852
        finally:
1890
1853
            child_pb.finished()
1891
 
        for path, trans_id in new_paths:
1892
 
            # new_paths includes stuff like workingtree conflicts. Only the
1893
 
            # stuff in new_contents actually comes from limbo.
1894
 
            if trans_id in self._limbo_files:
1895
 
                del self._limbo_files[trans_id]
1896
1854
        self._new_contents.clear()
1897
1855
        return modified_paths
1898
1856
 
1940
1898
        path = self._tree_id_paths.get(trans_id)
1941
1899
        if path is None:
1942
1900
            return None
1943
 
        kind = self._tree.path_content_summary(path)[0]
1944
 
        if kind == 'missing':
1945
 
            kind = None
1946
 
        return kind
 
1901
        file_id = self._tree.path2id(path)
 
1902
        try:
 
1903
            return self._tree.kind(file_id)
 
1904
        except errors.NoSuchFile:
 
1905
            return None
1947
1906
 
1948
1907
    def _set_mode(self, trans_id, mode_id, typefunc):
1949
1908
        """Set the mode of new file contents.
2018
1977
            vf.fallback_versionedfiles.append(base_vf)
2019
1978
        return tree_revision
2020
1979
 
2021
 
    def _stat_limbo_file(self, file_id=None, trans_id=None):
2022
 
        if trans_id is None:
2023
 
            trans_id = self._transform.trans_id_file_id(file_id)
 
1980
    def _stat_limbo_file(self, file_id):
 
1981
        trans_id = self._transform.trans_id_file_id(file_id)
2024
1982
        name = self._transform._limbo_name(trans_id)
2025
1983
        return os.lstat(name)
2026
1984
 
2051
2009
        pass
2052
2010
 
2053
2011
    @property
2054
 
    @deprecated_method(deprecated_in((2, 5, 0)))
2055
2012
    def inventory(self):
2056
2013
        """This Tree does not use inventory as its backing data."""
2057
2014
        raise NotImplementedError(_PreviewTree.inventory)
2058
2015
 
2059
 
    @property
2060
 
    def root_inventory(self):
2061
 
        """This Tree does not use inventory as its backing data."""
2062
 
        raise NotImplementedError(_PreviewTree.root_inventory)
2063
 
 
2064
2016
    def get_root_id(self):
2065
2017
        return self._transform.final_file_id(self._transform.root)
2066
2018
 
2112
2064
        return cur_parent
2113
2065
 
2114
2066
    def path2id(self, path):
2115
 
        if isinstance(path, list):
2116
 
            if path == []:
2117
 
                path = [""]
2118
 
            path = osutils.pathjoin(*path)
2119
2067
        return self._transform.final_file_id(self._path2trans_id(path))
2120
2068
 
2121
2069
    def id2path(self, file_id):
2182
2130
                ordered_ids.append((trans_id, parent_file_id))
2183
2131
        return ordered_ids
2184
2132
 
2185
 
    def iter_child_entries(self, file_id, path=None):
2186
 
        self.id2path(file_id)
2187
 
        trans_id = self._transform.trans_id_file_id(file_id)
2188
 
        todo = [(child_trans_id, trans_id) for child_trans_id in
2189
 
                self._all_children(trans_id)]
2190
 
        for entry, trans_id in self._make_inv_entries(todo):
2191
 
            yield entry
2192
 
 
2193
2133
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2194
2134
        # This may not be a maximally efficient implementation, but it is
2195
2135
        # reasonably straightforward.  An implementation that grafts the
2259
2199
 
2260
2200
    def get_file_size(self, file_id):
2261
2201
        """See Tree.get_file_size"""
2262
 
        trans_id = self._transform.trans_id_file_id(file_id)
2263
 
        kind = self._transform.final_kind(trans_id)
2264
 
        if kind != 'file':
2265
 
            return None
2266
 
        if trans_id in self._transform._new_contents:
2267
 
            return self._stat_limbo_file(trans_id=trans_id).st_size
2268
2202
        if self.kind(file_id) == 'file':
2269
2203
            return self._transform._tree.get_file_size(file_id)
2270
2204
        else:
2271
2205
            return None
2272
2206
 
2273
 
    def get_file_verifier(self, file_id, path=None, stat_value=None):
2274
 
        trans_id = self._transform.trans_id_file_id(file_id)
2275
 
        kind = self._transform._new_contents.get(trans_id)
2276
 
        if kind is None:
2277
 
            return self._transform._tree.get_file_verifier(file_id)
2278
 
        if kind == 'file':
2279
 
            fileobj = self.get_file(file_id)
2280
 
            try:
2281
 
                return ("SHA1", sha_file(fileobj))
2282
 
            finally:
2283
 
                fileobj.close()
2284
 
 
2285
2207
    def get_file_sha1(self, file_id, path=None, stat_value=None):
2286
2208
        trans_id = self._transform.trans_id_file_id(file_id)
2287
2209
        kind = self._transform._new_contents.get(trans_id)
2310
2232
            except errors.NoSuchId:
2311
2233
                return False
2312
2234
 
2313
 
    def has_filename(self, path):
2314
 
        trans_id = self._path2trans_id(path)
2315
 
        if trans_id in self._transform._new_contents:
2316
 
            return True
2317
 
        elif trans_id in self._transform._removed_contents:
2318
 
            return False
2319
 
        else:
2320
 
            return self._transform._tree.has_filename(path)
2321
 
 
2322
2235
    def path_content_summary(self, path):
2323
2236
        trans_id = self._path2trans_id(path)
2324
2237
        tt = self._transform
2337
2250
            if kind == 'file':
2338
2251
                statval = os.lstat(limbo_name)
2339
2252
                size = statval.st_size
2340
 
                if not tt._limbo_supports_executable():
 
2253
                if not supports_executable():
2341
2254
                    executable = False
2342
2255
                else:
2343
2256
                    executable = statval.st_mode & S_IEXEC
2412
2325
                                   self.get_file(file_id).readlines(),
2413
2326
                                   default_revision)
2414
2327
 
2415
 
    def get_symlink_target(self, file_id, path=None):
 
2328
    def get_symlink_target(self, file_id):
2416
2329
        """See Tree.get_symlink_target"""
2417
2330
        if not self._content_change(file_id):
2418
2331
            return self._transform._tree.get_symlink_target(file_id)
2558
2471
    file_trans_id = {}
2559
2472
    top_pb = ui.ui_factory.nested_progress_bar()
2560
2473
    pp = ProgressPhase("Build phase", 2, top_pb)
2561
 
    if tree.get_root_id() is not None:
 
2474
    if tree.inventory.root is not None:
2562
2475
        # This is kind of a hack: we should be altering the root
2563
2476
        # as part of the regular tree shape diff logic.
2564
2477
        # The conditional test here is to avoid doing an
2579
2492
        try:
2580
2493
            deferred_contents = []
2581
2494
            num = 0
2582
 
            total = len(tree.all_file_ids())
 
2495
            total = len(tree.inventory)
2583
2496
            if delta_from_tree:
2584
2497
                precomputed_delta = []
2585
2498
            else:
2594
2507
                for dir, files in wt.walkdirs():
2595
2508
                    existing_files.update(f[0] for f in files)
2596
2509
            for num, (tree_path, entry) in \
2597
 
                enumerate(tree.iter_entries_by_dir()):
2598
 
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
 
2510
                enumerate(tree.inventory.iter_entries_by_dir()):
 
2511
                pb.update("Building tree", num - len(deferred_contents), total)
2599
2512
                if entry.parent_id is None:
2600
2513
                    continue
2601
2514
                reparent = False
2607
2520
                    kind = file_kind(target_path)
2608
2521
                    if kind == "directory":
2609
2522
                        try:
2610
 
                            controldir.ControlDir.open(target_path)
 
2523
                            bzrdir.BzrDir.open(target_path)
2611
2524
                        except errors.NotBranchError:
2612
2525
                            pass
2613
2526
                        else:
2650
2563
            precomputed_delta = None
2651
2564
        conflicts = cook_conflicts(raw_conflicts, tt)
2652
2565
        for conflict in conflicts:
2653
 
            trace.warning(unicode(conflict))
 
2566
            trace.warning(conflict)
2654
2567
        try:
2655
2568
            wt.add_conflicts(conflicts)
2656
2569
        except errors.UnsupportedOperation:
2685
2598
                new_desired_files.append((file_id,
2686
2599
                    (trans_id, tree_path, text_sha1)))
2687
2600
                continue
2688
 
            pb.update(gettext('Adding file contents'), count + offset, total)
 
2601
            pb.update('Adding file contents', count + offset, total)
2689
2602
            if hardlink:
2690
2603
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2691
2604
                                   trans_id)
2712
2625
            contents = filtered_output_bytes(contents, filters,
2713
2626
                ContentFilterContext(tree_path, tree))
2714
2627
        tt.create_file(contents, trans_id, sha1=text_sha1)
2715
 
        pb.update(gettext('Adding file contents'), count + offset, total)
 
2628
        pb.update('Adding file contents', count + offset, total)
2716
2629
 
2717
2630
 
2718
2631
def _reparent_children(tt, old_parent, new_parent):
2832
2745
        tt.set_executability(entry.executable, trans_id)
2833
2746
 
2834
2747
 
 
2748
@deprecated_function(deprecated_in((2, 3, 0)))
 
2749
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
2750
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
2751
 
 
2752
 
 
2753
@deprecated_function(deprecated_in((2, 3, 0)))
 
2754
def _get_backup_name(name, by_parent, parent_trans_id, tt):
 
2755
    """Produce a backup-style name that appears to be available"""
 
2756
    def name_gen():
 
2757
        counter = 1
 
2758
        while True:
 
2759
            yield "%s.~%d~" % (name, counter)
 
2760
            counter += 1
 
2761
    for new_name in name_gen():
 
2762
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
2763
            return new_name
 
2764
 
 
2765
 
 
2766
def _entry_changes(file_id, entry, working_tree):
 
2767
    """Determine in which ways the inventory entry has changed.
 
2768
 
 
2769
    Returns booleans: has_contents, content_mod, meta_mod
 
2770
    has_contents means there are currently contents, but they differ
 
2771
    contents_mod means contents need to be modified
 
2772
    meta_mod means the metadata needs to be modified
 
2773
    """
 
2774
    cur_entry = working_tree.inventory[file_id]
 
2775
    try:
 
2776
        working_kind = working_tree.kind(file_id)
 
2777
        has_contents = True
 
2778
    except NoSuchFile:
 
2779
        has_contents = False
 
2780
        contents_mod = True
 
2781
        meta_mod = False
 
2782
    if has_contents is True:
 
2783
        if entry.kind != working_kind:
 
2784
            contents_mod, meta_mod = True, False
 
2785
        else:
 
2786
            cur_entry._read_tree_state(working_tree.id2path(file_id),
 
2787
                                       working_tree)
 
2788
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
 
2789
            cur_entry._forget_tree_state()
 
2790
    return has_contents, contents_mod, meta_mod
 
2791
 
 
2792
 
2835
2793
def revert(working_tree, target_tree, filenames, backups=False,
2836
2794
           pb=None, change_reporter=None):
2837
2795
    """Revert a working tree's contents to those of a target tree."""
2847
2805
                unversioned_filter=working_tree.is_ignored)
2848
2806
            delta.report_changes(tt.iter_changes(), change_reporter)
2849
2807
        for conflict in conflicts:
2850
 
            trace.warning(unicode(conflict))
 
2808
            trace.warning(conflict)
2851
2809
        pp.next_phase()
2852
2810
        tt.apply()
2853
2811
        working_tree.set_merge_modified(merge_modified)
2884
2842
                 backups, merge_modified, basis_tree=None):
2885
2843
    if basis_tree is not None:
2886
2844
        basis_tree.lock_read()
2887
 
    # We ask the working_tree for its changes relative to the target, rather
2888
 
    # than the target changes relative to the working tree. Because WT4 has an
2889
 
    # optimizer to compare itself to a target, but no optimizer for the
2890
 
    # reverse.
2891
 
    change_list = working_tree.iter_changes(target_tree,
 
2845
    change_list = target_tree.iter_changes(working_tree,
2892
2846
        specific_files=specific_files, pb=pb)
2893
2847
    if target_tree.get_root_id() is None:
2894
2848
        skip_root = True
2898
2852
        deferred_files = []
2899
2853
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2900
2854
                kind, executable) in enumerate(change_list):
2901
 
            target_path, wt_path = path
2902
 
            target_versioned, wt_versioned = versioned
2903
 
            target_parent, wt_parent = parent
2904
 
            target_name, wt_name = name
2905
 
            target_kind, wt_kind = kind
2906
 
            target_executable, wt_executable = executable
2907
 
            if skip_root and wt_parent is None:
 
2855
            if skip_root and file_id[0] is not None and parent[0] is None:
2908
2856
                continue
2909
2857
            trans_id = tt.trans_id_file_id(file_id)
2910
2858
            mode_id = None
2911
2859
            if changed_content:
2912
2860
                keep_content = False
2913
 
                if wt_kind == 'file' and (backups or target_kind is None):
 
2861
                if kind[0] == 'file' and (backups or kind[1] is None):
2914
2862
                    wt_sha1 = working_tree.get_file_sha1(file_id)
2915
2863
                    if merge_modified.get(file_id) != wt_sha1:
2916
2864
                        # acquire the basis tree lazily to prevent the
2919
2867
                        if basis_tree is None:
2920
2868
                            basis_tree = working_tree.basis_tree()
2921
2869
                            basis_tree.lock_read()
2922
 
                        if basis_tree.has_id(file_id):
 
2870
                        if file_id in basis_tree:
2923
2871
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
2924
2872
                                keep_content = True
2925
 
                        elif target_kind is None and not target_versioned:
 
2873
                        elif kind[1] is None and not versioned[1]:
2926
2874
                            keep_content = True
2927
 
                if wt_kind is not None:
 
2875
                if kind[0] is not None:
2928
2876
                    if not keep_content:
2929
2877
                        tt.delete_contents(trans_id)
2930
 
                    elif target_kind is not None:
2931
 
                        parent_trans_id = tt.trans_id_file_id(wt_parent)
 
2878
                    elif kind[1] is not None:
 
2879
                        parent_trans_id = tt.trans_id_file_id(parent[0])
2932
2880
                        backup_name = tt._available_backup_name(
2933
 
                            wt_name, parent_trans_id)
 
2881
                            name[0], parent_trans_id)
2934
2882
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
2935
 
                        new_trans_id = tt.create_path(wt_name, parent_trans_id)
2936
 
                        if wt_versioned and target_versioned:
 
2883
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
 
2884
                        if versioned == (True, True):
2937
2885
                            tt.unversion_file(trans_id)
2938
2886
                            tt.version_file(file_id, new_trans_id)
2939
2887
                        # New contents should have the same unix perms as old
2940
2888
                        # contents
2941
2889
                        mode_id = trans_id
2942
2890
                        trans_id = new_trans_id
2943
 
                if target_kind in ('directory', 'tree-reference'):
 
2891
                if kind[1] in ('directory', 'tree-reference'):
2944
2892
                    tt.create_directory(trans_id)
2945
 
                    if target_kind == 'tree-reference':
 
2893
                    if kind[1] == 'tree-reference':
2946
2894
                        revision = target_tree.get_reference_revision(file_id,
2947
 
                                                                      target_path)
 
2895
                                                                      path[1])
2948
2896
                        tt.set_tree_reference(revision, trans_id)
2949
 
                elif target_kind == 'symlink':
 
2897
                elif kind[1] == 'symlink':
2950
2898
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
2951
2899
                                      trans_id)
2952
 
                elif target_kind == 'file':
 
2900
                elif kind[1] == 'file':
2953
2901
                    deferred_files.append((file_id, (trans_id, mode_id)))
2954
2902
                    if basis_tree is None:
2955
2903
                        basis_tree = working_tree.basis_tree()
2956
2904
                        basis_tree.lock_read()
2957
2905
                    new_sha1 = target_tree.get_file_sha1(file_id)
2958
 
                    if (basis_tree.has_id(file_id) and
2959
 
                        new_sha1 == basis_tree.get_file_sha1(file_id)):
 
2906
                    if (file_id in basis_tree and new_sha1 ==
 
2907
                        basis_tree.get_file_sha1(file_id)):
2960
2908
                        if file_id in merge_modified:
2961
2909
                            del merge_modified[file_id]
2962
2910
                    else:
2963
2911
                        merge_modified[file_id] = new_sha1
2964
2912
 
2965
2913
                    # preserve the execute bit when backing up
2966
 
                    if keep_content and wt_executable == target_executable:
2967
 
                        tt.set_executability(target_executable, trans_id)
2968
 
                elif target_kind is not None:
2969
 
                    raise AssertionError(target_kind)
2970
 
            if not wt_versioned and target_versioned:
 
2914
                    if keep_content and executable[0] == executable[1]:
 
2915
                        tt.set_executability(executable[1], trans_id)
 
2916
                elif kind[1] is not None:
 
2917
                    raise AssertionError(kind[1])
 
2918
            if versioned == (False, True):
2971
2919
                tt.version_file(file_id, trans_id)
2972
 
            if wt_versioned and not target_versioned:
 
2920
            if versioned == (True, False):
2973
2921
                tt.unversion_file(trans_id)
2974
 
            if (target_name is not None and
2975
 
                (wt_name != target_name or wt_parent != target_parent)):
2976
 
                if target_name == '' and target_parent is None:
 
2922
            if (name[1] is not None and
 
2923
                (name[0] != name[1] or parent[0] != parent[1])):
 
2924
                if name[1] == '' and parent[1] is None:
2977
2925
                    parent_trans = ROOT_PARENT
2978
2926
                else:
2979
 
                    parent_trans = tt.trans_id_file_id(target_parent)
2980
 
                if wt_parent is None and wt_versioned:
2981
 
                    tt.adjust_root_path(target_name, parent_trans)
 
2927
                    parent_trans = tt.trans_id_file_id(parent[1])
 
2928
                if parent[0] is None and versioned[0]:
 
2929
                    tt.adjust_root_path(name[1], parent_trans)
2982
2930
                else:
2983
 
                    tt.adjust_path(target_name, parent_trans, trans_id)
2984
 
            if wt_executable != target_executable and target_kind == "file":
2985
 
                tt.set_executability(target_executable, trans_id)
 
2931
                    tt.adjust_path(name[1], parent_trans, trans_id)
 
2932
            if executable[0] != executable[1] and kind[1] == "file":
 
2933
                tt.set_executability(executable[1], trans_id)
2986
2934
        if working_tree.supports_content_filtering():
2987
2935
            for index, ((trans_id, mode_id), bytes) in enumerate(
2988
2936
                target_tree.iter_files_bytes(deferred_files)):
3014
2962
    pb = ui.ui_factory.nested_progress_bar()
3015
2963
    try:
3016
2964
        for n in range(10):
3017
 
            pb.update(gettext('Resolution pass'), n+1, 10)
 
2965
            pb.update('Resolution pass', n+1, 10)
3018
2966
            conflicts = tt.find_conflicts()
3019
2967
            if len(conflicts) == 0:
3020
2968
                return new_conflicts
3044
2992
                existing_file, new_file = conflict[2], conflict[1]
3045
2993
            else:
3046
2994
                existing_file, new_file = conflict[1], conflict[2]
3047
 
            new_name = tt.final_name(existing_file) + '.moved'
 
2995
            new_name = tt.final_name(existing_file)+'.moved'
3048
2996
            tt.adjust_path(new_name, final_parent, existing_file)
3049
2997
            new_conflicts.add((c_type, 'Moved existing file to',
3050
2998
                               existing_file, new_file))
3113
3061
        elif c_type == 'unversioned parent':
3114
3062
            file_id = tt.inactive_file_id(conflict[1])
3115
3063
            # special-case the other tree root (move its children instead)
3116
 
            if path_tree and path_tree.has_id(file_id):
 
3064
            if path_tree and file_id in path_tree:
3117
3065
                if path_tree.path2id('') == file_id:
3118
3066
                    # This is the root entry, skip it
3119
3067
                    continue
3137
3085
 
3138
3086
def cook_conflicts(raw_conflicts, tt):
3139
3087
    """Generate a list of cooked conflicts, sorted by file path"""
 
3088
    from bzrlib.conflicts import Conflict
3140
3089
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
3141
 
    return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
 
3090
    return sorted(conflict_iter, key=Conflict.sort_key)
3142
3091
 
3143
3092
 
3144
3093
def iter_cook_conflicts(raw_conflicts, tt):
 
3094
    from bzrlib.conflicts import Conflict
3145
3095
    fp = FinalPaths(tt)
3146
3096
    for conflict in raw_conflicts:
3147
3097
        c_type = conflict[0]
3149
3099
        modified_path = fp.get_path(conflict[2])
3150
3100
        modified_id = tt.final_file_id(conflict[2])
3151
3101
        if len(conflict) == 3:
3152
 
            yield conflicts.Conflict.factory(
3153
 
                c_type, action=action, path=modified_path, file_id=modified_id)
 
3102
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
3103
                                     file_id=modified_id)
3154
3104
 
3155
3105
        else:
3156
3106
            conflicting_path = fp.get_path(conflict[3])
3157
3107
            conflicting_id = tt.final_file_id(conflict[3])
3158
 
            yield conflicts.Conflict.factory(
3159
 
                c_type, action=action, path=modified_path,
3160
 
                file_id=modified_id,
3161
 
                conflict_path=conflicting_path,
3162
 
                conflict_file_id=conflicting_id)
 
3108
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
3109
                                   file_id=modified_id,
 
3110
                                   conflict_path=conflicting_path,
 
3111
                                   conflict_file_id=conflicting_id)
3163
3112
 
3164
3113
 
3165
3114
class _FileMover(object):