/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Jonathan Lange
  • Date: 2009-12-09 09:20:42 UTC
  • mfrom: (4881 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4907.
  • Revision ID: jml@canonical.com-20091209092042-s2zgqcf8f39yzxpj
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009 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
84
84
 
85
85
        :param tree: The tree that will be transformed, but not necessarily
86
86
            the output tree.
87
 
        :param pb: A ProgressBar indicating how much progress is being made
 
87
        :param pb: A ProgressTask indicating how much progress is being made
88
88
        :param case_sensitive: If True, the target of the transform is
89
89
            case sensitive, not just case preserving.
90
90
        """
442
442
        conflicts.extend(self._overwrite_conflicts())
443
443
        return conflicts
444
444
 
 
445
    def _check_malformed(self):
 
446
        conflicts = self.find_conflicts()
 
447
        if len(conflicts) != 0:
 
448
            raise MalformedTransform(conflicts=conflicts)
 
449
 
445
450
    def _add_tree_children(self):
446
451
        """Add all the children of all active parents to the known paths.
447
452
 
854
859
    def get_preview_tree(self):
855
860
        """Return a tree representing the result of the transform.
856
861
 
857
 
        This tree only supports the subset of Tree functionality required
858
 
        by show_diff_trees.  It must only be compared to tt._tree.
 
862
        The tree is a snapshot, and altering the TreeTransform will invalidate
 
863
        it.
859
864
        """
860
865
        return _PreviewTree(self)
861
866
 
 
867
    def commit(self, branch, message, merge_parents=None, strict=False):
 
868
        """Commit the result of this TreeTransform to a branch.
 
869
 
 
870
        :param branch: The branch to commit to.
 
871
        :param message: The message to attach to the commit.
 
872
        :param merge_parents: Additional parents specified by pending merges.
 
873
        :return: The revision_id of the revision committed.
 
874
        """
 
875
        self._check_malformed()
 
876
        if strict:
 
877
            unversioned = set(self._new_contents).difference(set(self._new_id))
 
878
            for trans_id in unversioned:
 
879
                if self.final_file_id(trans_id) is None:
 
880
                    raise errors.StrictCommitFailed()
 
881
 
 
882
        revno, last_rev_id = branch.last_revision_info()
 
883
        if last_rev_id == _mod_revision.NULL_REVISION:
 
884
            if merge_parents is not None:
 
885
                raise ValueError('Cannot supply merge parents for first'
 
886
                                 ' commit.')
 
887
            parent_ids = []
 
888
        else:
 
889
            parent_ids = [last_rev_id]
 
890
            if merge_parents is not None:
 
891
                parent_ids.extend(merge_parents)
 
892
        if self._tree.get_revision_id() != last_rev_id:
 
893
            raise ValueError('TreeTransform not based on branch basis: %s' %
 
894
                             self._tree.get_revision_id())
 
895
        builder = branch.get_commit_builder(parent_ids)
 
896
        preview = self.get_preview_tree()
 
897
        list(builder.record_iter_changes(preview, last_rev_id,
 
898
                                         self.iter_changes()))
 
899
        builder.finish_inventory()
 
900
        revision_id = builder.commit(message)
 
901
        branch.set_last_revision_info(revno + 1, revision_id)
 
902
        return revision_id
 
903
 
862
904
    def _text_parent(self, trans_id):
863
905
        file_id = self.tree_file_id(trans_id)
864
906
        try:
1012
1054
    def _limbo_name(self, trans_id):
1013
1055
        """Generate the limbo name of a file"""
1014
1056
        limbo_name = self._limbo_files.get(trans_id)
1015
 
        if limbo_name is not None:
1016
 
            return limbo_name
1017
 
        parent = self._new_parent.get(trans_id)
1018
 
        # if the parent directory is already in limbo (e.g. when building a
1019
 
        # tree), choose a limbo name inside the parent, to reduce further
1020
 
        # renames.
1021
 
        use_direct_path = False
1022
 
        if self._new_contents.get(parent) == 'directory':
1023
 
            filename = self._new_name.get(trans_id)
1024
 
            if filename is not None:
1025
 
                if parent not in self._limbo_children:
1026
 
                    self._limbo_children[parent] = set()
1027
 
                    self._limbo_children_names[parent] = {}
1028
 
                    use_direct_path = True
1029
 
                # the direct path can only be used if no other file has
1030
 
                # already taken this pathname, i.e. if the name is unused, or
1031
 
                # if it is already associated with this trans_id.
1032
 
                elif self._case_sensitive_target:
1033
 
                    if (self._limbo_children_names[parent].get(filename)
1034
 
                        in (trans_id, None)):
1035
 
                        use_direct_path = True
1036
 
                else:
1037
 
                    for l_filename, l_trans_id in\
1038
 
                        self._limbo_children_names[parent].iteritems():
1039
 
                        if l_trans_id == trans_id:
1040
 
                            continue
1041
 
                        if l_filename.lower() == filename.lower():
1042
 
                            break
1043
 
                    else:
1044
 
                        use_direct_path = True
1045
 
 
1046
 
        if use_direct_path:
1047
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
1048
 
            self._limbo_children[parent].add(trans_id)
1049
 
            self._limbo_children_names[parent][filename] = trans_id
1050
 
        else:
1051
 
            limbo_name = pathjoin(self._limbodir, trans_id)
1052
 
            self._needs_rename.add(trans_id)
1053
 
        self._limbo_files[trans_id] = limbo_name
 
1057
        if limbo_name is None:
 
1058
            limbo_name = self._generate_limbo_path(trans_id)
 
1059
            self._limbo_files[trans_id] = limbo_name
1054
1060
        return limbo_name
1055
1061
 
 
1062
    def _generate_limbo_path(self, trans_id):
 
1063
        """Generate a limbo path using the trans_id as the relative path.
 
1064
 
 
1065
        This is suitable as a fallback, and when the transform should not be
 
1066
        sensitive to the path encoding of the limbo directory.
 
1067
        """
 
1068
        self._needs_rename.add(trans_id)
 
1069
        return pathjoin(self._limbodir, trans_id)
 
1070
 
1056
1071
    def adjust_path(self, name, parent, trans_id):
1057
1072
        previous_parent = self._new_parent.get(trans_id)
1058
1073
        previous_name = self._new_name.get(trans_id)
1080
1095
                continue
1081
1096
            new_path = self._limbo_name(trans_id)
1082
1097
            os.rename(old_path, new_path)
 
1098
            for descendant in self._limbo_descendants(trans_id):
 
1099
                desc_path = self._limbo_files[descendant]
 
1100
                desc_path = new_path + desc_path[len(old_path):]
 
1101
                self._limbo_files[descendant] = desc_path
 
1102
 
 
1103
    def _limbo_descendants(self, trans_id):
 
1104
        """Return the set of trans_ids whose limbo paths descend from this."""
 
1105
        descendants = set(self._limbo_children.get(trans_id, []))
 
1106
        for descendant in list(descendants):
 
1107
            descendants.update(self._limbo_descendants(descendant))
 
1108
        return descendants
1083
1109
 
1084
1110
    def create_file(self, contents, trans_id, mode_id=None):
1085
1111
        """Schedule creation of a new file.
1354
1380
                continue
1355
1381
            yield self.trans_id_tree_path(childpath)
1356
1382
 
 
1383
    def _generate_limbo_path(self, trans_id):
 
1384
        """Generate a limbo path using the final path if possible.
 
1385
 
 
1386
        This optimizes the performance of applying the tree transform by
 
1387
        avoiding renames.  These renames can be avoided only when the parent
 
1388
        directory is already scheduled for creation.
 
1389
 
 
1390
        If the final path cannot be used, falls back to using the trans_id as
 
1391
        the relpath.
 
1392
        """
 
1393
        parent = self._new_parent.get(trans_id)
 
1394
        # if the parent directory is already in limbo (e.g. when building a
 
1395
        # tree), choose a limbo name inside the parent, to reduce further
 
1396
        # renames.
 
1397
        use_direct_path = False
 
1398
        if self._new_contents.get(parent) == 'directory':
 
1399
            filename = self._new_name.get(trans_id)
 
1400
            if filename is not None:
 
1401
                if parent not in self._limbo_children:
 
1402
                    self._limbo_children[parent] = set()
 
1403
                    self._limbo_children_names[parent] = {}
 
1404
                    use_direct_path = True
 
1405
                # the direct path can only be used if no other file has
 
1406
                # already taken this pathname, i.e. if the name is unused, or
 
1407
                # if it is already associated with this trans_id.
 
1408
                elif self._case_sensitive_target:
 
1409
                    if (self._limbo_children_names[parent].get(filename)
 
1410
                        in (trans_id, None)):
 
1411
                        use_direct_path = True
 
1412
                else:
 
1413
                    for l_filename, l_trans_id in\
 
1414
                        self._limbo_children_names[parent].iteritems():
 
1415
                        if l_trans_id == trans_id:
 
1416
                            continue
 
1417
                        if l_filename.lower() == filename.lower():
 
1418
                            break
 
1419
                    else:
 
1420
                        use_direct_path = True
 
1421
 
 
1422
        if not use_direct_path:
 
1423
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
 
1424
 
 
1425
        limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1426
        self._limbo_children[parent].add(trans_id)
 
1427
        self._limbo_children_names[parent][filename] = trans_id
 
1428
        return limbo_name
 
1429
 
1357
1430
 
1358
1431
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1359
1432
        """Apply all changes to the inventory and filesystem.
1370
1443
        :param _mover: Supply an alternate FileMover, for testing
1371
1444
        """
1372
1445
        if not no_conflicts:
1373
 
            conflicts = self.find_conflicts()
1374
 
            if len(conflicts) != 0:
1375
 
                raise MalformedTransform(conflicts=conflicts)
 
1446
            self._check_malformed()
1376
1447
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1377
1448
        try:
1378
1449
            if precomputed_delta is None:
1596
1667
        self._all_children_cache = {}
1597
1668
        self._path2trans_id_cache = {}
1598
1669
        self._final_name_cache = {}
1599
 
 
1600
 
    def _changes(self, file_id):
1601
 
        for changes in self._transform.iter_changes():
1602
 
            if changes[0] == file_id:
1603
 
                return changes
 
1670
        self._iter_changes_cache = dict((c[0], c) for c in
 
1671
                                        self._transform.iter_changes())
1604
1672
 
1605
1673
    def _content_change(self, file_id):
1606
1674
        """Return True if the content of this file changed"""
1607
 
        changes = self._changes(file_id)
 
1675
        changes = self._iter_changes_cache.get(file_id)
1608
1676
        # changes[2] is true if the file content changed.  See
1609
1677
        # InterTree.iter_changes.
1610
1678
        return (changes is not None and changes[2])
1679
1747
    def __iter__(self):
1680
1748
        return iter(self.all_file_ids())
1681
1749
 
1682
 
    def has_id(self, file_id):
 
1750
    def _has_id(self, file_id, fallback_check):
1683
1751
        if file_id in self._transform._r_new_id:
1684
1752
            return True
1685
1753
        elif file_id in set([self._transform.tree_file_id(trans_id) for
1686
1754
            trans_id in self._transform._removed_id]):
1687
1755
            return False
1688
1756
        else:
1689
 
            return self._transform._tree.has_id(file_id)
 
1757
            return fallback_check(file_id)
 
1758
 
 
1759
    def has_id(self, file_id):
 
1760
        return self._has_id(file_id, self._transform._tree.has_id)
 
1761
 
 
1762
    def has_or_had_id(self, file_id):
 
1763
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
1690
1764
 
1691
1765
    def _path2trans_id(self, path):
1692
1766
        # We must not use None here, because that is a valid value to store.
1745
1819
            if self._transform.final_file_id(trans_id) is None:
1746
1820
                yield self._final_paths._determine_path(trans_id)
1747
1821
 
1748
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
 
1822
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
1823
        yield_parents=False):
1749
1824
        for trans_id, parent_file_id in ordered_entries:
1750
1825
            file_id = self._transform.final_file_id(trans_id)
1751
1826
            if file_id is None:
1777
1852
                ordered_ids.append((trans_id, parent_file_id))
1778
1853
        return ordered_ids
1779
1854
 
1780
 
    def iter_entries_by_dir(self, specific_file_ids=None):
 
1855
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1781
1856
        # This may not be a maximally efficient implementation, but it is
1782
1857
        # reasonably straightforward.  An implementation that grafts the
1783
1858
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1785
1860
        # position.
1786
1861
        ordered_ids = self._list_files_by_dir()
1787
1862
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1788
 
                                                      specific_file_ids):
 
1863
            specific_file_ids, yield_parents=yield_parents):
1789
1864
            yield unicode(self._final_paths.get_path(trans_id)), entry
1790
1865
 
1791
1866
    def _iter_entries_for_dir(self, dir_path):
1838
1913
    def get_file_mtime(self, file_id, path=None):
1839
1914
        """See Tree.get_file_mtime"""
1840
1915
        if not self._content_change(file_id):
1841
 
            return self._transform._tree.get_file_mtime(file_id, path)
 
1916
            return self._transform._tree.get_file_mtime(file_id)
1842
1917
        return self._stat_limbo_file(file_id).st_mtime
1843
1918
 
1844
1919
    def _file_size(self, entry, stat_value):
1898
1973
                statval = os.lstat(limbo_name)
1899
1974
                size = statval.st_size
1900
1975
                if not supports_executable():
1901
 
                    executable = None
 
1976
                    executable = False
1902
1977
                else:
1903
1978
                    executable = statval.st_mode & S_IEXEC
1904
1979
            else:
1906
1981
                executable = None
1907
1982
            if kind == 'symlink':
1908
1983
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1909
 
        if supports_executable():
1910
 
            executable = tt._new_executability.get(trans_id, executable)
 
1984
        executable = tt._new_executability.get(trans_id, executable)
1911
1985
        return kind, size, executable, link_or_sha1
1912
1986
 
1913
1987
    def iter_changes(self, from_tree, include_unchanged=False,
1944
2018
 
1945
2019
    def annotate_iter(self, file_id,
1946
2020
                      default_revision=_mod_revision.CURRENT_REVISION):
1947
 
        changes = self._changes(file_id)
 
2021
        changes = self._iter_changes_cache.get(file_id)
1948
2022
        if changes is None:
1949
2023
            get_old = True
1950
2024
        else:
1962
2036
            return old_annotation
1963
2037
        if not changed_content:
1964
2038
            return old_annotation
 
2039
        # TODO: This is doing something similar to what WT.annotate_iter is
 
2040
        #       doing, however it fails slightly because it doesn't know what
 
2041
        #       the *other* revision_id is, so it doesn't know how to give the
 
2042
        #       other as the origin for some lines, they all get
 
2043
        #       'default_revision'
 
2044
        #       It would be nice to be able to use the new Annotator based
 
2045
        #       approach, as well.
1965
2046
        return annotate.reannotate([old_annotation],
1966
2047
                                   self.get_file(file_id).readlines(),
1967
2048
                                   default_revision)
2033
2114
        self.transform = transform
2034
2115
 
2035
2116
    def _determine_path(self, trans_id):
2036
 
        if trans_id == self.transform.root:
 
2117
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2037
2118
            return ""
2038
2119
        name = self.transform.final_name(trans_id)
2039
2120
        parent_id = self.transform.final_parent(trans_id)
2219
2300
        new_desired_files = desired_files
2220
2301
    else:
2221
2302
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2222
 
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2223
 
                         in iter if not (c or e[0] != e[1]))
 
2303
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
 
2304
                     in iter if not (c or e[0] != e[1])]
 
2305
        if accelerator_tree.supports_content_filtering():
 
2306
            unchanged = [(f, p) for (f, p) in unchanged
 
2307
                         if not accelerator_tree.iter_search_rules([p]).next()]
 
2308
        unchanged = dict(unchanged)
2224
2309
        new_desired_files = []
2225
2310
        count = 0
2226
2311
        for file_id, (trans_id, tree_path) in desired_files:
2349
2434
        tt.create_directory(trans_id)
2350
2435
 
2351
2436
 
2352
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2353
 
    """Create new file contents according to tree contents."""
 
2437
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2438
    filter_tree_path=None):
 
2439
    """Create new file contents according to tree contents.
 
2440
    
 
2441
    :param filter_tree_path: the tree path to use to lookup
 
2442
      content filters to apply to the bytes output in the working tree.
 
2443
      This only applies if the working tree supports content filtering.
 
2444
    """
2354
2445
    kind = tree.kind(file_id)
2355
2446
    if kind == 'directory':
2356
2447
        tt.create_directory(trans_id)
2361
2452
                bytes = tree_file.readlines()
2362
2453
            finally:
2363
2454
                tree_file.close()
 
2455
        wt = tt._tree
 
2456
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2457
            filters = wt._content_filter_stack(filter_tree_path)
 
2458
            bytes = filtered_output_bytes(bytes, filters,
 
2459
                ContentFilterContext(filter_tree_path, tree))
2364
2460
        tt.create_file(bytes, trans_id)
2365
2461
    elif kind == "symlink":
2366
2462
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2557
2653
                tt.adjust_path(name[1], parent_trans, trans_id)
2558
2654
            if executable[0] != executable[1] and kind[1] == "file":
2559
2655
                tt.set_executability(executable[1], trans_id)
2560
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2561
 
            deferred_files):
2562
 
            tt.create_file(bytes, trans_id, mode_id)
 
2656
        if working_tree.supports_content_filtering():
 
2657
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
2658
                target_tree.iter_files_bytes(deferred_files)):
 
2659
                file_id = deferred_files[index][0]
 
2660
                # We're reverting a tree to the target tree so using the
 
2661
                # target tree to find the file path seems the best choice
 
2662
                # here IMO - Ian C 27/Oct/2009
 
2663
                filter_tree_path = target_tree.id2path(file_id)
 
2664
                filters = working_tree._content_filter_stack(filter_tree_path)
 
2665
                bytes = filtered_output_bytes(bytes, filters,
 
2666
                    ContentFilterContext(filter_tree_path, working_tree))
 
2667
                tt.create_file(bytes, trans_id, mode_id)
 
2668
        else:
 
2669
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
2670
                deferred_files):
 
2671
                tt.create_file(bytes, trans_id, mode_id)
2563
2672
    finally:
2564
2673
        if basis_tree is not None:
2565
2674
            basis_tree.unlock()