Skip to content

Commit

Permalink
#83: Fall back to 0.0.0 when there are tags but none match
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Apr 29, 2024
1 parent c42b85b commit 42828f5
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 33 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## Unreleased

* Generally, when Dunamai can detect the VCS in use, but there's no version set yet,
then Dunamai uses 0.0.0 as a fallback, unless strict mode is enabled.
This is useful for new projects that do not yet have a release.

However, if there were some tags and none matched the version pattern,
then Dunamai would yield an error.
That wouldn't be helpful for a new project with some non-version tag,
and it could be incorrect for a monorepo with different tags for different packages.

Now, Dunamai will use 0.0.0 in this case as well, unless strict mode is enabled.

## v1.20.0 (2024-04-12)

* Updated `Version.bump()` to add a `smart` argument,
Expand Down
166 changes: 134 additions & 32 deletions dunamai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ def _run_cmd(


def _match_version_pattern(
pattern: Union[str, Pattern], sources: Sequence[str], latest_source: bool
) -> _MatchedVersionPattern:
pattern: Union[str, Pattern], sources: Sequence[str], latest_source: bool, strict: bool
) -> Optional[_MatchedVersionPattern]:
"""
:returns: Tuple of:
* matched tag
Expand Down Expand Up @@ -281,8 +281,10 @@ def _match_version_pattern(
sources,
)
)
else:
elif strict:
raise ValueError(_pattern_error("The pattern did not match any tags", pattern, sources))
else:
return None

stage = pattern_match.groupdict().get("stage")
revision = pattern_match.groupdict().get("revision")
Expand Down Expand Up @@ -845,9 +847,13 @@ def parse(cls, version: str, pattern: Union[str, Pattern] = Pattern.Default) ->
if not version.startswith("v") and pattern in [VERSION_SOURCE_PATTERN, Pattern.Default]:
normalized = "v{}".format(version)

failed = False
try:
matched_pattern = _match_version_pattern(pattern, [normalized], True)
matched_pattern = _match_version_pattern(pattern, [normalized], True, strict=True)
except ValueError:
failed = True

if failed or matched_pattern is None:
replaced = re.sub(r"(\.post(\d+)\.dev\d+)", r".dev\2", version, 1)
if replaced != version:
alt = Version.parse(replaced, pattern)
Expand Down Expand Up @@ -1060,9 +1066,19 @@ def from_git(
vcs=vcs,
)

tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, [tag], latest_tag
)
matched_pattern = _match_version_pattern(pattern, [tag], latest_tag, strict)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
branch=branch,
timestamp=timestamp,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

version = cls(
base,
stage=stage,
Expand Down Expand Up @@ -1158,9 +1174,7 @@ def from_git(
vcs=vcs,
)
tags = [line.replace("refs/tags/", "") for line in msg.splitlines()]
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, tags, latest_tag
)
matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict)
else:
code, msg = _run_cmd(
'git for-each-ref "refs/tags/**" --merged {}'.format(tag_branch)
Expand Down Expand Up @@ -1199,9 +1213,28 @@ def from_git(
detailed_tags.append(_GitRefInfo(*parts).with_tag_topo_lookup(tag_topo_lookup))

tags = [t.ref for t in sorted(detailed_tags, key=lambda x: x.sort_key, reverse=True)]
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, tags, latest_tag
matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict)

if matched_pattern is None:
distance = 0

code, msg = _run_cmd("git rev-list --max-parents=0 HEAD", path)
if msg:
initial_commit = msg.splitlines()[0].strip()
code, msg = _run_cmd("git rev-list --count {}..HEAD".format(initial_commit), path)
distance = int(msg)

return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
branch=branch,
timestamp=timestamp,
concerns=concerns,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

code, msg = _run_cmd("git rev-list --count refs/tags/{}..HEAD".format(tag), path)
distance = int(msg)
Expand Down Expand Up @@ -1281,9 +1314,17 @@ def from_mercurial(
continue
all_tags.append(parts[1])

tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, all_tags, latest_tag
)
matched_pattern = _match_version_pattern(pattern, all_tags, latest_tag, strict)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
branch=branch,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

version = cls(
base,
stage=stage,
Expand Down Expand Up @@ -1336,9 +1377,19 @@ def from_mercurial(
vcs=vcs,
)
tags = [tag for tags in [line.split(":") for line in msg.splitlines()] for tag in tags]
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, tags, latest_tag
)

matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
branch=branch,
timestamp=timestamp,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

code, msg = _run_cmd('hg log -r "{0}::{1} - {0}" --template "."'.format(tag, commit), path)
# The tag itself is in the list, so offset by 1.
Expand Down Expand Up @@ -1407,9 +1458,18 @@ def from_darcs(
strict, distance=distance, commit=commit, dirty=dirty, timestamp=timestamp, vcs=vcs
)
tags = msg.splitlines()
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, tags, latest_tag
)

matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
timestamp=timestamp,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

code, msg = _run_cmd("darcs log --from-tag {} --count".format(tag), path)
# The tag itself is in the list, so offset by 1.
Expand Down Expand Up @@ -1501,9 +1561,18 @@ def from_subversion(
source = int(match.group(1))
tags_to_sources_revs[tag] = (source, rev)
tags = sorted(tags_to_sources_revs, key=lambda x: tags_to_sources_revs[x], reverse=True)
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, tags, latest_tag
)

matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
timestamp=timestamp,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

source, rev = tags_to_sources_revs[tag]
# The tag itself is in the list, so offset by 1.
Expand Down Expand Up @@ -1589,9 +1658,19 @@ def from_bazaar(
if line.split()[1] != "?"
}
tags = [x[1] for x in sorted([(v, k) for k, v in tags_to_revs.items()], reverse=True)]
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, tags, latest_tag
)

matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
branch=branch,
timestamp=timestamp,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

distance = int(commit) - tags_to_revs[tag]

Expand Down Expand Up @@ -1712,9 +1791,22 @@ def from_fossil(
(line.rsplit(",", 1)[0][5:-1], int(line.rsplit(",", 1)[1]) - 1)
for line in msg.splitlines()
]
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, [t for t, d in tags_to_distance], latest_tag

matched_pattern = _match_version_pattern(
pattern, [t for t, d in tags_to_distance], latest_tag, strict
)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
branch=branch,
timestamp=timestamp,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

distance = dict(tags_to_distance)[tag]

version = cls(
Expand Down Expand Up @@ -1841,9 +1933,19 @@ def from_pijul(
t["message"]
for t in sorted(tag_meta_by_msg.values(), key=lambda x: x["timestamp"], reverse=True)
]
tag, base, stage, unmatched, tagged_metadata, epoch = _match_version_pattern(
pattern, tags, latest_tag
)

matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict)
if matched_pattern is None:
return cls._fallback(
strict,
distance=distance,
commit=commit,
dirty=dirty,
branch=branch,
timestamp=timestamp,
vcs=vcs,
)
tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern

tag_id = tag_meta_by_msg[tag]["state"]
_run_cmd("pijul tag checkout {}".format(tag_id), path, codes=[0, 1])
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/test_dunamai.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ def test__version__from_git__with_annotated_tags(tmp_path) -> None:
# Additional one-off check not in other VCS integration tests:
# when the only tag in the repository does not match the pattern.
run("git tag other -m Annotated")
assert from_vcs() == Version("0.0.0", dirty=False, branch=b)
with pytest.raises(ValueError):
from_vcs()
from_vcs(strict=True)

avoid_identical_ref_timestamps()
run("git tag v0.1.0 -m Annotated")
Expand Down

0 comments on commit 42828f5

Please sign in to comment.