diff --git a/capella_diff_tools/__main__.py b/capella_diff_tools/__main__.py index c7b33c7..7be3f35 100644 --- a/capella_diff_tools/__main__.py +++ b/capella_diff_tools/__main__.py @@ -6,7 +6,6 @@ import datetime import logging -import os import pathlib import sys import typing as t @@ -65,7 +64,7 @@ def main( """ logging.basicConfig(level="DEBUG") model.pop("revision", None) - model["path"] = _ensure_git(model["path"]) + _ensure_git(model) old_model = capellambse.MelodyModel(**model, revision=old_version) new_model = capellambse.MelodyModel(**model, revision=new_version) @@ -73,6 +72,7 @@ def main( "model": model, "old_revision": _get_revision_info(old_model, old_version), "new_revision": _get_revision_info(new_model, new_version), + "commit_log": _get_commit_log(new_model, old_version, new_version), } objects = compare.compare_all_objects(old_model, new_model) diagrams = compare.compare_all_diagrams(old_model, new_model) @@ -92,18 +92,19 @@ def main( report_file.write(report.generate_html(result)) -def _ensure_git(path: str | os.PathLike[str]) -> str: - proto, path = fh.split_protocol(path) +def _ensure_git(model: dict[str, t.Any]) -> None: + proto, path = fh.split_protocol(model["path"]) if proto == "file": assert isinstance(path, pathlib.Path) - path = "git+" + path.resolve().as_uri() + path = path.resolve() + if "entrypoint" not in model and path.is_file(): + model["entrypoint"] = path.name + path = path.parent + model["path"] = "git+" + path.as_uri() + elif proto != "git": + raise click.UsageError("The 'model' must point to a git repository") - proto, _ = fh.split_protocol(path) - if proto != "git": - raise click.Abort("The 'model' must point to a git repository") - - assert isinstance(path, str) - return path + assert isinstance(model["path"], str) def _get_revision_info( @@ -132,6 +133,36 @@ def _get_revision_info( } +def _get_commit_log( + model: capellambse.MelodyModel, + old_version: str, + new_version: str, +) -> list[types.RevisionInfo]: + res = model._loader.resources["\x00"] + assert isinstance(res, fh.git.GitFileHandler) + commits: list[types.RevisionInfo] = [] + rawlog = res._git( + "log", + "-z", + "--reverse", + "--format=format:%H%x00%aN%x00%aI%x00%B", + f"{old_version}..{new_version}", + encoding="utf-8", + ).split("\x00") + log = capellambse.helpers.ntuples(4, rawlog) + for hash, author, date_str, description in log: + commits.append( + { + "hash": hash, + "revision": hash, + "author": author, + "date": datetime.datetime.fromisoformat(date_str), + "description": description.rstrip(), + } + ) + return commits + + class CustomYAMLDumper(yaml.SafeDumper): """A custom YAML dumper that can serialize markupsafe.Markup.""" diff --git a/capella_diff_tools/report.html.jinja b/capella_diff_tools/report.html.jinja index e621356..90a8ca1 100644 --- a/capella_diff_tools/report.html.jinja +++ b/capella_diff_tools/report.html.jinja @@ -58,7 +58,7 @@ Repository: {{ data["metadata"]["model"]["path"] | e }}
Entry point: {{ data["metadata"]["model"]["entrypoint"] | e }}

-

The review of changes covers the following commits:

+

The review of changes covers the following range of commits:

@@ -84,9 +84,9 @@ - - - + + + @@ -96,6 +96,48 @@
{{ data["metadata"]["new_revision"]["date"] | e }}
Commit message{{ data["metadata"]["old_revision"]["description"] | e }}{{ data["metadata"]["new_revision"]["description"] | e }}Summary{{ data["metadata"]["old_revision"]["description"].split("\n") | first | e }}{{ data["metadata"]["new_revision"]["description"].split("\n") | first | e }}
Commit ID (hash)
+

Detailed information about the contained commits:

+ + + + {% for commit in data.metadata.commit_log -%} + + + + + + + {% else -%} + + + + + + + + + + + + + + + + {% endfor -%} + + + + + + + + + +
{{ commit.hash | e }}{{ commit.date | e }}{{ commit.author | e }}{{ commit.description | e }}
{{ data.metadata.old_revision.hash }}{{ data.metadata.old_revision.date }}{{ data.metadata.old_revision.author }}{{ data.metadata.old_revision.description }}
+ Error: The supplied report does not contain the commit log. + Detailed information is only available for the first and last commit. +
{{ data.metadata.new_revision.hash }}{{ data.metadata.new_revision.date }}{{ data.metadata.new_revision.author }}{{ data.metadata.new_revision.description }}
Commit ID (hash)Date & timeAuthorCommit message
+ {% macro pretty_stats(stats) %} ( {% if stats.created %}+{{stats["created"]}} / {% endif %} diff --git a/capella_diff_tools/types.py b/capella_diff_tools/types.py index c3a51f3..672d73c 100644 --- a/capella_diff_tools/types.py +++ b/capella_diff_tools/types.py @@ -21,6 +21,7 @@ class Metadata(te.TypedDict): """The 'modelinfo' used to load the models, sans the revision key.""" new_revision: RevisionInfo old_revision: RevisionInfo + commit_log: list[RevisionInfo] class RevisionInfo(te.TypedDict, total=False): diff --git a/ci-templates/gitlab/compare-to-tag.yml b/ci-templates/gitlab/compare-to-tag.yml new file mode 100644 index 0000000..f5e0367 --- /dev/null +++ b/ci-templates/gitlab/compare-to-tag.yml @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 +# yaml-language-server: $schema=https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json + +compare-to-tag: + image: python:3.12 + script: + - oldversion="$(git describe --tags --abbrev=0 || true)" + - if ! [[ $oldversion ]]; then echo >&2 No recent tag found to compare against; exit 0; fi + - |- + # Install capella-diff-tools from PyPI or Github + if [[ ${CAPELLA_DIFF_TOOLS_VERSION:-vX.Y.Z} = v*.*.* ]]; then + pip install "capella-diff-tools${CAPELLA_DIFF_TOOLS_VERSION:+==$CAPELLA_DIFF_TOOLS_VERSION}" + else + pip install "git+https://github.com/DSD-DBS/capella-diff-tools.git@$CAPELLA_DIFF_TOOLS_VERSION" + fi + - capella-diff-tool ${ENTRYPOINT:-.} "$oldversion" HEAD -o model-diff.yml -r model-diff.html + + artifacts: + paths: [model-diff.html, model-diff.yml] + +variables: + CAPELLA_DIFF_TOOLS_VERSION: + description: Version of capella-diff-tools to install. Should be the same as the version of this template.