Repository: DavidVujic/python-polylith

Issue:
Allow diff from commit hash
**Is your feature request related to a problem? Please describe.**
The organization forces us to work with the branches and have four eyes review. We are looking to optimize for branch development testing time and would like to run only tests for modules that have changed since the branch was taken.

**Describe the solution you'd like**
We want to extend the  ```poly diff --since ``` to support hash or short hash as well as tag. 

**Describe alternatives you've considered**
We could create a new parameter named --hash for which we could pass the custom hash.

**Additional context**
This is to optimize branch development on a repository, for which testing is time-consuming.

Hints from issue comments:
@DavidVujic: This is a very good idea, thank you @davzucky!

I will have a look.@davzucky: I am happy to help and contribute, but I haven't looked at the code yet. Let me know if you accept contributions from external people.

If yes, What would be your preference?@DavidVujic: You are very much welcome to make contributions! For this issue, I have already started working though.

I have added some ideas for contributing here: https://github.com/DavidVujic/python-polylith/blob/main/CONTRIBUTING.md@DavidVujic: @davzucky I have published new versions of the Poetry plugin (v 1.29.0) and CLI (v 1.17.0). The two `PDM` specific hooks are also updated, because they use one of the changed components.

Please let me know if the new feature works as expected!@davzucky: Thank you, David. I tested the change, and it works perfectly. Your implementation is really elegant.
Poly cli is missing one command, version to get the version of poly. I had to check my python package to know which version I was running@DavidVujic: Great!

Yes, that's missing in the tooling. I think I've skipped that because it is already there in the underlying tooling and relatively easy to lookup.

With Poetry, you can check the current plugin version with:
```shell
# displays all installed plugins and versions
poetry self show plugins

# or

poetry self show poetry-polylith-plugin
```

Example, Hatch and uv:
```shell
# uv
uv pip list
# or
uv pip show polylith-cli

# hatch
hatch run pip list
# or
hatch run pip show polylith-cli
```

Solution patch:
diff --git a/components/polylith/commands/diff.py b/components/polylith/commands/diff.py
index 501472b0..abac0ed6 100644
--- a/components/polylith/commands/diff.py
+++ b/components/polylith/commands/diff.py
@@ -99,10 +99,10 @@ def print_views(root: Path, tag: str, options: dict) -> None:
 def run(tag_name: Union[str, None], options: dict):
     root = repo.get_workspace_root(Path.cwd())
 
-    tag = diff.collect.get_latest_tag(root, tag_name)
+    tag = diff.collect.get_latest_tag(root, tag_name) or tag_name
 
     if not tag:
-        print("No tags found in repository.")
+        print("No matching tags or commits found in repository.")
         return
 
     print_views(root, tag, options)
diff --git a/components/polylith/configuration/core.py b/components/polylith/configuration/core.py
index f327fed0..0e6d95e2 100644
--- a/components/polylith/configuration/core.py
+++ b/components/polylith/configuration/core.py
@@ -29,12 +29,15 @@ def get_git_tag_pattern(toml: dict) -> str:
     return toml["tool"]["polylith"]["git_tag_pattern"]
 
 
-def get_tag_pattern_from_config(path: Path, key: Union[str, None]) -> str:
+def get_tag_pattern_from_config(path: Path, key: Union[str, None]) -> Union[str, None]:
     toml: dict = _load_workspace_config(path)
 
     patterns = toml["tool"]["polylith"].get("tag", {}).get("patterns")
 
-    return patterns[key or "stable"] if patterns else get_git_tag_pattern(toml)
+    if not key:
+        return patterns["stable"] if patterns else get_git_tag_pattern(toml)
+
+    return patterns.get(key)
 
 
 def get_tag_sort_options_from_config(path: Path) -> List[str]:
diff --git a/components/polylith/diff/collect.py b/components/polylith/diff/collect.py
index 1a5dd867..e6571aa8 100644
--- a/components/polylith/diff/collect.py
+++ b/components/polylith/diff/collect.py
@@ -58,6 +58,10 @@ def get_changed_projects(changed_files: List[Path]) -> list:
 
 def get_latest_tag(root: Path, key: Union[str, None]) -> Union[str, None]:
     tag_pattern = configuration.get_tag_pattern_from_config(root, key)
