Skip to content

Commit

Permalink
#63: Add automatic check for shallow repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed May 18, 2023
1 parent c0f360b commit 4be128c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 14 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Unreleased

* The `from` command will print a warning for shallow Git repositories.
This becomes an error with `--strict`.
* The `Version` class has a new `concerns` field to indicate warnings with the version.
Right now, the only possibility is `Concern.ShallowRepository`.

## v1.16.1 (2023-05-13)

* Fixed outdated reference to `pkg_resources` in the docstring for `get_version`.
Expand Down
66 changes: 54 additions & 12 deletions dunamai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
NamedTuple,
Optional,
Sequence,
Set,
Tuple,
TypeVar,
Union,
Expand Down Expand Up @@ -124,6 +125,16 @@ def parse(pattern: Union[str, "Pattern"]) -> str:
return pattern.regex()


class Concern(Enum):
ShallowRepository = "shallow-repository"

def message(self) -> str:
if self == Concern.ShallowRepository:
return "This is a shallow repository, so Dunamai may not produce the correct version."
else:
return ""


def _pattern_error(
primary: str, pattern: Union[str, Pattern], tags: Optional[Sequence[str]] = None
) -> str:
Expand Down Expand Up @@ -465,8 +476,9 @@ def __init__(
tagged_metadata: Optional[str] = None,
epoch: Optional[int] = None,
branch: Optional[str] = None,
timestamp: Optional[dt.datetime] = None,
# fmt: off
timestamp: Optional[dt.datetime] = None
concerns: Optional[Set[Concern]] = None
# fmt: on
) -> None:
"""
Expand All @@ -479,6 +491,7 @@ def __init__(
:param epoch: Optional PEP 440 epoch.
:param branch: Name of the current branch.
:param timestamp: Timestamp of the current commit.
:param concerns: Any concerns regarding the version.
"""
#: Release segment.
self.base = base
Expand Down Expand Up @@ -506,6 +519,8 @@ def __init__(
except ValueError:
# Will fail for naive timestamps before Python 3.6.
self.timestamp = timestamp
#: Any concerns regarding the version.
self.concerns = concerns or set()

self._matched_tag = None # type: Optional[str]
self._newer_unmatched_tags = None # type: Optional[Sequence[str]]
Expand Down Expand Up @@ -861,8 +876,9 @@ def _fallback(
tagged_metadata: Optional[str] = None,
epoch: Optional[int] = None,
branch: Optional[str] = None,
timestamp: Optional[dt.datetime] = None,
# fmt: off
timestamp: Optional[dt.datetime] = None
concerns: Optional[Set[Concern]] = None
# fmt: on
):
if strict:
Expand All @@ -877,6 +893,7 @@ def _fallback(
epoch=epoch,
branch=branch,
timestamp=timestamp,
concerns=concerns,
)

@classmethod
Expand All @@ -899,7 +916,8 @@ def from_git(
:param tag_branch: Branch on which to find tags, if different than the
current branch.
:param full_commit: Get the full commit hash instead of the short form.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
archival = _find_higher_file(".git_archival.json", ".git")
if archival is not None:
Expand Down Expand Up @@ -968,11 +986,24 @@ def from_git(
return version

_detect_vcs(Vcs.Git)
concerns = set() # type: Set[Concern]
if tag_branch is None:
tag_branch = "HEAD"

git_version = _get_git_version()

if git_version < [2, 15]:
flag_file = _find_higher_file(".git/shallow")
if flag_file:
concerns.add(Concern.ShallowRepository)
else:
code, msg = _run_cmd("git rev-parse --is-shallow-repository")
if msg.strip() == "true":
concerns.add(Concern.ShallowRepository)

if strict and concerns:
raise RuntimeError("\n".join(x.message() for x in concerns))

code, msg = _run_cmd("git symbolic-ref --short HEAD", codes=[0, 128])
if code == 128:
branch = None
Expand All @@ -984,7 +1015,7 @@ def from_git(
codes=[0, 128],
)
if code == 128:
return cls._fallback(strict, distance=0, dirty=True, branch=branch)
return cls._fallback(strict, distance=0, dirty=True, branch=branch, concerns=concerns)
commit = msg

timestamp = None
Expand Down Expand Up @@ -1020,6 +1051,7 @@ def from_git(
dirty=dirty,
branch=branch,
timestamp=timestamp,
concerns=concerns,
)
tags = [line.replace("refs/tags/", "") for line in msg.splitlines()]
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
Expand Down Expand Up @@ -1048,6 +1080,7 @@ def from_git(
dirty=dirty,
branch=branch,
timestamp=timestamp,
concerns=concerns,
)

detailed_tags = [] # type: List[_GitRefInfo]
Expand Down Expand Up @@ -1075,6 +1108,7 @@ def from_git(
epoch=epoch,
branch=branch,
timestamp=timestamp,
concerns=concerns,
)
version._matched_tag = tag
version._newer_unmatched_tags = unmatched
Expand All @@ -1097,7 +1131,8 @@ def from_mercurial(
tagged commit for a pattern match. If false, keep looking at tags
until there is a match.
:param full_commit: Get the full commit hash instead of the short form.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
archival = _find_higher_file(".hg_archival.txt", ".hg")
if archival is not None:
Expand Down Expand Up @@ -1220,7 +1255,8 @@ def from_darcs(
:param latest_tag: If true, only inspect the latest tag on the latest
tagged commit for a pattern match. If false, keep looking at tags
until there is a match.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
_detect_vcs(Vcs.Darcs)

Expand Down Expand Up @@ -1286,7 +1322,8 @@ def from_subversion(
tagged commit for a pattern match. If false, keep looking at tags
until there is a match.
:param tag_dir: Location of tags relative to the root.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
_detect_vcs(Vcs.Subversion)

Expand Down Expand Up @@ -1370,7 +1407,8 @@ def from_bazaar(
:param latest_tag: If true, only inspect the latest tag on the latest
tagged commit for a pattern match. If false, keep looking at tags
until there is a match.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
_detect_vcs(Vcs.Bazaar)

Expand Down Expand Up @@ -1449,7 +1487,8 @@ def from_fossil(
Refer to `from_any_vcs` for more info.
:param latest_tag: If true, only inspect the latest tag for a pattern
match. If false, keep looking at tags until there is a match.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
_detect_vcs(Vcs.Fossil)

Expand Down Expand Up @@ -1558,7 +1597,8 @@ def from_pijul(
:param latest_tag: If true, only inspect the latest tag on the latest
tagged commit for a pattern match. If false, keep looking at tags
until there is a match.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
_detect_vcs(Vcs.Pijul)

Expand Down Expand Up @@ -1708,7 +1748,8 @@ def from_any_vcs(
current branch. This is only used for Git currently.
:param full_commit: Get the full commit hash instead of the short form.
This is only used for Git and Mercurial.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
vcs = _detect_vcs_from_archival()
if vcs is None:
Expand Down Expand Up @@ -1746,7 +1787,8 @@ def from_vcs(
current branch. This is only used for Git currently.
:param full_commit: Get the full commit hash instead of the short form.
This is only used for Git and Mercurial.
:param strict: When there are no tags, fail instead of falling back to 0.0.0.
:param strict: Elevate warnings to errors.
When there are no tags, fail instead of falling back to 0.0.0.
"""
return cls._do_vcs_callback(
vcs, pattern, latest_tag, tag_dir, tag_branch, full_commit, strict
Expand Down
10 changes: 9 additions & 1 deletion dunamai/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@
"action": "store_true",
"dest": "strict",
"default": False,
"help": "When there are no tags, fail instead of falling back to 0.0.0",
"help": (
"Elevate warnings to errors."
"When there are no tags, fail instead of falling back to 0.0.0"
),
},
{
"triggers": ["--debug"],
Expand Down Expand Up @@ -249,7 +252,12 @@ def from_vcs(
strict: bool,
) -> None:
version = Version.from_vcs(vcs, pattern, latest_tag, tag_dir, tag_branch, full_commit, strict)

for concern in version.concerns:
print("Warning: {}".format(concern.message()), file=sys.stderr)

print(version.serialize(metadata, dirty, format, style, bump, tagged_metadata=tagged_metadata))

if debug:
print("# Matched tag: {}".format(version._matched_tag), file=sys.stderr)
print("# Newer unmatched tags: {}".format(version._newer_unmatched_tags), file=sys.stderr)
Expand Down
17 changes: 16 additions & 1 deletion tests/integration/test_dunamai.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import pytest

from dunamai import Version, Vcs, _get_git_version, _run_cmd
from dunamai import Version, Vcs, Concern, _get_git_version, _run_cmd


def avoid_identical_ref_timestamps() -> None:
Expand Down Expand Up @@ -96,6 +96,7 @@ def test__version__from_git__with_annotated_tags(tmp_path) -> None:

(vcs / "foo.txt").write_text("hi")
assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True, branch=b)
assert from_vcs(fresh=True).concerns == set()

run("git add .")
run('git commit --no-gpg-sign -m "Initial commit"')
Expand Down Expand Up @@ -435,6 +436,20 @@ def test__version__from_git__archival_tagged_post() -> None:
assert detected._newer_unmatched_tags == []


@pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git")
def test__version__from_git__shallow(tmp_path) -> None:
vcs = tmp_path / "dunamai-git-shallow"
vcs.mkdir()
run = make_run_callback(vcs)

with chdir(vcs):
run("git clone --depth 1 https://github.com/mtkennerly/dunamai.git .")
assert Version.from_git().concerns == {Concern.ShallowRepository}

with pytest.raises(RuntimeError):
Version.from_git(strict=True)


@pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git")
def test__version__not_a_repository(tmp_path) -> None:
vcs = tmp_path / "dunamai-not-a-repo"
Expand Down

0 comments on commit 4be128c

Please sign in to comment.