Пример #1
0
def symlink_clowder_yaml(source: Path, target: Path) -> None:
    """Force symlink creation

    :param Path source: File to create symlink pointing to
    :param Path target: Symlink location
    :raise ExistingFileError:
    :raise MissingSourceError:
    """

    if not target.is_symlink() and target.is_file():
        raise ExistingFileError(
            f"Found non-symlink file {fmt.path(target)} at target path")
    if not Path(target.parent / source).exists():
        raise MissingSourceError(
            f"Symlink source {fmt.path(source)} appears to be missing")
    if target.is_symlink():
        remove_file(target)
    try:
        path = target.parent
        fd = os.open(path, os.O_DIRECTORY)
        os.symlink(source, target, dir_fd=fd)
        os.close(fd)
    except OSError:
        LOG.error(
            f"Failed to symlink file {fmt.path(target)} -> {fmt.path(source)}")
        raise
Пример #2
0
    def create_clowder_repo(self, url: str, branch: str, depth: int = 0) -> None:
        """Clone clowder git repo from url at path

        :param str url: URL of repo
        :param str branch: Branch name
        :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer
        :raise ExistingFileError:
        """

        if existing_git_repo(self.repo_path):
            # TODO: Throw error if repo doesn't match one trying to create
            return

        if self.repo_path.is_dir():
            try:
                self.repo_path.rmdir()
            except OSError:
                LOG.error(f"Directory already exists at {fmt.path(self.repo_path)}")
                raise

        if self.repo_path.is_symlink():
            remove_file(self.repo_path)
        else:
            from clowder.environment import ENVIRONMENT
            if ENVIRONMENT.existing_clowder_repo_file_error:
                raise ENVIRONMENT.existing_clowder_repo_file_error

        self._init_repo()
        self._create_remote(self.remote, url, remove_dir=True)
        self._checkout_new_repo_branch(branch, depth)
Пример #3
0
def main() -> None:
    """Clowder command CLI main function"""

    try:
        parser = create_parsers()
        argcomplete.autocomplete(parser)
        args = parser.parse_args()
        if 'projects' in args:
            if isinstance(args.projects, str):
                args.projects = [args.projects]
        if args.debug:
            LOG.level = LOG.DEBUG
        args.func(args)
    except CalledProcessError as err:
        LOG.error(error=err)
        exit(err.returncode)
    except OSError as err:
        LOG.error(error=err)
        exit(err.errno)
    except SystemExit as err:
        if err.code == 0:
            exit()
        LOG.error(error=err)
        exit(err.code)
    except KeyboardInterrupt:
        LOG.error('** KeyboardInterrupt **')
        exit(1)
    except BaseException as err:
        LOG.error(error=err)
        exit(1)
Пример #4
0
    def _get_remote_tag(self,
                        tag: str,
                        remote: str,
                        depth: int = 0,
                        remove_dir: bool = False) -> Optional[Tag]:
        """Returns Tag object

        :param str tag: Tag name
        :param str remote: Remote name
        :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer
        :param bool remove_dir: Whether to remove the directory if commands fail
        :return: GitPython Tag object if it exists, otherwise None
        """

        self._remote(remote, remove_dir=remove_dir)
        self.fetch(remote,
                   depth=depth,
                   ref=GitRef(tag=tag),
                   remove_dir=remove_dir)

        try:
            return self.repo.tags[tag]
        except (GitError, IndexError) as err:
            LOG.error(f'No existing tag {fmt.ref(tag)}')
            if remove_dir:
                remove_directory(self.repo_path, check=False)
                raise
            LOG.debug(error=err)
            return None
        except BaseException:
            LOG.error('Failed to get tag')
            if remove_dir:
                remove_directory(self.repo_path, check=False)
            raise
Пример #5
0
    def prune_branch_local(self, branch: str, force: bool) -> None:
        """Prune local branch

        :param str branch: Branch name to delete
        :param bool force: Force delete branch
        """

        if branch not in self.repo.heads:
            CONSOLE.stdout(f" - Local branch {fmt.ref(branch)} doesn't exist")
            return

        prune_branch = self.repo.heads[branch]
        if self.repo.head.ref == prune_branch:
            try:
                CONSOLE.stdout(f' - Checkout ref {fmt.ref(self.default_ref.short_ref)}')
                self.repo.git.checkout(self.default_ref.short_ref)
            except GitError:
                LOG.error(f'Failed to checkout ref {fmt.ref(self.default_ref.short_ref)}')
                raise

        try:
            CONSOLE.stdout(f' - Delete local branch {fmt.ref(branch)}')
            self.repo.delete_head(branch, force=force)
        except GitError:
            LOG.error(f'Failed to delete local branch {fmt.ref(branch)}')
            raise
