/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

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
    annotate,
26
26
    bencode,
27
27
    bzrdir,
 
28
    commit,
28
29
    delta,
29
30
    errors,
30
31
    inventory,
314
315
 
315
316
    def delete_contents(self, trans_id):
316
317
        """Schedule the contents of a path entry for deletion"""
317
 
        # Ensure that the object exists in the WorkingTree, this will raise an
318
 
        # exception if there is a problem
319
 
        self.tree_kind(trans_id)
320
 
        self._removed_contents.add(trans_id)
 
318
        kind = self.tree_kind(trans_id)
 
319
        if kind is not None:
 
320
            self._removed_contents.add(trans_id)
321
321
 
322
322
    def cancel_deletion(self, trans_id):
323
323
        """Cancel a scheduled deletion"""
388
388
        changed_kind = set(self._removed_contents)
389
389
        changed_kind.intersection_update(self._new_contents)
390
390
        changed_kind.difference_update(new_ids)
391
 
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
392
 
                        self.final_kind(t))
 
391
        changed_kind = (t for t in changed_kind
 
392
                        if self.tree_kind(t) != self.final_kind(t))
393
393
        new_ids.update(changed_kind)
394
394
        return sorted(FinalPaths(self).get_paths(new_ids))
395
395
 
396
396
    def final_kind(self, trans_id):
397
397
        """Determine the final file kind, after any changes applied.
398
398
 
399
 
        Raises NoSuchFile if the file does not exist/has no contents.
400
 
        (It is conceivable that a path would be created without the
401
 
        corresponding contents insertion command)
 
399
        :return: None if the file does not exist/has no contents.  (It is
 
400
            conceivable that a path would be created without the corresponding
 
401
            contents insertion command)
402
402
        """
403
403
        if trans_id in self._new_contents:
404
404
            return self._new_contents[trans_id]
405
405
        elif trans_id in self._removed_contents:
406
 
            raise NoSuchFile(None)
 
406
            return None
407
407
        else:
408
408
            return self.tree_kind(trans_id)
409
409
 
595
595
        """
596
596
        conflicts = []
597
597
        for trans_id in self._new_id.iterkeys():
598
 
            try:
599
 
                kind = self.final_kind(trans_id)
600
 
            except NoSuchFile:
 
598
            kind = self.final_kind(trans_id)
 
599
            if kind is None:
601
600
                conflicts.append(('versioning no contents', trans_id))
602
601
                continue
603
602
            if not InventoryEntry.versionable_kind(kind):
617
616
            if self.final_file_id(trans_id) is None:
618
617
                conflicts.append(('unversioned executability', trans_id))
619
618
            else:
620
 
                try:
621
 
                    non_file = self.final_kind(trans_id) != "file"
622
 
                except NoSuchFile:
623
 
                    non_file = True
624
 
                if non_file is True:
 
619
                if self.final_kind(trans_id) != "file":
625
620
                    conflicts.append(('non-file executability', trans_id))
626
621
        return conflicts
627
622
 
629
624
        """Check for overwrites (not permitted on Win32)"""
630
625
        conflicts = []
631
626
        for trans_id in self._new_contents:
632
 
            try:
633
 
                self.tree_kind(trans_id)
634
 
            except NoSuchFile:
 
627
            if self.tree_kind(trans_id) is None:
635
628
                continue
636
629
            if trans_id not in self._removed_contents:
637
630
                conflicts.append(('overwrite', trans_id,
651
644
            last_name = None
652
645
            last_trans_id = None
653
646
            for name, trans_id in name_ids:
654
 
                try:
655
 
                    kind = self.final_kind(trans_id)
656
 
                except NoSuchFile:
657
 
                    kind = None
 
647
                kind = self.final_kind(trans_id)
658
648
                file_id = self.final_file_id(trans_id)
659
649
                if kind is None and file_id is None:
660
650
                    continue
686
676
                continue
687
677
            if not self._any_contents(children):
688
678
                continue
689
 
            for child in children:
690
 
                try:
691
 
                    self.final_kind(child)
692
 
                except NoSuchFile:
693
 
                    continue
694
 
            try:
695
 
                kind = self.final_kind(parent_id)
696
 
            except NoSuchFile:
697
 
                kind = None
 
679
            kind = self.final_kind(parent_id)
698
680
            if kind is None:
699
681
                conflicts.append(('missing parent', parent_id))
700
682
            elif kind != "directory":
704
686
    def _any_contents(self, trans_ids):
705
687
        """Return true if any of the trans_ids, will have contents."""
706
688
        for trans_id in trans_ids:
707
 
            try:
708
 
                kind = self.final_kind(trans_id)
709
 
            except NoSuchFile:
710
 
                continue
711
 
            return True
 
689
            if self.final_kind(trans_id) is not None:
 
690
                return True
712
691
        return False
713
692
 
714
693
    def _set_executability(self, path, trans_id):
844
823
        Return a (name, parent, kind, executable) tuple
845
824
        """
