Exemple #1
0
    def list_unpushed_lfs_paths(self, client=None):
        """List paths tracked in lfs for a client."""
        client = client or self

        if (len(client.repo.remotes) < 1
                or not client.repo.active_branch.tracking_branch()):
            raise errors.ConfigurationError(
                'No git remote is configured for {} branch {}.'.format(
                    client.path, client.repo.active_branch.name) +
                'Cleaning the storage cache would lead to a loss of data as ' +
                'it is not on a server. Please see ' +
                'https://www.atlassian.com/git/tutorials/syncing for ' +
                'information on how to sync with a remote.')
        try:
            status = check_output(self._CMD_STORAGE_STATUS,
                                  cwd=client.path,
                                  encoding='UTF-8')
        except (KeyboardInterrupt, OSError) as e:
            raise errors.ParameterError(
                'Couldn\'t run \'git lfs\':\n{0}'.format(e))

        files = status.split('Objects to be committed:')[0].splitlines()[2:]
        files = [
            client.path / f.rsplit('(', 1)[0].strip() for f in files
            if f.strip()
        ]
        return files
Exemple #2
0
def detect_registry_url(client, auto_login=True):
    """Return a URL of the Docker registry."""
    repo = client.repo
    config = repo.config_reader()

    # Find registry URL in .git/config
    remote_url = None
    try:
        registry_url = config.get_value('renku', 'registry', None)
    except NoSectionError:
        registry_url = None

    remote_branch = repo.head.reference.tracking_branch()

    if remote_branch is not None:
        remote_name = remote_branch.remote_name
        config_section = 'renku "{remote_name}"'.format(
            remote_name=remote_name)
        try:
            registry_url = config.get_value(config_section, 'registry',
                                            registry_url)
        except NoSectionError:
            pass
        remote_url = repo.remotes[remote_name].url

    if registry_url:
        # Look in [renku] and [renku "{remote_name}"] for registry_url key.
        url = GitURL.parse(registry_url)
    elif remote_url:
        # Use URL based on remote configuration.
        url = GitURL.parse(remote_url)

        # Replace gitlab. with registry. unless running on gitlab.com.
        hostname_parts = url.hostname.split('.')
        if len(hostname_parts) > 2 and hostname_parts[0] == 'gitlab':
            hostname_parts = hostname_parts[1:]
        hostname = '.'.join(['registry'] + hostname_parts)
        url = attr.evolve(url, hostname=hostname)
    else:
        raise errors.ConfigurationError(
            'Configure renku.repository_url or Git remote.')

    if auto_login and url.username and url.password:
        try:
            subprocess.run([
                'docker',
                'login',
                url.hostname,
                '-u',
                url.username,
                '--password-stdin',
            ],
                           check=True,
                           input=url.password.encode('utf-8'))
        except subprocess.CalledProcessError:
            raise errors.AuthenticationError(
                'Check configuration of password or token in the registry URL')

    return url
Exemple #3
0
 def parse(cls, href):
     """Derive basic informations."""
     for regex in _REPOSITORY_URLS:
         matches = re.search(regex, href)
         if matches:
             return cls(href=href, regex=regex, **matches.groupdict())
     else:
         raise errors.ConfigurationError(
             '"{href} is not a valid Git remote.'.format(href=href))
Exemple #4
0
    def from_git(cls, git):
        """Create an instance from a Git repo."""
        git_config = git.config_reader()
        try:
            name = git_config.get_value('user', 'name', None)
            email = git_config.get_value('user', 'email', None)
        except (configparser.NoOptionError,
                configparser.NoSectionError):  # pragma: no cover
            raise errors.ConfigurationError(
                'The user name and email are not configured. '
                'Please use the "git config" command to configure them.\n\n'
                '\tgit config --global --add user.name "John Doe"\n'
                '\tgit config --global --add user.email '
                '"*****@*****.**"\n')

        # Check the git configuration.
        if not name:  # pragma: no cover
            raise errors.MissingUsername()
        if not email:  # pragma: no cover
            raise errors.MissingEmail()

        return cls(name=name, email=email)