Пример #6
0
    def _create_branch_local_tracking(self,
                                      branch: str,
                                      remote: str,
                                      depth: int,
                                      fetch: bool = True,
                                      remove_dir: bool = False) -> None:
        """Create and checkout tracking branch

        :param str branch: Branch name
        :param str remote: Remote name
        :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer
        :param bool fetch: Whether to fetch before creating branch
        :param bool remove_dir: Whether to remove the directory if commands fail
        """

        origin = self._remote(remote, remove_dir=remove_dir)
        if fetch:
            self.fetch(remote,
                       depth=depth,
                       ref=GitRef(branch=branch),
                       remove_dir=remove_dir)

        try:
            CONSOLE.stdout(f' - Create branch {fmt.ref(branch)}')
            self.repo.create_head(branch, origin.refs[branch])
        except BaseException:
            LOG.error(f'Failed to create branch {fmt.ref(branch)}')
            if remove_dir:
                remove_directory(self.repo_path, check=False)
            raise
        else:
            self._set_tracking_branch(remote, branch, remove_dir=remove_dir)
            self._checkout_branch_local(branch, remove_dir=remove_dir)
Пример #7
0
    def _checkout_new_repo_tag(self,
                               tag: str,
                               remote: str,
                               depth: int,
                               remove_dir: bool = False) -> None:
        """Checkout tag or fail and delete repo if it doesn't exist

        :param str tag: Tag name
        :param str remote: Remote name
        :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer
        :param bool remove_dir: Whether to remove the directory if commands fail
        """

        remote_tag = self._get_remote_tag(tag,
                                          remote,
                                          depth=depth,
                                          remove_dir=remove_dir)
        if remote_tag is None:
            return

        try:
            CONSOLE.stdout(f' - Checkout tag {fmt.ref(tag)}')
            self.repo.git.checkout(remote_tag)
        except BaseException:
            LOG.error(f'Failed to checkout tag {fmt.ref(tag)}')
            if remove_dir:
                remove_directory(self.repo_path, check=False)
            raise
Пример #8
0
    def current_timestamp(self) -> str:
        """Current timestamp of HEAD commit"""

        try:
            return self.repo.git.log('-1', '--format=%cI')
        except GitError:
            LOG.error('Failed to find current timestamp')
            raise
Пример #9
0
    def install_lfs_hooks(self) -> None:
        """Install git lfs hooks"""

        CONSOLE.stdout(" - Update git lfs hooks")
        try:
            self.repo.git.lfs('install', '--local')
        except GitError:
            LOG.error('Failed to update git lfs hooks')
            raise
Пример #10
0
    def pull(self) -> None:
        """Pull upstream changes"""

        try:
            CONSOLE.stdout(' - Pull latest changes')
            CONSOLE.stdout(self.repo.git.pull())
        except GitError:
            LOG.error('Failed to pull latest changes')
            raise
Пример #11
0
    def pull_lfs(self) -> None:
        """Pull lfs files"""

        try:
            CONSOLE.stdout(' - Pull git lfs files')
            self.repo.git.lfs('pull')
        except GitError:
            LOG.error('Failed to pull git lfs files')
            raise
Пример #12
0
    def push(self) -> None:
        """Push changes"""

        try:
            CONSOLE.stdout(' - Push local changes')
            CONSOLE.stdout(self.repo.git.push())
        except GitError:
            LOG.error('Failed to push local changes')
            raise
Пример #13
0
    def _abort_rebase(self) -> None:
        """Abort rebase"""

        if not self._is_rebase_in_progress:
            return

        try:
            self.repo.git.rebase('--abort')
        except GitError:
            LOG.error('Failed to abort rebase')
            raise
Пример #14
0
    def _clean(self, args: str) -> None:
        """Clean git directory

        :param str args: Git clean args
        """

        try:
            self.repo.git.clean(args)
        except GitError:
            LOG.error('Failed to clean git repo')
            raise
Пример #15
0
    def _create_branch_local(self, branch: str) -> None:
        """Create local branch

        :param str branch: Branch name
        """

        try:
            CONSOLE.stdout(f' - Create branch {fmt.ref(branch)}')
            self.repo.create_head(branch)
        except GitError:
            LOG.error(f'Failed to create branch {fmt.ref(branch)}')
            raise
Пример #16
0
    def commit(self, message: str) -> None:
        """Commit current changes

        :param str message: Git commit message
        """

        try:
            CONSOLE.stdout(' - Commit current changes')
            CONSOLE.stdout(self.repo.git.commit(message=message))
        except GitError:
            LOG.error('Failed to commit current changes')
            raise
Пример #17
0
    def status_verbose(self) -> None:
        """Print git status

        Equivalent to: ``git status -vv``
        """

        command = 'git status -vv'
        CONSOLE.stdout(fmt.command(command))
        try:
            execute_command(command, self.repo_path)
        except CalledProcessError:
            LOG.error('Failed to print verbose status')
            raise
Пример #18
0
    def _create_repo(self) -> Repo:
        """Create Repo instance for self.repo_path

        :return: GitPython Repo instance
        """

        try:
            return Repo(self.repo_path)
        except GitError:
            LOG.error(
                f"Failed to create Repo instance for {fmt.path(self.repo_path)}"
            )
            raise
Пример #19
0
    def _print_has_remote_branch_message(self, branch: str) -> None:
        """Print output message for existing remote branch

        :param str branch: Branch name
        """

        try:
            self.repo.git.config('--get', 'branch.' + branch + '.merge')
            CONSOLE.stdout(
                f' - Tracking branch {fmt.ref(branch)} already exists')
        except GitError:
            LOG.error(f'Remote branch {fmt.ref(branch)} already exists')
            raise