846
825
        to_name = self.final_name(to_trans_id)
847
 
        try:
848
 
            to_kind = self.final_kind(to_trans_id)
849
 
        except NoSuchFile:
850
 
            to_kind = None
 
826
        to_kind = self.final_kind(to_trans_id)
851
827
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
852
828
        if to_trans_id in self._new_executability:
853
829
            to_executable = self._new_executability[to_trans_id]
927
903
        """
928
904
        return _PreviewTree(self)
929
905
 
930
 
    def commit(self, branch, message, merge_parents=None, strict=False):
 
906
    def commit(self, branch, message, merge_parents=None, strict=False,
 
907
               timestamp=None, timezone=None, committer=None, authors=None,
 
908
               revprops=None, revision_id=None):
931
909
        """Commit the result of this TreeTransform to a branch.
932
910
 
933
911
        :param branch: The branch to commit to.
934
912
        :param message: The message to attach to the commit.
935
 
        :param merge_parents: Additional parents specified by pending merges.
 
913
        :param merge_parents: Additional parent revision-ids specified by
 
914
            pending merges.
 
915
        :param strict: If True, abort the commit if there are unversioned
 
916
            files.
 
917
        :param timestamp: if not None, seconds-since-epoch for the time and
 
918
            date.  (May be a float.)
 
919
        :param timezone: Optional timezone for timestamp, as an offset in
 
920
            seconds.
 
921
        :param committer: Optional committer in email-id format.
 
922
            (e.g. "J Random Hacker <jrandom@example.com>")
 
923
        :param authors: Optional list of authors in email-id format.
 
924
        :param revprops: Optional dictionary of revision properties.
 
925
        :param revision_id: Optional revision id.  (Specifying a revision-id
 
926
            may reduce performance for some non-native formats.)
936
927
        :return: The revision_id of the revision committed.
937
928
        """
938
929
        self._check_malformed()
955
946
        if self._tree.get_revision_id() != last_rev_id:
956
947
            raise ValueError('TreeTransform not based on branch basis: %s' %
957
948
                             self._tree.get_revision_id())
958
 
        builder = branch.get_commit_builder(parent_ids)
 
949
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
 
950
        builder = branch.get_commit_builder(parent_ids,
 
951
                                            timestamp=timestamp,
 
952
                                            timezone=timezone,
 
953
                                            committer=committer,
 
954
                                            revprops=revprops,
 
955
                                            revision_id=revision_id)
959
956
        preview = self.get_preview_tree()
960
957
        list(builder.record_iter_changes(preview, last_rev_id,
961
958
                                         self.iter_changes()))
1160
1157
            if trans_id not in self._new_contents:
1161
1158
                continue
1162
1159
            new_path = self._limbo_name(trans_id)
1163
 
            osutils.rename(old_path, new_path)
 
1160
            os.rename(old_path, new_path)
1164
1161
            for descendant in self._limbo_descendants(trans_id):
1165
1162
                desc_path = self._limbo_files[descendant]
1166
1163
                desc_path = new_path + desc_path[len(old_path):]
1397
1394
    def tree_kind(self, trans_id):
1398
1395
        """Determine the file kind in the working tree.
1399
1396
 
1400
 
        Raises NoSuchFile if the file does not exist
 
1397
        :returns: The file kind or None if the file does not exist
