def test_prompt_should_ask_and_keep_repo_on_no_reuse(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user wants to keep their old \
    cloned template repo, it should not be deleted."""
    mock_read_user = mocker.patch('cookiecutter.utils.read_user_yes_no',
                                  return_value=False,
                                  autospec=True)
    repo_dir = tmpdir.mkdir('repo')

    with pytest.raises(SystemExit):
        utils.prompt_and_delete(str(repo_dir))

    assert mock_read_user.called
    assert repo_dir.exists()
示例#2
0
def test_prompt_should_ask_and_keep_repo_on_no_reuse(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user wants to keep their old
    cloned template repo, it should not be deleted.
    """
    mock_read_user = mocker.patch(
        'cookiecutter.utils.read_user_yes_no',
        return_value=False,
        autospec=True
    )
    repo_dir = tmpdir.mkdir('repo')

    with pytest.raises(SystemExit):
        utils.prompt_and_delete(str(repo_dir))

    assert mock_read_user.called
    assert repo_dir.exists()
示例#3
0
def clone(repo_url, checkout=None, clone_to_dir='.', no_input=False):
    """
    Clone a repo to the current directory.

    :param repo_url: Repo URL of unknown type.
    :param checkout: The branch, tag or commit ID to checkout after clone.
    :param clone_to_dir: The directory to clone to.
                         Defaults to the current directory.
    :param no_input: Suppress all user prompts when calling via API.
    :returns: str with path to the new directory of the repository.
    """
    # Ensure that clone_to_dir exists
    clone_to_dir = os.path.expanduser(clone_to_dir)
    make_sure_path_exists(clone_to_dir)

    # identify the repo_type
    vcs, repo_url = identify_repo(repo_url)

    # check that the appropriate VCS for the repo_type is installed
    if not vcs.is_installed():
        msg = "'{0}' is not installed.".format(vcs.cmd)
        raise VCSNotInstalled(msg)

    repo_url = repo_url.rstrip('/')
    repo_name = os.path.split(repo_url)[1]
    repo_dir = vcs.get_repo_dir(repo_name, clone_to_dir)
    logger.debug('repo_dir is {0}'.format(repo_dir))

    if os.path.isdir(repo_dir):
        clone = prompt_and_delete(repo_dir, no_input=no_input)
    else:
        clone = True

    if clone:
        try:
            vcs.clone(repo_url, checkout, clone_to_dir, repo_dir)
        except subprocess.CalledProcessError as clone_error:
            output = clone_error.output.decode('utf-8')

            # In case of error, print VCS output
            click.echo(
                'Cloning of {} repository {} returned an error:\n{}'.format(
                    vcs.cmd, repo_url, output))

            if any(error in output.lower() for error in vcs.not_found_errors):
                raise RepositoryNotFound(
                    'The repository {} could not be found, '
                    'have you made a typo?'.format(repo_url))
            if any(error in output.lower() for error in vcs.branch_errors):
                raise RepositoryCloneFailed(
                    'The {} branch of repository {} could not found, '
                    'have you made a typo?'.format(checkout, repo_url))
            # Raise base subprocess error if SVN error can't be identified
            raise

    return repo_dir
def test_prompt_should_ask_and_rm_repo_dir(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user agrees to delete/reclone the \
    repo, the repo should be deleted."""
    mock_read_user = mocker.patch('cookiecutter.utils.read_user_yes_no',
                                  return_value=True,
                                  autospec=True)
    repo_dir = tmpdir.mkdir('repo')

    deleted = utils.prompt_and_delete(str(repo_dir))

    assert mock_read_user.called
    assert not repo_dir.exists()
    assert deleted
示例#5
0
def test_prompt_should_not_ask_if_no_input_and_rm_repo_dir(mocker, tmpdir):
    """In `prompt_and_delete()`, if `no_input` is True, the call to
    `prompt.read_user_yes_no()` should be suppressed.
    """
    mock_read_user = mocker.patch('cookiecutter.prompt.read_user_yes_no',
                                  return_value=True,
                                  autospec=True)
    repo_dir = tmpdir.mkdir('repo')

    deleted = utils.prompt_and_delete(str(repo_dir), no_input=True)

    assert not mock_read_user.called
    assert not repo_dir.exists()
    assert deleted
示例#6
0
def test_prompt_should_ask_and_exit_on_user_no_answer(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user decline to delete/reclone the \
    repo, cookiecutter should exit."""
    mock_read_user = mocker.patch(
        'cookiecutter.utils.read_user_yes_no', return_value=False,
    )
    mock_sys_exit = mocker.patch('sys.exit', return_value=True)
    repo_dir = tmpdir.mkdir('repo')

    deleted = utils.prompt_and_delete(str(repo_dir))

    assert mock_read_user.called
    assert repo_dir.exists()
    assert not deleted
    assert mock_sys_exit.called
def test_prompt_should_ask_and_rm_repo_file(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user agrees to delete/reclone a \
    repo file, the repo should be deleted."""
    mock_read_user = mocker.patch('cookiecutter.utils.read_user_yes_no',
                                  return_value=True,
                                  autospec=True)

    repo_file = tmpdir.join('repo.zip')
    repo_file.write('this is zipfile content')

    deleted = utils.prompt_and_delete(str(repo_file))

    assert mock_read_user.called
    assert not repo_file.exists()
    assert deleted
示例#8
0
def test_prompt_should_ask_and_rm_repo_dir(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user agrees to delete/reclone the
    repo, the repo should be deleted.
    """
    mock_read_user = mocker.patch(
        'cookiecutter.utils.read_user_yes_no',
        return_value=True,
        autospec=True
    )
    repo_dir = tmpdir.mkdir('repo')

    deleted = utils.prompt_and_delete(str(repo_dir))

    assert mock_read_user.called
    assert not repo_dir.exists()
    assert deleted
示例#9
0
def test_prompt_should_not_ask_if_no_input_and_rm_repo_dir(mocker, tmpdir):
    """In `prompt_and_delete()`, if `no_input` is True, the call to
    `prompt.read_user_yes_no()` should be suppressed.
    """
    mock_read_user = mocker.patch(
        'cookiecutter.prompt.read_user_yes_no',
        return_value=True,
        autospec=True
    )
    repo_dir = tmpdir.mkdir('repo')

    deleted = utils.prompt_and_delete(str(repo_dir), no_input=True)

    assert not mock_read_user.called
    assert not repo_dir.exists()
    assert deleted
示例#10
0
def test_prompt_should_not_ask_if_no_input_and_rm_repo_file(mocker, tmpdir):
    """In `prompt_and_delete()`, if `no_input` is True, the call to
    `prompt.read_user_yes_no()` should be suppressed.
    """
    mock_read_user = mocker.patch('cookiecutter.prompt.read_user_yes_no',
                                  return_value=True,
                                  autospec=True)

    repo_file = tmpdir.join('repo.zip')
    repo_file.write('this is zipfile content')

    deleted = utils.prompt_and_delete(str(repo_file), no_input=True)

    assert not mock_read_user.called
    assert not repo_file.exists()
    assert deleted
示例#11
0
def test_prompt_should_not_ask_if_no_input_and_rm_repo_file(mocker, tmpdir):
    """In `prompt_and_delete()`, if `no_input` is True, the call to
    `prompt.read_user_yes_no()` should be suppressed.
    """
    mock_read_user = mocker.patch(
        'cookiecutter.prompt.read_user_yes_no',
        return_value=True,
        autospec=True
    )

    repo_file = tmpdir.join('repo.zip')
    repo_file.write('this is zipfile content')

    deleted = utils.prompt_and_delete(str(repo_file), no_input=True)

    assert not mock_read_user.called
    assert not repo_file.exists()
    assert deleted
示例#12
0
def test_prompt_should_ask_and_rm_repo_file(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user agrees to delete/reclone a
    repo file, the repo should be deleted.
    """
    mock_read_user = mocker.patch(
        'cookiecutter.utils.read_user_yes_no',
        return_value=True,
        autospec=True
    )

    repo_file = tmpdir.join('repo.zip')
    repo_file.write('this is zipfile content')

    deleted = utils.prompt_and_delete(str(repo_file))

    assert mock_read_user.called
    assert not repo_file.exists()
    assert deleted
示例#13
0
def test_prompt_should_ask_and_keep_repo_on_reuse(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user wants to keep their old \
    cloned template repo, it should not be deleted."""
    def answer(question, default):
        if 'okay to delete' in question:
            return False
        else:
            return True

    mock_read_user = mocker.patch('cookiecutter.utils.read_user_yes_no',
                                  side_effect=answer,
                                  autospec=True)
    repo_dir = tmpdir.mkdir('repo')

    deleted = utils.prompt_and_delete(str(repo_dir))

    assert mock_read_user.called
    assert repo_dir.exists()
    assert not deleted
示例#14
0
def test_prompt_should_ask_and_keep_repo_on_reuse(mocker, tmpdir):
    """In `prompt_and_delete()`, if the user wants to keep their old
    cloned template repo, it should not be deleted.
    """
    def answer(question, default):
        if 'okay to delete' in question:
            return False
        else:
            return True

    mock_read_user = mocker.patch(
        'cookiecutter.utils.read_user_yes_no',
        side_effect=answer,
        autospec=True
    )
    repo_dir = tmpdir.mkdir('repo')

    deleted = utils.prompt_and_delete(str(repo_dir))

    assert mock_read_user.called
    assert repo_dir.exists()
    assert not deleted
示例#15
0
def unzip(zip_uri,
          is_url,
          clone_to_dir='.',
          no_input=False,
          refresh=False,
          password=None):
    """Download and unpack a zipfile at a given URI.

    This will download the zipfile to the cookiecutter repository,
    and unpack into a temporary directory.

    :param zip_uri: The URI for the zipfile.
    :param is_url: Is the zip URI a URL or a file?
    :param clone_to_dir: The cookiecutter repository directory
        to put the archive into.
    :param no_input: Suppress any prompts
    :param refresh: If true, overwrites cached cookiecutter without prompting user
    :param password: The password to use when unpacking the repository.
    """
    # Ensure that clone_to_dir exists
    clone_to_dir = os.path.expanduser(clone_to_dir)
    make_sure_path_exists(clone_to_dir)

    if is_url:
        # Build the name of the cached zipfile,
        # and prompt to delete if it already exists.
        identifier = zip_uri.rsplit('/', 1)[1]
        zip_path = os.path.join(clone_to_dir, identifier)

        if os.path.exists(zip_path):
            download = prompt_and_delete(zip_path,
                                         no_input=no_input,
                                         refresh=refresh)
        else:
            download = True

        if download:
            # (Re) download the zipfile
            r = requests.get(zip_uri, stream=True)
            with open(zip_path, 'wb') as f:
                for chunk in r.iter_content(chunk_size=1024):
                    if chunk:  # filter out keep-alive new chunks
                        f.write(chunk)
    else:
        # Just use the local zipfile as-is.
        zip_path = os.path.abspath(zip_uri)

    # Now unpack the repository. The zipfile will be unpacked
    # into a temporary directory
    try:
        zip_file = ZipFile(zip_path)

        if len(zip_file.namelist()) == 0:
            raise InvalidZipRepository(
                'Zip repository {} is empty'.format(zip_uri))

        # The first record in the zipfile should be the directory entry for
        # the archive. If it isn't a directory, there's a problem.
        first_filename = zip_file.namelist()[0]
        if not first_filename.endswith('/'):
            raise InvalidZipRepository('Zip repository {} does not include '
                                       'a top-level directory'.format(zip_uri))

        # Construct the final target directory
        project_name = first_filename[:-1]
        unzip_base = tempfile.mkdtemp()
        unzip_path = os.path.join(unzip_base, project_name)

        # Extract the zip file into the temporary directory
        try:
            zip_file.extractall(path=unzip_base)
        except RuntimeError:
            # File is password protected; try to get a password from the
            # environment; if that doesn't work, ask the user.
            if password is not None:
                try:
                    zip_file.extractall(path=unzip_base,
                                        pwd=password.encode('utf-8'))
                except RuntimeError:
                    raise InvalidZipRepository(
                        'Invalid password provided for protected repository')
            elif no_input:
                raise InvalidZipRepository(
                    'Unable to unlock password protected repository')
            else:
                retry = 0
                while retry is not None:
                    try:
                        password = read_repo_password('Repo password')
                        zip_file.extractall(path=unzip_base,
                                            pwd=password.encode('utf-8'))
                        retry = None
                    except RuntimeError:
                        retry += 1
                        if retry == 3:
                            raise InvalidZipRepository(
                                'Invalid password provided for protected repository'
                            )

    except BadZipFile:
        raise InvalidZipRepository(
            'Zip repository {} is not a valid zip archive:'.format(zip_uri))

    return unzip_path
示例#16
0
def clone(repo_url, checkout=None, clone_to_dir='.', no_input=False):
    """Clone a repo to the current directory.

    :param repo_url: Repo URL of unknown type.
    :param checkout: The branch, tag or commit ID to checkout after clone.
    :param clone_to_dir: The directory to clone to.
                         Defaults to the current directory.
    :param no_input: Suppress all user prompts when calling via API.
    :returns: str with path to the new directory of the repository.
    """
    # Ensure that clone_to_dir exists
    clone_to_dir = os.path.expanduser(clone_to_dir)
    make_sure_path_exists(clone_to_dir)

    # identify the repo_type
    repo_type, repo_url = identify_repo(repo_url)

    # check that the appropriate VCS for the repo_type is installed
    if not is_vcs_installed(repo_type):
        msg = "'{0}' is not installed.".format(repo_type)
        raise VCSNotInstalled(msg)

    repo_url = repo_url.rstrip('/')
    repo_name = os.path.split(repo_url)[1]
    if repo_type == 'git':
        repo_name = repo_name.split(':')[-1].rsplit('.git')[0]
        repo_dir = os.path.normpath(os.path.join(clone_to_dir, repo_name))
    elif repo_type == 'hg':
        repo_dir = os.path.normpath(os.path.join(clone_to_dir, repo_name))
    logger.debug('repo_dir is {0}'.format(repo_dir))

    if os.path.isdir(repo_dir):
        clone = prompt_and_delete(repo_dir, no_input=no_input)
    else:
        clone = True

    if clone:
        try:
            subprocess.check_output(  # nosec
                [repo_type, 'clone', repo_url],
                cwd=clone_to_dir,
                stderr=subprocess.STDOUT,
            )
            if checkout is not None:
                subprocess.check_output(  # nosec
                    [repo_type, 'checkout', checkout],
                    cwd=repo_dir,
                    stderr=subprocess.STDOUT,
                )
        except subprocess.CalledProcessError as clone_error:
            output = clone_error.output.decode('utf-8')
            if 'not found' in output.lower():
                raise RepositoryNotFound(
                    'The repository {} could not be found, '
                    'have you made a typo?'.format(repo_url))
            if any(error in output for error in BRANCH_ERRORS):
                raise RepositoryCloneFailed(
                    'The {} branch of repository {} could not found, '
                    'have you made a typo?'.format(checkout, repo_url))
            logger.error('git clone failed with error: %s', output)
            raise

    return repo_dir
示例#17
0
def clone(repo_url, checkout=None, clone_to_dir=".", no_input=False):
    """Clone a repo to the current directory.

    :param repo_url: Repo URL of unknown type.
    :param checkout: The branch, tag or commit ID to checkout after clone.
    :param clone_to_dir: The directory to clone to.
                         Defaults to the current directory.
    :param no_input: Suppress all user prompts when calling via API.
    """
    # Ensure that clone_to_dir exists
    clone_to_dir = os.path.expanduser(clone_to_dir)
    make_sure_path_exists(clone_to_dir)

    # identify the repo_type
    repo_type, repo_url = identify_repo(repo_url)

    # check that the appropriate VCS for the repo_type is installed
    if not is_vcs_installed(repo_type):
        msg = "'{0}' is not installed.".format(repo_type)
        raise VCSNotInstalled(msg)

    repo_url = repo_url.rstrip("/")
    tail = os.path.split(repo_url)[1]
    if repo_type == "git":
        repo_dir = os.path.normpath(
            os.path.join(clone_to_dir,
                         tail.rsplit(".git")[0]))
    elif repo_type == "hg":
        repo_dir = os.path.normpath(os.path.join(clone_to_dir, tail))
    logger.debug("repo_dir is {0}".format(repo_dir))

    if os.path.isdir(repo_dir):
        clone = prompt_and_delete(repo_dir, no_input=no_input)
    else:
        clone = True

    if clone:
        try:
            subprocess.check_output(
                [repo_type, "clone", repo_url],
                cwd=clone_to_dir,
                stderr=subprocess.STDOUT,
            )
            if checkout is not None:
                subprocess.check_output(
                    [repo_type, "checkout", checkout],
                    cwd=repo_dir,
                    stderr=subprocess.STDOUT,
                )
        except subprocess.CalledProcessError as clone_error:
            output = clone_error.output.decode("utf-8")
            if "not found" in output.lower():
                raise RepositoryNotFound(
                    "The repository {} could not be found, "
                    "have you made a typo?".format(repo_url))
            if any(error in output for error in BRANCH_ERRORS):
                raise RepositoryCloneFailed(
                    "The {} branch of repository {} could not found, "
                    "have you made a typo?".format(checkout, repo_url))
            raise

    return repo_dir