+
+    if not tag_pattern:
+        return None
+
     sorting_options = [
         f"--sort={option}"
         for option in configuration.get_tag_sort_options_from_config(root)
diff --git a/components/polylith/diff/report.py b/components/polylith/diff/report.py
index a635f780..01b1d108 100644
--- a/components/polylith/diff/report.py
+++ b/components/polylith/diff/report.py
@@ -86,7 +86,7 @@ def print_projects_affected_by_changes(projects: Set[str], short: bool) -> None:
 def print_diff_summary(tag: str, bases: List[str], components: List[str]) -> None:
     console = Console(theme=theme.poly_theme)
 
-    console.print(Padding(f"[data]Diff: based on the {tag} tag[/]", (1, 0, 1, 0)))
+    console.print(Padding(f"[data]Diff: based on {tag}[/]", (1, 0, 1, 0)))
 
     if not bases and not components:
         console.print("[data]No brick changes found.[/]")
diff --git a/projects/pdm_polylith_bricks/pyproject.toml b/projects/pdm_polylith_bricks/pyproject.toml
index f1cddee1..25ba92d5 100644
--- a/projects/pdm_polylith_bricks/pyproject.toml
+++ b/projects/pdm_polylith_bricks/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "pdm-polylith-bricks"
-version = "1.0.7"
+version = "1.0.8"
 description = "a PDM build hook for Polylith"
 authors = ["David Vujic"]
 homepage = "https://davidvujic.github.io/python-polylith-docs/"
diff --git a/projects/pdm_polylith_workspace/pyproject.toml b/projects/pdm_polylith_workspace/pyproject.toml
index ae3f86a1..a422dc4d 100644
--- a/projects/pdm_polylith_workspace/pyproject.toml
+++ b/projects/pdm_polylith_workspace/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "pdm-polylith-workspace"
-version = "1.0.7"
+version = "1.0.8"
 description = "a PDM build hook for a Polylith workspace"
 homepage = "https://davidvujic.github.io/python-polylith-docs/"
 repository = "https://github.com/davidvujic/python-polylith"
diff --git a/projects/poetry_polylith_plugin/pyproject.toml b/projects/poetry_polylith_plugin/pyproject.toml
index f1afe9ed..1de34a61 100644
--- a/projects/poetry_polylith_plugin/pyproject.toml
+++ b/projects/poetry_polylith_plugin/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "poetry-polylith-plugin"
-version = "1.28.0"
+version = "1.29.0"
 description = "A Poetry plugin that adds tooling support for the Polylith Architecture"
 authors = ["David Vujic"]
 homepage = "https://davidvujic.github.io/python-polylith-docs/"
diff --git a/projects/polylith_cli/pyproject.toml b/projects/polylith_cli/pyproject.toml
index 1d07e0a3..4567f393 100644
--- a/projects/polylith_cli/pyproject.toml
+++ b/projects/polylith_cli/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "polylith-cli"
-version = "1.16.0"
+version = "1.17.0"
 description = "Python tooling support for the Polylith Architecture"
 authors = ['David Vujic']
 homepage = "https://davidvujic.github.io/python-polylith-docs/"


Test patch:
diff --git a/test/components/polylith/configuration/test_core.py b/test/components/polylith/configuration/test_core.py
index 4df0d781..05295cae 100644
--- a/test/components/polylith/configuration/test_core.py
+++ b/test/components/polylith/configuration/test_core.py
@@ -74,6 +74,8 @@ def test_get_tag_pattern(use_loose):
     assert stable == "stable-*"
     assert release == "v[0-9]*"
 
+    assert core.get_tag_pattern_from_config(fake_path, "non_existing_tag") is None
+
 
 def test_get_tag_sort_options_from_config(use_loose):
     options = core.get_tag_sort_options_from_config(fake_path)