~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/controldir.py

  • Committer: jelmer at samba
  • Date: 2011-10-11 10:21:35 UTC
  • mto: This revision was merged to the branch mainline in revision 6214.
  • Revision ID: jelmer@samba.org-20111011102135-0wuzm1stjwxehkft
Move convenience methods to ControlDir.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
 
29
29
from bzrlib import (
30
30
    errors,
 
31
    hooks,
31
32
    revision as _mod_revision,
32
33
    transport as _mod_transport,
33
34
    ui,
483
484
        """
484
485
        raise NotImplementedError(self.clone_on_transport)
485
486
 
 
487
    @classmethod
 
488
    def find_bzrdirs(cls, transport, evaluate=None, list_current=None):
 
489
        """Find bzrdirs recursively from current location.
 
490
 
 
491
        This is intended primarily as a building block for more sophisticated
 
492
        functionality, like finding trees under a directory, or finding
 
493
        branches that use a given repository.
 
494
 
 
495
        :param evaluate: An optional callable that yields recurse, value,
 
496
            where recurse controls whether this bzrdir is recursed into
 
497
            and value is the value to yield.  By default, all bzrdirs
 
498
            are recursed into, and the return value is the bzrdir.
 
499
        :param list_current: if supplied, use this function to list the current
 
500
            directory, instead of Transport.list_dir
 
501
        :return: a generator of found bzrdirs, or whatever evaluate returns.
 
502
        """
 
503
        if list_current is None:
 
504
            def list_current(transport):
 
505
                return transport.list_dir('')
 
506
        if evaluate is None:
 
507
            def evaluate(bzrdir):
 
508
                return True, bzrdir
 
509
 
 
510
        pending = [transport]
 
511
        while len(pending) > 0:
 
512
            current_transport = pending.pop()
 
513
            recurse = True
 
514
            try:
 
515
                bzrdir = cls.open_from_transport(current_transport)
 
516
            except (errors.NotBranchError, errors.PermissionDenied):
 
517
                pass
 
518
            else:
 
519
                recurse, value = evaluate(bzrdir)
 
520
                yield value
 
521
            try:
 
522
                subdirs = list_current(current_transport)
 
523
            except (errors.NoSuchFile, errors.PermissionDenied):
 
524
                continue
 
525
            if recurse:
 
526
                for subdir in sorted(subdirs, reverse=True):
 
527
                    pending.append(current_transport.clone(subdir))
 
528
 
 
529
    @classmethod
 
530
    def find_branches(cls, transport):
 
531
        """Find all branches under a transport.
 
532
 
 
533
        This will find all branches below the transport, including branches
 
534
        inside other branches.  Where possible, it will use
 
535
        Repository.find_branches.
 
536
 
 
537
        To list all the branches that use a particular Repository, see
 
538
        Repository.find_branches
 
539
        """
 
540
        def evaluate(bzrdir):
 
541
            try:
 
542
                repository = bzrdir.open_repository()
 
543
            except errors.NoRepositoryPresent:
 
544
                pass
 
545
            else:
 
546
                return False, ([], repository)
 
547
            return True, (bzrdir.list_branches(), None)
 
548
        ret = []
 
549
        for branches, repo in cls.find_bzrdirs(
 
550
                transport, evaluate=evaluate):
 
551
            if repo is not None:
 
552
                ret.extend(repo.find_branches())
 
553
            if branches is not None:
 
554
                ret.extend(branches)
 
555
        return ret
 
556
 
 
557
    @classmethod
 
558
    def create_branch_and_repo(cls, base, force_new_repo=False, format=None):
 
559
        """Create a new ControlDir, Branch and Repository at the url 'base'.
 
560
 
 
561
        This will use the current default ControlDirFormat unless one is
 
562
        specified, and use whatever
 
563
        repository format that that uses via bzrdir.create_branch and
 
564
        create_repository. If a shared repository is available that is used
 
565
        preferentially.
 
566
 
 
567
        The created Branch object is returned.
 
568
 
 
569
        :param base: The URL to create the branch at.
 
570
        :param force_new_repo: If True a new repository is always created.
 
571
        :param format: If supplied, the format of branch to create.  If not
 
572
            supplied, the default is used.
 
573
        """
 
574
        bzrdir = cls.create(base, format)
 
575
        bzrdir._find_or_create_repository(force_new_repo)
 
576
        return bzrdir.create_branch()
 
577
 
 
578
    @classmethod
 
579
    def create_branch_convenience(cls, base, force_new_repo=False,
 
580
                                  force_new_tree=None, format=None,
 
581
                                  possible_transports=None):
 
582
        """Create a new ControlDir, Branch and Repository at the url 'base'.
 
583
 
 
584
        This is a convenience function - it will use an existing repository
 
585
        if possible, can be told explicitly whether to create a working tree or
 
586
        not.
 
587
 
 
588
        This will use the current default ControlDirFormat unless one is
 
589
        specified, and use whatever
 
590
        repository format that that uses via bzrdir.create_branch and
 
591
        create_repository. If a shared repository is available that is used
 
592
        preferentially. Whatever repository is used, its tree creation policy
 
593
        is followed.
 
594
 
 
595
        The created Branch object is returned.
 
596
        If a working tree cannot be made due to base not being a file:// url,
 
597
        no error is raised unless force_new_tree is True, in which case no
 
598
        data is created on disk and NotLocalUrl is raised.
 
599
 
 
600
        :param base: The URL to create the branch at.
 
601
        :param force_new_repo: If True a new repository is always created.
 
602
        :param force_new_tree: If True or False force creation of a tree or
 
603
                               prevent such creation respectively.
 
604
        :param format: Override for the bzrdir format to create.
 
605
        :param possible_transports: An optional reusable transports list.
 
606
        """
 
607
        if force_new_tree:
 
608
            # check for non local urls
 
609
            t = _mod_transport.get_transport(base, possible_transports)
 
610
            if not isinstance(t, local.LocalTransport):
 
611
                raise errors.NotLocalUrl(base)
 
612
        bzrdir = cls.create(base, format, possible_transports)
 
613
        repo = bzrdir._find_or_create_repository(force_new_repo)
 
614
        result = bzrdir.create_branch()
 
615
        if force_new_tree or (repo.make_working_trees() and
 
616
                              force_new_tree is None):
 
617
            try:
 
618
                bzrdir.create_workingtree()
 
619
            except errors.NotLocalUrl:
 
620
                pass
 
621
        return result
 
622
 
 
623
    @classmethod
 
624
    def create_standalone_workingtree(cls, base, format=None):
 
625
        """Create a new ControlDir, WorkingTree, Branch and Repository at 'base'.
 
626
 
 
627
        'base' must be a local path or a file:// url.
 
628
 
 
629
        This will use the current default ControlDirFormat unless one is
 
630
        specified, and use whatever
 
631
        repository format that that uses for bzrdirformat.create_workingtree,
 
632
        create_branch and create_repository.
 
633
 
 
634
        :param format: Override for the bzrdir format to create.
 
635
        :return: The WorkingTree object.
 
636
        """
 
637
        from bzrlib.transport import local
 
638
        t = _mod_transport.get_transport(base)
 
639
        if not isinstance(t, local.LocalTransport):
 
640
            raise errors.NotLocalUrl(base)
 
641
        bzrdir = cls.create_branch_and_repo(base,
 
642
                                               force_new_repo=True,
 
643
                                               format=format).bzrdir
 
644
        return bzrdir.create_workingtree()
 
645
 
 
646
    @classmethod
 
647
    def open_unsupported(cls, base):
 
648
        """Open a branch which is not supported."""
 
649
        return cls.open(base, _unsupported=True)
 
650
 
 
651
    @classmethod
 
652
    def open(cls, base, _unsupported=False, possible_transports=None):
 
653
        """Open an existing bzrdir, rooted at 'base' (url).
 
654
 
 
655
        :param _unsupported: a private parameter to the ControlDir class.
 
656
        """
 
657
        t = _mod_transport.get_transport(base, possible_transports)
 
658
        return cls.open_from_transport(t, _unsupported=_unsupported)
 
659
 
 
660
    @classmethod
 
661
    def open_from_transport(cls, transport, _unsupported=False,
 
662
                            _server_formats=True):
 
663
        """Open a bzrdir within a particular directory.
 
664
 
 
665
        :param transport: Transport containing the bzrdir.
 
666
        :param _unsupported: private.
 
667
        """
 
668
        for hook in cls.hooks['pre_open']:
 
669
            hook(transport)
 
670
        # Keep initial base since 'transport' may be modified while following
 
671
        # the redirections.
 
672
        base = transport.base
 
673
        def find_format(transport):
 
674
            return transport, ControlDirFormat.find_format(
 
675
                transport, _server_formats=_server_formats)
 
676
 
 
677
        def redirected(transport, e, redirection_notice):
 
678
            redirected_transport = transport._redirected_to(e.source, e.target)
 
679
            if redirected_transport is None:
 
680
                raise errors.NotBranchError(base)
 
681
            note(gettext('{0} is{1} redirected to {2}').format(
 
682
                 transport.base, e.permanently, redirected_transport.base))
 
683
            return redirected_transport
 
684
 
 
685
        try:
 
686
            transport, format = _mod_transport.do_catching_redirections(
 
687
                find_format, transport, redirected)
 
688
        except errors.TooManyRedirections:
 
689
            raise errors.NotBranchError(base)
 
690
 
 
691
        format.check_support_status(_unsupported)
 
692
        return format.open(transport, _found=True)
 
693
 
 
694
    @classmethod
 
695
    def open_containing(cls, url, possible_transports=None):
 
696
        """Open an existing branch which contains url.
 
697
 
 
698
        :param url: url to search from.
 
699
 
 
700
        See open_containing_from_transport for more detail.
 
701
        """
 
702
        transport = _mod_transport.get_transport(url, possible_transports)
 
703
        return cls.open_containing_from_transport(transport)
 
704
 
 
705
    @classmethod
 
706
    def open_containing_from_transport(cls, a_transport):
 
707
        """Open an existing branch which contains a_transport.base.
 
708
 
 
709
        This probes for a branch at a_transport, and searches upwards from there.
 
710
 
 
711
        Basically we keep looking up until we find the control directory or
 
712
        run into the root.  If there isn't one, raises NotBranchError.
 
713
        If there is one and it is either an unrecognised format or an unsupported
 
714
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
715
        If there is one, it is returned, along with the unused portion of url.
 
716
 
 
717
        :return: The ControlDir that contains the path, and a Unicode path
 
718
                for the rest of the URL.
 
719
        """
 
720
        # this gets the normalised url back. I.e. '.' -> the full path.
 
721
        url = a_transport.base
 
722
        while True:
 
723
            try:
 
724
                result = cls.open_from_transport(a_transport)
 
725
                return result, urlutils.unescape(a_transport.relpath(url))
 
726
            except errors.NotBranchError, e:
 
727
                pass
 
728
            try:
 
729
                new_t = a_transport.clone('..')
 
730
            except errors.InvalidURLJoin:
 
731
                # reached the root, whatever that may be
 
732
                raise errors.NotBranchError(path=url)
 
733
            if new_t.base == a_transport.base:
 
734
                # reached the root, whatever that may be
 
735
                raise errors.NotBranchError(path=url)
 
736
            a_transport = new_t
 
737
 
 
738
    @classmethod
 
739
    def open_tree_or_branch(klass, location):
 
740
        """Return the branch and working tree at a location.
 
741
 
 
742
        If there is no tree at the location, tree will be None.
 
743
        If there is no branch at the location, an exception will be
 
744
        raised
 
745
        :return: (tree, branch)
 
746
        """
 
747
        bzrdir = klass.open(location)
 
748
        return bzrdir._get_tree_branch()
 
749
 
 
750
    @classmethod
 
751
    def open_containing_tree_or_branch(klass, location):
 
752
        """Return the branch and working tree contained by a location.
 
753
 
 
754
        Returns (tree, branch, relpath).
 
755
        If there is no tree at containing the location, tree will be None.
 
756
        If there is no branch containing the location, an exception will be
 
757
        raised
 
758
        relpath is the portion of the path that is contained by the branch.
 
759
        """
 
760
        bzrdir, relpath = klass.open_containing(location)
 
761
        tree, branch = bzrdir._get_tree_branch()
 
762
        return tree, branch, relpath
 
763
 
 
764
    @classmethod
 
765
    def open_containing_tree_branch_or_repository(klass, location):
 
766
        """Return the working tree, branch and repo contained by a location.
 
767
 
 
768
        Returns (tree, branch, repository, relpath).
 
769
        If there is no tree containing the location, tree will be None.
 
770
        If there is no branch containing the location, branch will be None.
 
771
        If there is no repository containing the location, repository will be
 
772
        None.
 
773
        relpath is the portion of the path that is contained by the innermost
 
774
        ControlDir.
 
775
 
 
776
        If no tree, branch or repository is found, a NotBranchError is raised.
 
777
        """
 
778
        bzrdir, relpath = klass.open_containing(location)
 
779
        try:
 
780
            tree, branch = bzrdir._get_tree_branch()
 
781
        except errors.NotBranchError:
 
782
            try:
 
783
                repo = bzrdir.find_repository()
 
784
                return None, None, repo, relpath
 
785
            except (errors.NoRepositoryPresent):
 
786
                raise errors.NotBranchError(location)
 
787
        return tree, branch, branch.repository, relpath
 
788
 
 
789
    @classmethod
 
790
    def create(cls, base, format=None, possible_transports=None):
 
791
        """Create a new ControlDir at the url 'base'.
 
792
 
 
793
        :param format: If supplied, the format of branch to create.  If not
 
794
            supplied, the default is used.
 
795
        :param possible_transports: If supplied, a list of transports that
 
796
            can be reused to share a remote connection.
 
797
        """
 
798
        if cls is not ControlDir:
 
799
            raise AssertionError("ControlDir.create always creates the"
 
800
                "default format, not one of %r" % cls)
 
801
        t = _mod_transport.get_transport(base, possible_transports)
 
802
        t.ensure_base()
 
803
        if format is None:
 
804
            format = ControlDirFormat.get_default_format()
 
805
        return format.initialize_on_transport(t)
 
806
 
 
807
 
 
808
class ControlDirHooks(hooks.Hooks):
 
809
    """Hooks for ControlDir operations."""
 
810
 
 
811
    def __init__(self):
 
812
        """Create the default hooks."""
 
813
        hooks.Hooks.__init__(self, "bzrlib.controldir", "ControlDir.hooks")
 
814
        self.add_hook('pre_open',
 
815
            "Invoked before attempting to open a ControlDir with the transport "
 
816
            "that the open will use.", (1, 14))
 
817
        self.add_hook('post_repo_init',
 
818
            "Invoked after a repository has been initialized. "
 
819
            "post_repo_init is called with a "
 
820
            "bzrlib.controldir.RepoInitHookParams.",
 
821
            (2, 2))
 
822
 
 
823
# install the default hooks
 
824
ControlDir.hooks = ControlDirHooks()
 
825
 
486
826
 
487
827
class ControlComponentFormat(object):
488
828
    """A component that can live inside of a .bzr meta directory."""