Пример #20
0
def remove_directory(dir_path: Path, check: bool = True) -> None:
    """Remove directory at path

    :param str dir_path: Path to directory to remove
    :param bool check: Whether to raise errors
    """

    try:
        shutil.rmtree(dir_path)
    except shutil.Error:
        LOG.error(f"Failed to remove directory {fmt.path(dir_path)}")
        if check:
            raise
Пример #21
0
def _print_yaml(yaml_file: Path) -> None:
    """Private print current clowder yaml file

    :param Path yaml_file: Path to yaml file
    """

    try:
        with yaml_file.open() as raw_file:
            contents = raw_file.read()
            CONSOLE.stdout(contents.rstrip())
    except IOError:
        LOG.error(f"Failed to open file '{yaml_file}'")
        raise
Пример #22
0
    def _find_rev_by_timestamp(self, timestamp: str, ref: str) -> str:
        """Find rev by timestamp

        :param str timestamp: Commit ref timestamp
        :param str ref: Reference ref
        :return: Commit sha at or before timestamp
        """

        try:
            return self.repo.git.log('-1', '--format=%H',
                                     '--before=' + timestamp, ref)
        except GitError:
            LOG.error('Failed to find revision from timestamp')
            raise
Пример #23
0
    def _is_tracking_branch(self, branch: str) -> bool:
        """Check if branch is a tracking branch

        :param str branch: Branch name
        :return: True, if branch has a tracking relationship
        """

        try:
            local_branch = self.repo.heads[branch]
            tracking_branch = local_branch.tracking_branch()
            return True if tracking_branch else False
        except GitError:
            LOG.error(f'No existing branch {fmt.ref(branch)}')
            raise
Пример #24
0
    def _pull(self, remote: str, branch: str) -> None:
        """Pull from remote branch

        :param str remote: Remote name
        :param str branch: Branch name
        """

        CONSOLE.stdout(f' - Pull from {fmt.remote(remote)} {fmt.ref(branch)}')
        try:
            execute_command(f"git pull {remote} {branch}", self.repo_path)
        except CalledProcessError:
            LOG.error(
                f'Failed to pull from {fmt.remote(remote)} {fmt.ref(branch)}')
            raise
Пример #25
0
    def git_config_add_local(self, variable: str, value: str) -> None:
        """Add local git config value for given variable key

        :param str variable: Fully qualified git config variable
        :param str value: Git config value
        """

        try:
            self.repo.git.config('--local', '--add', variable, value)
        except GitError:
            LOG.error(
                f'Failed to add local git config value {value} for variable {variable}'
            )
            raise
Пример #26
0
def validate_yaml_file(parsed_yaml: dict, file_path: Path) -> None:
    """Validate yaml file

    :param dict parsed_yaml: Parsed yaml dictionary
    :param Path file_path: Path to yaml file
    """

    json_schema = _load_json_schema(file_path.stem)
    try:
        jsonschema.validate(parsed_yaml, json_schema)
    except jsonschema.exceptions.ValidationError:
        LOG.error(
            f'Yaml json schema validation failed {fmt.invalid_yaml(file_path.name)}\n'
        )
        raise
Пример #27
0
    def add(self, files: str) -> None:
        """Add files to git index

        :param str files: Files to git add
        :raise:
        """

        CONSOLE.stdout(' - Add files to git index')
        try:
            CONSOLE.stdout(self.repo.git.add(files))
        except GitError:
            LOG.error("Failed to add files to git index")
            raise
        else:
            self.status_verbose()
Пример #28
0
    def _remote(self, remote: str, remove_dir: bool = False) -> Remote:
        """Get GitPython Remote instance

        :param str remote: Remote name
        :param bool remove_dir: Whether to remove the directory if commands fail
        :return: GitPython Remote instance
        """

        try:
            return self.repo.remotes[remote]
        except GitError:
            LOG.error(f'No existing remote {fmt.remote(remote)}')
            if remove_dir:
                remove_directory(self.repo_path, check=False)
            raise
Пример #29
0
    def _checkout_sha(self, sha: str) -> None:
        """Checkout commit by sha

        :param str sha: Commit sha
        """

        try:
            if self.repo.head.commit.hexsha == sha:
                CONSOLE.stdout(' - On correct commit')
                return
            CONSOLE.stdout(f' - Checkout commit {fmt.ref(sha)}')
            self.repo.git.checkout(sha)
        except GitError:
            LOG.error(f'Failed to checkout commit {fmt.ref(sha)}')
            raise
Пример #30
0
def yaml_string(yaml_output: dict) -> str:
    """Return yaml string from python data structures

    :param dict yaml_output: YAML python object
    :return: YAML as a string
    """

    try:
        return pyyaml.safe_dump(yaml_output,
                                default_flow_style=False,
                                indent=2,
                                sort_keys=False)
    except pyyaml.YAMLError:
        LOG.error(f"Failed to dump yaml file contents", )
        raise