Exemple #5
0
def get_user_info(git):
    """Get Git repository's owner name and email."""

    git_config = git.config_reader()
    try:
        name = git_config.get_value("user", "name", None)
        email = git_config.get_value("user", "email", None)
    except (configparser.NoOptionError,
            configparser.NoSectionError):  # pragma: no cover
        raise errors.ConfigurationError(
            "The user name and email are not configured. "
            'Please use the "git config" command to configure them.\n\n'
            '\tgit config --global --add user.name "John Doe"\n'
            "\tgit config --global --add user.email "
            '"*****@*****.**"\n')

    # Check the git configuration.
    if not name:  # pragma: no cover
        raise errors.MissingUsername()
    if not email:  # pragma: no cover
        raise errors.MissingEmail()

    return name, email
Exemple #6
0
def repo_sync(repo, message=None, remote=None, paths=None):
    """Commit and push paths."""
    origin = None
    saved_paths = []

    # get branch that's pushed
    if repo.active_branch.tracking_branch():
        pushed_branch = repo.active_branch.tracking_branch().name
    else:
        pushed_branch = repo.active_branch.name

    if remote:
        # get/setup supplied remote for pushing
        if repo.remotes:
            existing = next((r for r in repo.remotes if r.url == remote), None)
            if not existing:
                existing = next((r for r in repo.remotes if r.name == remote),
                                None)
            origin = next((r for r in repo.remotes if r.name == "origin"),
                          None)
            if existing:
                origin = existing
            elif origin:
                pushed_branch = uuid4().hex
                origin = repo.create_remote(pushed_branch, remote)
        if not origin:
            origin = repo.create_remote("origin", remote)
    elif not repo.active_branch.tracking_branch():
        # No remote set on branch, push to available remote if only a single
        # one is available
        if len(repo.remotes) == 1:
            origin = repo.remotes[0]
        else:
            raise errors.ConfigurationError(
                "No remote has been set up for the current branch")
    else:
        # get remote that's set up to track the local branch
        origin = repo.remotes[repo.active_branch.tracking_branch().remote_name]

    if paths:
        # commit uncommitted changes
        try:
            repo.git.add(*paths)
            saved_paths = [d.b_path for d in repo.index.diff("HEAD")]

            if not message:
                # Show saved files in message
                max_len = 100
                message = "Saved changes to: "
                paths_with_lens = reduce(
                    lambda c, x: c + [(x, c[-1][1] + len(x))], saved_paths,
                    [(None, len(message))])[1:]
                # limit first line to max_len characters
                message += " ".join(p if l < max_len else "\n\t" + p
                                    for p, l in paths_with_lens)

            repo.index.commit(message)
        except git.exc.GitCommandError as e:
            raise errors.GitError("Cannot commit changes") from e

    try:
        # push local changes to remote branch
        if origin.refs and repo.active_branch in origin.refs:
            origin.fetch()
            origin.pull(repo.active_branch)

        origin.push(repo.active_branch)
    except git.exc.GitCommandError as e:
        if "protected branches" not in e.stderr:
            raise errors.GitError("Cannot push changes") from e
        # push to new remote branch if original one is protected
        pushed_branch = uuid4().hex
        origin = repo.create_remote(pushed_branch, remote)
        origin.push(repo.active_branch)

    return saved_paths, pushed_branch
