Skip to content

kraken.std.git

kraken.std.git

Tools for Git versioned projects.

GitVersion dataclass

Represents a "git version" that has a major, minor and patch version and optionally a commit distance.

Source code in kraken/std/git/version.py
@dataclasses.dataclass
class GitVersion:
    """Represents a "git version" that has a major, minor and patch version and optionally a commit distance."""

    @dataclasses.dataclass
    class PreRelease:
        @enum.unique
        class Kind(str, enum.Enum):
            ALPHA = "alpha"
            BETA = "beta"
            RC = "rc"

        kind: Kind
        value: int

    @dataclasses.dataclass
    class CommitDistance:
        value: int
        sha: str

    major: int
    minor: int
    patch: int
    pre_release: PreRelease | None
    distance: CommitDistance | None
    dirty: bool

    @staticmethod
    def parse(value: str) -> GitVersion:
        GIT_VERSION_REGEX = r"^(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta|rc).(\d+))?(?:-(\d+)-g(\w+))?(-dirty)?$"
        match = re.match(GIT_VERSION_REGEX, value)
        if not match:
            raise ValueError(f"not a valid GitVersion: {value!r}")
        if match.group(4):
            pre_release = GitVersion.PreRelease(
                kind=GitVersion.PreRelease.Kind(match.group(4)), value=int(match.group(5))
            )
        else:
            pre_release = None
        if match.group(6):
            distance = GitVersion.CommitDistance(value=int(match.group(6)), sha=match.group(7))
        else:
            distance = None
        return GitVersion(
            major=int(match.group(1)),
            minor=int(match.group(2)),
            patch=int(match.group(3)),
            pre_release=pre_release,
            distance=distance,
            dirty=match.group(8) is not None,
        )

    def format(self, distance: bool = True, sha: bool = True, dirty: bool = False) -> str:
        result = f"{self.major}.{self.minor}.{self.patch}"
        if self.pre_release:
            result = f"{result}-{self.pre_release.kind.value}.{self.pre_release.value}"
        if self.distance and distance:
            result = f"{result}-{self.distance.value}"
            if sha:
                result = f"{result}-g{self.distance.sha}"
            if self.dirty and dirty:
                result = f"{result}-dirty"
        return result

dump_gitconfig

dump_gitconfig(data: dict[str, dict[str, str]]) -> str

Formats a Git configuration file.

Source code in kraken/std/git/config.py
def dump_gitconfig(data: dict[str, dict[str, str]]) -> str:
    """Formats a Git configuration file."""

    parser = configparser.RawConfigParser()
    for section, values in data.items():
        parser.add_section(section)
        for key, value in values.items():
            parser.set(section, key, value)
    fp = io.StringIO()
    parser.write(fp)
    return fp.getvalue()

git_describe

git_describe(
    path: Path | None, tags: bool = True, dirty: bool = True
) -> str

Describe a repository with tags.

:param path: The directory in which to describe. :param tags: Whether to include tags (adds the --tags flag). :param dirty: Whether to include if the directory tree is dirty (adds the --dirty flag). :raise ValueError: If git describe failed. :return: The Git head description.

Source code in kraken/std/git/version.py
def git_describe(path: Path | None, tags: bool = True, dirty: bool = True) -> str:
    """Describe a repository with tags.

    :param path: The directory in which to describe.
    :param tags: Whether to include tags (adds the `--tags` flag).
    :param dirty: Whether to include if the directory tree is dirty (adds the `--dirty` flag).
    :raise ValueError: If `git describe` failed.
    :return: The Git head description.
    """

    command = ["git", "describe"]
    if tags:
        command.append("--tags")
    if dirty:
        command.append("--dirty")
    try:
        return sp.check_output(command, cwd=path).decode().strip()
    except sp.CalledProcessError:
        count = int(sp.check_output(["git", "rev-list", "HEAD", "--count"], cwd=path).decode().strip())
        short_rev = sp.check_output(["git", "rev-parse", "--short", "HEAD"], cwd=path).decode().strip()
        return f"0.0.0-{count}-g{short_rev}"

gitignore_extend

gitignore_extend(
    *,
    project: Project | None = None,
    patterns: Sequence[str],
    dedup: bool = True
) -> None

Extend the Gitignore task's generated content section by the given patterns.

Parameters:

Name Type Description Default
project Project | None

The project to look for the Gitignore task configuraton in. If it is not specified, it will be searched in the currently active project and any of its parents (often the Gitignore tasks only exist on the root project).

None
patterns Sequence[str]

The patterns to add to the config.

required
dedup bool

If enabled, do not add any patterns that are already present.

True
Source code in kraken/std/git/__init__.py
def gitignore_extend(
    *,
    project: Project | None = None,
    patterns: Sequence[str],
    dedup: bool = True,
) -> None:
    """
    Extend the Gitignore task's generated content section by the given *pattern*s.

    Args:
        project: The project to look for the Gitignore task configuraton in. If it is not specified, it will be
                 searched in the currently active project and any of its parents (often the Gitignore tasks only exist
                 on the root project).
        patterns: The patterns to add to the config.
        dedup: If enabled, do not add any patterns that are already present.
    """

    if project is None:
        projects = []
        project = Project.current()
        while project:
            projects.append(project)
            project = project.parent

    else:
        projects = [project]

    task: tasks.GitignoreSyncTask | None
    for project in projects:
        task = next((t for t in project.tasks().values() if isinstance(t, tasks.GitignoreSyncTask)), None)
        if task is not None:
            break

    if task is None:
        raise ValueError("Could not find GitignoreSyncTask")

    task.generated_content.setmap(lambda x: [*x, *(p for p in patterns if p not in x)] if dedup else [*x, *patterns])

load_gitconfig

load_gitconfig(
    file: TextIO | Path | str,
) -> dict[str, dict[str, str]]

Parses a Git configuration file.

Source code in kraken/std/git/config.py
def load_gitconfig(file: TextIO | Path | str) -> dict[str, dict[str, str]]:
    """Parses a Git configuration file."""

    if isinstance(file, str):
        return load_gitconfig(io.StringIO(file))
    elif isinstance(file, Path):
        with file.open() as fp:
            return load_gitconfig(fp)

    parser = configparser.RawConfigParser(strict=False)
    parser.read_file(file)
    result = dict(parser._sections)  # type: ignore[attr-defined]
    for k in result:
        result[k] = dict(parser._defaults, **result[k])  # type: ignore[attr-defined]
        result[k].pop("__name__", None)
    return result