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'
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}\'')
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))
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)