1401
1398
        """
1402
1399
        path = self._tree_id_paths.get(trans_id)
1403
1400
        if path is None:
1404
 
            raise NoSuchFile(None)
 
1401
            return None
1405
1402
        try:
1406
1403
            return file_kind(self._tree.abspath(path))
1407
 
        except OSError, e:
1408
 
            if e.errno != errno.ENOENT:
1409
 
                raise
1410
 
            else:
1411
 
                raise NoSuchFile(path)
 
1404
        except errors.NoSuchFile:
 
1405
            return None
1412
1406
 
1413
1407
    def _set_mode(self, trans_id, mode_id, typefunc):
1414
1408
        """Set the mode of new file contents.
1583
1577
                if file_id is None:
1584
1578
                    continue
1585
1579
                needs_entry = False
1586
 
                try:
1587
 
                    kind = self.final_kind(trans_id)
1588
 
                except NoSuchFile:
 
1580
                kind = self.final_kind(trans_id)
 
1581
                if kind is None:
1589
1582
                    kind = self._tree.stored_kind(file_id)
1590
1583
                parent_trans_id = self.final_parent(trans_id)
1591
1584
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1635
1628
                      or trans_id in self._new_parent):
1636
1629
                    try:
1637
1630
                        mover.rename(full_path, self._limbo_name(trans_id))
1638
 
                    except OSError, e:
 
1631
                    except errors.TransformRenameFailed, e:
1639
1632
                        if e.errno != errno.ENOENT:
1640
1633
                            raise
1641
1634
                    else:
1666
1659
                if trans_id in self._needs_rename:
1667
1660
                    try:
1668
1661
                        mover.rename(self._limbo_name(trans_id), full_path)
1669
 
                    except OSError, e:
 
1662
                    except errors.TransformRenameFailed, e:
1670
1663
                        # We may be renaming a dangling inventory id
1671
1664
                        if e.errno != errno.ENOENT:
1672
1665
                            raise
1703
1696
    def tree_kind(self, trans_id):
1704
1697
        path = self._tree_id_paths.get(trans_id)
1705
1698
        if path is None:
1706
 
            raise NoSuchFile(None)
 
1699
            return None
1707
1700
        file_id = self._tree.path2id(path)
1708
 
        return self._tree.kind(file_id)
 
1701
        try:
 
1702
            return self._tree.kind(file_id)
 
1703
        except errors.NoSuchFile:
 
1704
            return None
1709
1705
 
1710
1706
    def _set_mode(self, trans_id, mode_id, typefunc):