Exemple #7
0
def init(ctx, client, use_external_storage, path, name, template,
         template_source, template_ref, template_variables, description,
         print_manifest, force):
    """Initialize a project in PATH. Default is current path."""
    # verify dirty path
    if not is_path_empty(path) and not force and not print_manifest:
        raise errors.InvalidFileOperation(
            'Folder "{0}" is not empty. Please add --force '
            'flag to transform it into a Renku repository.'.format(str(path)))

    if not check_git_user_config():
        raise errors.ConfigurationError(
            'The user name and email are not configured. '
            'Please use the "git config" command to configure them.\n\n'
            '\tgit config --global --add user.name "John Doe"\n'
            '\tgit config --global --add user.email '
            '"*****@*****.**"\n')

    # select template source
    if template_source:
        click.echo('Fetching template from {0}@{1}... '.format(
            template_source, template_ref),
                   nl=False)
        template_folder = Path(mkdtemp())
        fetch_template(template_source, template_ref, template_folder)
        template_manifest = read_template_manifest(template_folder,
                                                   checkout=True)
        click.secho('OK', fg='green')
    else:
        template_folder = Path(
            pkg_resources.resource_filename('renku', 'templates'))
        template_manifest = read_template_manifest(template_folder)

    # select specific template
    repeat = False
    template_data = None
    if template:
        template_filtered = [
            template_elem for template_elem in template_manifest
            if template_elem['name'] == template
        ]
        if len(template_filtered) == 1:
            template_data = template_filtered[0]
        else:
            click.echo('The template "{0}" is not available.'.format(template))
            repeat = True

    if print_manifest:
        if template_data:
            click.echo(create_template_sentence([template_data]))
        else:
            click.echo(create_template_sentence(template_manifest))
        return

    if not template or repeat:
        templates = [template_elem for template_elem in template_manifest]
        if len(templates) == 1:
            template_data = templates[0]
        else:
            template_num = click.prompt(text=create_template_sentence(
                templates, True),
                                        type=click.IntRange(1, len(templates)),
                                        show_default=False,
                                        show_choices=False)
            template_data = templates[template_num - 1]

    # set local path and storage
    store_directory(path)
    if not client.use_external_storage:
        use_external_storage = False
    ctx.obj = client = attr.evolve(client,
                                   path=path,
                                   use_external_storage=use_external_storage)
    if not is_path_empty(path):
        from git import GitCommandError
        try:
            commit = client.find_previous_commit('*')
            branch_name = 'pre_renku_init_{0}'.format(commit.hexsha[:7])
            with client.worktree(path=path,
                                 branch_name=branch_name,
                                 commit=commit,
                                 merge_args=[
                                     '--no-ff', '-s', 'recursive', '-X',
                                     'ours', '--allow-unrelated-histories'
                                 ]):
                click.echo(
                    'Saving current data in branch {0}'.format(branch_name))
        except AttributeError:
            click.echo('Warning! Overwriting non-empty folder.')
        except GitCommandError as e:
            click.UsageError(e)

    # clone the repo
    template_path = template_folder / template_data['folder']
    click.echo('Initializing new Renku repository... ', nl=False)
    with client.lock:
        try:
            create_from_template(template_path, client, name, description,
                                 template_variables, force)
        except FileExistsError as e:
            raise click.UsageError(e)

    # Install git hooks
    from .githooks import install
    ctx.invoke(install, force=force)
