def test_raises_repository_empty(repo, worktree_name):
    # Initially set the HEAD pointer to a value without a backing reference.
    repo.set_head('refs/heads/test-empty')

    with pytest.raises(RepositoryEmptyException) as e:
        with TemporaryWorktree(repo, worktree_name):
            pass
def test_raises_worktree_conflict(repo, worktree_name, worktree_path):
    repo.add_worktree(worktree_name, worktree_path)

    with pytest.raises(WorktreeConflictException) as e:
        with TemporaryWorktree(repo, worktree_name):
            pass

    assert str(e.value) == f'Worktree {worktree_name} already exists'
Exemple #3
0
    def install(self,
                template: str,
                checkout: str = 'master',
                no_input: bool = False):
        """Creates a fresh template install within the supplied repo.

        Generates a template using the provided context, or invokes the questionnaire to elicit it.

        Args:
            template: The path (either local or git) to the template project. It must follow
                the cookiecutter format to be compatible with battenberg.
            checkout: The new state to pull from the template, normally this will be a git tag on
                the template repo.
            no_input: Whether to ask the user to answer the template questions again or take the
                default answers from the templates "cookiecutter.json".

        Raises:
            MergeConflictException: Thrown when an upgrade results in merge conflicts between the
                template branch and the merge-target branch.
            TemplateConflictException: When the repo already contains a template branch. If you
                encounter this please run "battenberg upgrade" instead.
        """

        # Assert template branch doesn't exist or raise conflict
        if self.is_installed():
            raise TemplateConflictException()

        # Create temporary worktree
        with TemporaryWorktree(self.repo, WORKTREE_NAME) as worktree:
            cookiecutter_kwargs = {
                'template': template,
                'checkout': checkout,
                'no_input': no_input
            }
            self._cookiecut(cookiecutter_kwargs, worktree)

            # Stage changes
            worktree.repo.index.add_all()
            worktree.repo.index.write()
            tree = worktree.repo.index.write_tree()

            # Create an orphaned commit
            oid = worktree.repo.create_commit(
                None, worktree.repo.default_signature,
                worktree.repo.default_signature,
                'Prepared template installation', tree, [])
            commit = self.repo.get(oid)

            # Create a branch which target orphaned commit
            branch = self.repo.create_branch(TEMPLATE_BRANCH, commit)

            # Optionally, set worktree HEAD to this branch (useful for debugging)
            # Optional ? Obviously the tmp worktree will be removed in __exit__
            worktree.repo.set_head(branch.name)

        # Let's merge our changes into HEAD
        self._merge_template_branch(f'Installed template \'{template}\'')
Exemple #4
0
def test_initializes_empty_worktree(repo, worktree_name, empty, dir_contents):
    # Loop over all the entries in the unstaged tree.
    get_tree_entries = lambda r: [entry.name for entry in r[r.head.target].tree]
    assert get_tree_entries(repo) == ['.gitignore', 'hello.txt']

    with TemporaryWorktree(repo, worktree_name, empty=empty) as tmp_worktree:
        # Not an easy way to compare apples to apples here as it constructs the worktree
        # and immediately empties it in the same function. Some work todo here to make
        # this a little cleaner in the future I think.
        assert os.listdir(tmp_worktree.path) == dir_contents
def test_raises_worktree_error(mkdtemp, repo, worktree_name, worktree_path):
    mkdtemp.return_value = worktree_path

    with patch.object(repo, 'add_worktree') as add_worktree:
        add_worktree.side_effect = ValueError('Error adding new worktree')
        with pytest.raises(WorktreeException) as e:
            with TemporaryWorktree(repo, worktree_name):
                pass

        add_worktree.assert_called_once_with(
            worktree_name, os.path.join(worktree_path, worktree_name))
Exemple #6
0
    def upgrade(self,
                checkout: str = 'master',
                no_input: bool = True,
                merge_target: str = None,
                context_file: str = '.cookiecutter.json'):
        """Updates a repo using the found template context.

        Generates and applies any updates from the current repo state to the template state defined
        by "checkout". It does this by reading the existing template context state defined within
        "context_file" and using that to pull in any new updates.

        Args:
            checkout: The new state to pull from the template, normally this will be a git tag on
                the template repo.
            no_input: Whether to ask the user to answer the template questions again or take the
                answers from the template context defined in "context_file".
            merge_target: A branch to checkout other than the current HEAD. Useful if you're
                upgrading a project you do not directly own.
            context_file: Where battenberg should look to read the template context.

        Raises:
            MergeConflictException: Thrown when an upgrade results in merge conflicts between the
                template branch and the merge-target branch.
            TemplateNotFoundException: When the repo does not already contain a template branch. If
                you encounter this please run "battenberg install" instead.
        """

        if not self.is_installed():
            try:
                self._fetch_remote_template()
            except KeyError as e:
                # Cannot find the origin remote branch.
                logger.error(e)
                raise TemplateNotFoundException() from e

        # Get last context used to apply template
        context = self._get_context(context_file)
        logger.debug(f'Found context: {context}')
        # Fetch template information, this is normally the git:// URL.
        template = context['_template']
        logger.debug(f'Found template: {template}')

        # Create temporary EMPTY worktree
        with TemporaryWorktree(self.repo, WORKTREE_NAME) as worktree:
            # Set HEAD to template branch
            branch = worktree.repo.lookup_branch(TEMPLATE_BRANCH)
            worktree.repo.set_head(branch.name)

            cookiecutter_kwargs = {
                'template': template,
                'checkout': checkout,
                'extra_context': context,
                'no_input': no_input
            }
            self._cookiecut(cookiecutter_kwargs, worktree)

            # Stage changes
            worktree.repo.index.read()
            worktree.repo.index.add_all()
            worktree.repo.index.write()
            tree = worktree.repo.index.write_tree()

            # Create commit on the template branch
            oid = worktree.repo.create_commit('HEAD',
                                              worktree.repo.default_signature,
                                              worktree.repo.default_signature,
                                              'Prepared template upgrade',
                                              tree,
                                              [worktree.repo.head.target])
            commit = worktree.repo.get(oid)

        # Make template branch ref to created commit
        self.repo.lookup_branch(TEMPLATE_BRANCH).set_target(commit.hex)

        # Let's merge our changes into HEAD
        self._merge_template_branch(f'Upgraded template \'{template}\'',
                                    merge_target)