Skip to content

Commit

Permalink
feat: Replace manual subprocess calls with GitPython usage. (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
EdgyEdgemond authored Jul 8, 2024
1 parent bbb3a2d commit d5541bd
Show file tree
Hide file tree
Showing 17 changed files with 279 additions and 259 deletions.
5 changes: 0 additions & 5 deletions Makefile

This file was deleted.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ new features.
pip install changelog-gen
```

or clone this repo and install with poetry.
or clone this repo and install with invoke/poetry.

```bash
poetry install
invoke install-dev
```

## Contributing

This project uses pre-commit hooks, please run `pre-commit install` after
cloning and installing dev dependencies.
This project uses pre-commit hooks, please run `invoke install-dev` after
cloning to install dev dependencies and commit hooks.
58 changes: 26 additions & 32 deletions changelog_gen/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import platform
import shlex
import subprocess
import time
from datetime import datetime, timezone
from pathlib import Path
from tempfile import NamedTemporaryFile
Expand All @@ -27,6 +28,7 @@
from changelog_gen.cli import util
from changelog_gen.extractor import extract_version_tag
from changelog_gen.post_processor import per_issue_post_process
from changelog_gen.util import timer
from changelog_gen.vcs import Git
from changelog_gen.version import BumpVersion

Expand All @@ -42,6 +44,7 @@
tempfile_prefix = "_tmp_changelog"


@timer
def setup_logging(verbose: int = 0) -> None:
"""Configure the logging."""
logging.basicConfig(
Expand Down Expand Up @@ -86,6 +89,7 @@ def _callback(
app = typer.Typer(name="changelog", callback=_callback)


@timer
def process_info(info: dict, cfg: config.Config, *, dry_run: bool) -> None:
"""Process git info and raise on invalid state."""
if dry_run:
Expand Down Expand Up @@ -185,6 +189,7 @@ def gen( # noqa: PLR0913
Read release notes and generate a new CHANGELOG entry for the current version.
"""
start = time.time()
setup_logging(verbose)
cfg = config.read(
release=release,
Expand All @@ -206,9 +211,12 @@ def gen( # noqa: PLR0913
_gen(cfg, version_part, version_tag, dry_run=dry_run, interactive=interactive, include_all=include_all)
except errors.ChangelogException as ex:
logger.error("%s", ex) # noqa: TRY400
logger.debug("Run time (error) %f", (time.time() - start) * 1000)
raise typer.Exit(code=1) from ex
logger.debug("Run time %f", (time.time() - start) * 1000)


@timer
def create_with_editor(content: str, extension: writer.Extension) -> str:
"""Open temporary file in editor to allow modifications."""
editor = util.get_editor()
Expand Down Expand Up @@ -243,6 +251,7 @@ def create_with_editor(content: str, extension: writer.Extension) -> str:
return content


@timer
def _gen( # noqa: PLR0913
cfg: config.Config,
version_part: str | None = None,
Expand All @@ -252,8 +261,8 @@ def _gen( # noqa: PLR0913
interactive: bool = True,
include_all: bool = False,
) -> None:
bv = BumpVersion(verbose=cfg.verbose, dry_run=dry_run)
git = Git(dry_run=dry_run)
bv = BumpVersion(verbose=cfg.verbose, dry_run=dry_run, allow_dirty=cfg.allow_dirty)
git = Git(dry_run=dry_run, commit=cfg.commit)

extension = util.detect_extension()

Expand All @@ -263,21 +272,21 @@ def _gen( # noqa: PLR0913

process_info(git.get_current_info(), cfg, dry_run=dry_run)

version_info_ = bv.get_version_info("patch")
e = extractor.ReleaseNoteExtractor(cfg=cfg, git=git, dry_run=dry_run, include_all=include_all)
sections = e.extract(version_info_["current"])
version_info_ = bv.get_version_info(version_part or "patch")
current = version_info_["current"]
if version_part:
version_tag = version_info_["new"]

e = extractor.ChangeExtractor(cfg=cfg, git=git, dry_run=dry_run, include_all=include_all)
sections = e.extract(current)

unique_issues = e.unique_issues(sections)
if not unique_issues and cfg.reject_empty:
logger.error("No changes present and reject_empty configured.")
raise typer.Exit(code=0)

if version_part is not None:
version_info_ = bv.get_version_info(version_part)
version_tag = version_info_["new"]

if version_tag is None:
version_tag = extract_version_tag(sections, cfg, bv)
version_tag = extract_version_tag(sections, cfg, current, bv)

version_string = cfg.version_string.format(new_version=version_tag)

Expand All @@ -295,29 +304,11 @@ def _gen( # noqa: PLR0913
logger.error(changes)
w.content = changes.split("\n")[2:-2]

processed = _finalise(w, version_tag, extension, cfg, dry_run=dry_run)

post_process = cfg.post_process
if post_process and processed:
unique_issues = [r for r in unique_issues if not r.startswith("__")]
per_issue_post_process(post_process, sorted(unique_issues), version_tag, dry_run=dry_run)


def _finalise(
writer: writer.BaseWriter,
version_tag: str,
extension: writer.Extension,
cfg: config.Config,
*,
dry_run: bool,
) -> bool:
bv = BumpVersion(verbose=cfg.verbose, dry_run=dry_run, allow_dirty=cfg.allow_dirty)
git = Git(dry_run=dry_run, commit=cfg.commit)

processed = False
if dry_run or typer.confirm(
f"Write CHANGELOG for suggested version {version_tag}",
):
writer.write()
w.write()

paths = [f"CHANGELOG.{extension.value}"]
git.commit(version_tag, paths)
Expand All @@ -329,6 +320,9 @@ def _finalise(
git.revert()
logger.error("Error creating release: %s", str(e)) # noqa: TRY400
raise typer.Exit(code=1) from e
return True
processed = True

return False
post_process = cfg.post_process
if post_process and processed:
unique_issues = [r for r in unique_issues if not r.startswith("__")]
per_issue_post_process(post_process, sorted(unique_issues), version_tag, dry_run=dry_run)
5 changes: 5 additions & 0 deletions changelog_gen/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import rtoml

from changelog_gen import errors
from changelog_gen.util import timer


@dataclasses.dataclass
Expand Down Expand Up @@ -125,6 +126,7 @@ def to_dict(self: Config) -> dict:
return dataclasses.asdict(self)


@timer
def _process_overrides(overrides: dict) -> tuple[dict, PostProcessConfig | None]:
"""Process provided overrides.
Expand All @@ -145,6 +147,7 @@ def _process_overrides(overrides: dict) -> tuple[dict, PostProcessConfig | None]
return overrides, post_process


@timer
def _process_pyproject(pyproject: Path) -> dict:
cfg = {}
with pyproject.open() as f:
Expand All @@ -159,12 +162,14 @@ def _process_pyproject(pyproject: Path) -> dict:
return data["tool"]["changelog_gen"]


@timer
def check_deprecations(cfg: dict) -> None: # noqa: ARG001
"""Check parsed configuration dict for deprecated features."""
# No current deprecations
return


@timer
def read(**kwargs) -> Config:
"""Read configuration from local environment.
Expand Down
15 changes: 10 additions & 5 deletions changelog_gen/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import typing
from collections import defaultdict

from changelog_gen.util import timer

if typing.TYPE_CHECKING:
from changelog_gen import config
from changelog_gen.vcs import Git
Expand Down Expand Up @@ -35,9 +37,10 @@ def __lt__(self: typing.Self, other: Change) -> bool: # noqa: D105
SectionDict = dict[str, dict[str, Change]]


class ReleaseNoteExtractor:
"""Parse release notes and generate section dictionaries."""
class ChangeExtractor:
"""Parse commit logs and generate section dictionaries."""

@timer
def __init__(
self: typing.Self,
cfg: config.Config,
Expand All @@ -53,6 +56,7 @@ def __init__(
self.type_headers["_misc"] = "Miscellaneous"
self.git = git

@timer
def _extract_commit_logs(
self: typing.Self,
sections: dict[str, dict],
Expand Down Expand Up @@ -133,6 +137,7 @@ def _extract_commit_logs(
else:
logger.debug(" Skipping commit log (not conventional): %s", log.strip())

@timer
def extract(self: typing.Self, current_version: str) -> SectionDict:
"""Iterate over release note files extracting sections and issues."""
sections = defaultdict(dict)
Expand All @@ -141,6 +146,7 @@ def extract(self: typing.Self, current_version: str) -> SectionDict:

return sections

@timer
def unique_issues(self: typing.Self, sections: SectionDict) -> list[str]:
"""Generate unique list of issue references."""
issue_refs = set()
Expand All @@ -153,7 +159,8 @@ def unique_issues(self: typing.Self, sections: SectionDict) -> list[str]:
return sorted(issue_refs)


def extract_version_tag(sections: SectionDict, cfg: config.Config, bv: BumpVersion) -> str:
@timer
def extract_version_tag(sections: SectionDict, cfg: config.Config, current: str, bv: BumpVersion) -> str:
"""Generate new version tag based on changelog sections.
Breaking changes: major
Expand All @@ -163,8 +170,6 @@ def extract_version_tag(sections: SectionDict, cfg: config.Config, bv: BumpVersi
"""
logger.warning("Detecting semver from changes.")
semver_mapping = cfg.semver_mappings
version_info_ = bv.get_version_info("patch")
current = version_info_["current"]

semvers = ["patch", "minor", "major"]
semver = "patch"
Expand Down
3 changes: 3 additions & 0 deletions changelog_gen/post_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import httpx
import typer

from changelog_gen.util import timer

if typing.TYPE_CHECKING:
from changelog_gen.config import PostProcessConfig

Expand Down Expand Up @@ -56,6 +58,7 @@ def make_client(cfg: PostProcessConfig) -> httpx.Client:
)


@timer
def per_issue_post_process(
cfg: PostProcessConfig,
issue_refs: list[str],
Expand Down
18 changes: 18 additions & 0 deletions changelog_gen/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import logging
import time
import typing as t

logger = logging.getLogger(__name__)


def timer(func: t.Callable) -> t.Callable:
"""Timing decorator."""

def wrapper(*arg, **kw) -> t.Any: # noqa: ANN401
t1 = time.time()
res = func(*arg, **kw)
t2 = time.time()
logger.debug("%s %f", func.__name__, (t2 - t1) * 1000)
return res

return wrapper
Loading

0 comments on commit d5541bd

Please sign in to comment.