Exemple #8
0
def init(
    ctx,
    client,
    external_storage_requested,
    path,
    name,
    template_id,
    template_index,
    template_source,
    template_ref,
    metadata,
    list_templates,
    force,
    describe,
    data_dir,
):
    """Initialize a project in PATH. Default is the current path."""
    # verify dirty path
    if not is_path_empty(path) and not force and not list_templates:
        existing_paths = [
            str(p.relative_to(path)) for p in Path(path).iterdir()
        ]
        existing_paths.sort()
        raise errors.InvalidFileOperation(
            f'Folder "{str(path)}" is not empty and contains the following files/directories:'
            + "".join((f"\n\t{e}" for e in existing_paths)) +
            "\nPlease add --force flag to transform it into a Renku repository."
        )

    data_dir = resolve_data_directory(data_dir, path)

    if not check_git_user_config():
        raise errors.ConfigurationError(
            "The user name and email are not configured. "
            'Please use the "git config" command to configure them.\n\n'
            '\tgit config --global --add user.name "John Doe"\n'
            "\tgit config --global --add user.email "
            '"*****@*****.**"\n')

    # select template source
    if template_source:
        click.echo("Fetching template from {0}@{1}... ".format(
            template_source, template_ref),
                   nl=False)
        template_folder = Path(mkdtemp())
        fetch_template(template_source, template_ref, template_folder)
        template_manifest = read_template_manifest(template_folder,
                                                   checkout=True)
        click.secho("OK", fg="green")
    else:
        template_folder = Path(
            pkg_resources.resource_filename("renku", "templates"))
        template_manifest = read_template_manifest(template_folder)
        template_source = "renku"

    # select specific template
    repeat = False
    template_data = None
    if template_id:
        if template_index:
            raise errors.ParameterError(
                "Use either --template-id or --template-index, not both",
                '"--template-index"')
        template_filtered = [
            template_elem for template_elem in template_manifest
            if template_elem["folder"] == template_id
        ]
        if len(template_filtered) == 1:
            template_data = template_filtered[0]
        else:
            click.echo(
                f'The template with id "{template_id}" is not available.')
            repeat = True

    if template_index or template_index == 0:
        if template_index > 0 and template_index <= len(template_manifest):
            template_data = template_manifest[template_index - 1]
        else:
            click.echo(
                f"The template at index {template_index} is not available.")
            repeat = True

    if list_templates:
        if template_data:
            click.echo(
                create_template_sentence([template_data], describe=describe))
        else:
            click.echo(
                create_template_sentence(template_manifest, describe=describe))
        return

    if repeat or not (template_id or template_index):
        templates = [template_elem for template_elem in template_manifest]
        if len(templates) == 1:
            template_data = templates[0]
        else:
            template_index = click.prompt(
                text=create_template_sentence(templates,
                                              describe=describe,
                                              instructions=True),
                type=click.IntRange(1, len(templates)),
                show_default=False,
                show_choices=False,
            )
            template_data = templates[template_index - 1]

        template_id = template_data["folder"]

    # verify variables have been passed
    template_variables = template_data.get("variables", {})
    template_variables_keys = set(template_variables.keys())
    input_parameters_keys = set(metadata.keys())
    for key in template_variables_keys - input_parameters_keys:
        value = click.prompt(
            text=(f'The template requires a value for "{key}" '
                  f"({template_variables[key]})"),
            default="",
            show_default=False,
        )
        metadata[key] = value
    useless_variables = input_parameters_keys - template_variables_keys
    if len(useless_variables) > 0:
        click.echo(INFO +
                   "These parameters are not used by the template and were "
                   "ignored:\n\t{}".format("\n\t".join(useless_variables)))
        for key in useless_variables:
            del metadata[key]

    # set local path and storage
    store_directory(path)
    if not client.external_storage_requested:
        external_storage_requested = False
    ctx.obj = client = attr.evolve(
        client,
        path=path,
        data_dir=data_dir,
        external_storage_requested=external_storage_requested)
    if not is_path_empty(path):
        from git import GitCommandError

        try:
            commit = client.find_previous_commit("*")
            branch_name = "pre_renku_init_{0}".format(commit.hexsha[:7])
            with client.worktree(
                    path=path,
                    branch_name=branch_name,
                    commit=commit,
                    merge_args=[
                        "--no-ff", "-s", "recursive", "-X", "ours",
                        "--allow-unrelated-histories"
                    ],
            ):
                click.echo(
                    "Saving current data in branch {0}".format(branch_name))
        except AttributeError:
            click.echo("Warning! Overwriting non-empty folder.")
        except GitCommandError as e:
            click.UsageError(e)

    # supply additional metadata
    metadata["__template_source__"] = template_source
    metadata["__template_ref__"] = template_ref
    metadata["__template_id__"] = template_id
    metadata["__namespace__"] = ""
    metadata["__sanitized_project_name__"] = ""
    metadata["__repository__"] = ""
    metadata["__project_slug__"] = ""

    # clone the repo
    template_path = template_folder / template_data["folder"]
    click.echo("Initializing new Renku repository... ", nl=False)
    with client.lock:
        try:
            create_from_template(
                template_path=template_path,
                client=client,
                name=name,
                metadata=metadata,
                force=force,
                data_dir=data_dir,
            )
        except FileExistsError as e:
            raise click.UsageError(e)

    # Install git hooks
    from .githooks import install

    ctx.invoke(install, force=force)