1711
1707
        """Set the mode of new file contents.
1770
1766
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1771
1767
                       self._iter_parent_trees()]
1772
1768
        vf.add_lines((file_id, tree_revision), parent_keys,
1773
 
                     self.get_file(file_id).readlines())
 
1769
                     self.get_file_lines(file_id))
1774
1770
        repo = self._get_repository()
1775
1771
        base_vf = repo.texts
1776
1772
        if base_vf not in vf.fallback_versionedfiles:
1798
1794
            executable = self.is_executable(file_id, path)
1799
1795
        return kind, executable, None
1800
1796
 
 
1797
    def is_locked(self):
 
1798
        return False
 
1799
 
1801
1800
    def lock_read(self):
1802
1801
        # Perhaps in theory, this should lock the TreeTransform?
1803
 
        pass
 
1802
        return self
1804
1803
 
1805
1804
    def unlock(self):
1806
1805
        pass
1904
1903
            if (specific_file_ids is not None
1905
1904
                and file_id not in specific_file_ids):
1906
1905
                continue
1907
 
            try:
1908
 
                kind = self._transform.final_kind(trans_id)
1909
 
            except NoSuchFile:
 
1906
            kind = self._transform.final_kind(trans_id)
 
1907
            if kind is None:
1910
1908
                kind = self._transform._tree.stored_kind(file_id)
1911
1909
            new_entry = inventory.make_entry(
1912
1910
                kind,
2144
2142
                path_from_root = self._final_paths.get_path(child_id)
2145
2143
                basename = self._transform.final_name(child_id)
2146
2144
                file_id = self._transform.final_file_id(child_id)
2147
 
                try:
2148
 
                    kind = self._transform.final_kind(child_id)
 
2145
                kind  = self._transform.final_kind(child_id)
 
2146
                if kind is not None:
2149
2147
                    versioned_kind = kind
2150
 
                except NoSuchFile:
 
2148
                else:
2151
2149
                    kind = 'unknown'
2152
2150
                    versioned_kind = self._transform._tree.stored_kind(file_id)
2153
2151
                if versioned_kind == 'directory':
2266
2264
    for num, _unused in enumerate(wt.all_file_ids()):
2267
2265
        if num > 0:  # more than just a root
2268
2266
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2269
 
    existing_files = set()
2270
 
    for dir, files in wt.walkdirs():
2271
 
        existing_files.update(f[0] for f in files)
2272
2267
    file_trans_id = {}
2273
2268
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2274
2269
    pp = ProgressPhase("Build phase", 2, top_pb)
2298
2293
                precomputed_delta = []
2299
2294
            else:
2300
2295
                precomputed_delta = None
 
2296
            # Check if tree inventory has content. If so, we populate
 
2297
            # existing_files with the directory content. If there are no
 
2298
            # entries we skip populating existing_files as its not used.
 
2299
            # This improves performance and unncessary work on large
 
2300
            # directory trees. (#501307)
 
2301
            if total > 0:
 
2302
                existing_files = set()
 
2303
                for dir, files in wt.walkdirs():
 
2304
                    existing_files.update(f[0] for f in files)
2301
2305
            for num, (tree_path, entry) in \
2302
2306
                enumerate(tree.inventory.iter_entries_by_dir()):
2303
2307
                pb.update("Building tree", num - len(deferred_contents), total)
2435
2439
    if entry.kind == "directory":
2436
2440
        return True
2437
2441
    if entry.kind == "file":
2438
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2439
 
            return True
 
2442
        f = file(target_path, 'rb')
 
2443
        try:
 
2444
            if tree.get_file_text(file_id) == f.read():
 
2445
                return True
 
2446
        finally:
 
2447
            f.close()
2440
2448
    elif entry.kind == "symlink":
2441
2449
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
2442
2450
            return True
2494
2502
        raise errors.BadFileKindError(name, kind)
2495
2503
 
2496
2504
 
2497
 
@deprecated_function(deprecated_in((1, 9, 0)))
2498
 
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2499
 
    """Create new file contents according to an inventory entry.
2500
 
 
2501
 
    DEPRECATED.  Use create_from_tree instead.
2502
 
    """
2503
 
    if entry.kind == "file":
2504
 
        if lines is None:
2505
 
            lines = tree.get_file(entry.file_id).readlines()
2506
 
        tt.create_file(lines, trans_id, mode_id=mode_id)
2507
 
    elif entry.kind == "symlink":
2508
 
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2509
 
    elif entry.kind == "directory":
2510
 
        tt.create_directory(trans_id)
2511
 
 
2512
 
 
2513
2505
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2514
2506
    filter_tree_path=None):
2515
2507
    """Create new file contents according to tree contents.
2900
2892
    def rename(self, from_, to):
2901
2893
        """Rename a file from one path to another."""
2902
2894
        try:
2903
 
            osutils.rename(from_, to)
 
2895
            os.rename(from_, to)
2904
2896
        except OSError, e:
2905
2897
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2906
2898
                raise errors.FileExists(to, str(e))
2907
 
            raise
 
2899
            # normal OSError doesn't include filenames so it's hard to see where
 
2900
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
 
2901
            raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2908
2902
        self.past_renames.append((from_, to))
2909
2903
 
2910
2904
    def pre_delete(self, from_, to):
2920
2914
    def rollback(self):
2921
2915
        """Reverse all renames that have been performed"""
2922
2916
        for from_, to in reversed(self.past_renames):
2923
 
            osutils.rename(to, from_)
 
2917
            try:
 
2918
                os.rename(to, from_)
 
2919
            except OSError, e:
 
2920
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)                
2924
2921
        # after rollback, don't reuse _FileMover
2925
2922
        past_renames = None
2926
2923
        pending_deletions = None