Exemple #9
0
def init(ctx, client, external_storage_requested, path, name, template_id,
         template_index, template_source, template_ref, parameter,
         list_templates, force, describe, data_dir):
    """Initialize a project in PATH. Default is the current path."""
    # verify dirty path
    if not is_path_empty(path) and not force and not list_templates:
        raise errors.InvalidFileOperation(
            'Folder "{0}" is not empty. Please add --force '
            'flag to transform it into a Renku repository.'.format(str(path)))

    data_dir = resolve_data_directory(data_dir, path)

    if not check_git_user_config():
        raise errors.ConfigurationError(
            'The user name and email are not configured. '
            'Please use the "git config" command to configure them.\n\n'
            '\tgit config --global --add user.name "John Doe"\n'
            '\tgit config --global --add user.email '
            '"*****@*****.**"\n')

    # select template source
    if template_source:
        click.echo('Fetching template from {0}@{1}... '.format(
            template_source, template_ref),
                   nl=False)
        template_folder = Path(mkdtemp())
        fetch_template(template_source, template_ref, template_folder)
        template_manifest = read_template_manifest(template_folder,
                                                   checkout=True)
        click.secho('OK', fg='green')
    else:
        template_folder = Path(
            pkg_resources.resource_filename('renku', 'templates'))
        template_manifest = read_template_manifest(template_folder)

    # select specific template
    repeat = False
    template_data = None
    if template_id:
        if template_index:
            raise errors.ParameterError(
                'Use either --template-id or --template-index, not both',
                '"--template-index"')
        template_filtered = [
            template_elem for template_elem in template_manifest
            if template_elem['folder'] == template_id
        ]
        if len(template_filtered) == 1:
            template_data = template_filtered[0]
        else:
            click.echo(
                f'The template with id "{template_id}" is not available.')
            repeat = True

    if template_index or template_index == 0:
        if template_index > 0 and template_index <= len(template_manifest):
            template_data = template_manifest[template_index - 1]
        else:
            click.echo(
                f'The template at index {template_index} is not available.')
            repeat = True

    if list_templates:
        if template_data:
            click.echo(
                create_template_sentence([template_data], describe=describe))
        else:
            click.echo(
                create_template_sentence(template_manifest, describe=describe))
        return

    if repeat or not (template_id or template_index):
        templates = [template_elem for template_elem in template_manifest]
        if len(templates) == 1:
            template_data = templates[0]
        else:
            template_num = click.prompt(text=create_template_sentence(
                templates, describe=describe, instructions=True),
                                        type=click.IntRange(1, len(templates)),
                                        show_default=False,
                                        show_choices=False)
            template_data = templates[template_num - 1]

    # verify variables have been passed
    template_variables = template_data.get('variables', {})
    template_variables_keys = set(template_variables.keys())
    input_parameters_keys = set(parameter.keys())
    for key in (template_variables_keys - input_parameters_keys):
        value = click.prompt(
            text=(f'The template requires a value for "{key}" '
                  f'({template_variables[key]})'),
            default='',
            show_default=False)
        parameter[key] = value
    useless_variables = input_parameters_keys - template_variables_keys
    if (len(useless_variables) > 0):
        click.echo(INFO +
                   'These parameters are not used by the template and were '
                   'ignored:\n\t{}'.format('\n\t'.join(useless_variables)))
        for key in useless_variables:
            del parameter[key]

    # set local path and storage
    store_directory(path)
    if not client.external_storage_requested:
        external_storage_requested = False
    ctx.obj = client = attr.evolve(
        client,
        path=path,
        data_dir=data_dir,
        external_storage_requested=external_storage_requested)
    if not is_path_empty(path):
        from git import GitCommandError
        try:
            commit = client.find_previous_commit('*')
            branch_name = 'pre_renku_init_{0}'.format(commit.hexsha[:7])
            with client.worktree(path=path,
                                 branch_name=branch_name,
                                 commit=commit,
                                 merge_args=[
                                     '--no-ff', '-s', 'recursive', '-X',
                                     'ours', '--allow-unrelated-histories'
                                 ]):
                click.echo(
                    'Saving current data in branch {0}'.format(branch_name))
        except AttributeError:
            click.echo('Warning! Overwriting non-empty folder.')
        except GitCommandError as e:
            click.UsageError(e)

    # clone the repo
    template_path = template_folder / template_data['folder']
    click.echo('Initializing new Renku repository... ', nl=False)
    with client.lock:
        try:
            create_from_template(template_path=template_path,
                                 client=client,
                                 name=name,
                                 metadata=parameter,
                                 force=force,
                                 data_dir=data_dir)
        except FileExistsError as e:
            raise click.UsageError(e)

    # Install git hooks
    from .githooks import install
    ctx.invoke(install, force=force)