gitacross 2.3.1__tar.gz → 2.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitacross-2.3.1/src/gitacross.egg-info → gitacross-2.3.2}/PKG-INFO +2 -2
- {gitacross-2.3.1 → gitacross-2.3.2}/README.md +1 -1
- {gitacross-2.3.1 → gitacross-2.3.2}/pyproject.toml +1 -1
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/source.py +110 -7
- {gitacross-2.3.1 → gitacross-2.3.2/src/gitacross.egg-info}/PKG-INFO +2 -2
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_source.py +163 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/LICENSE +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/setup.cfg +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/__init__.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/__main__.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/cli.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/config.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/git.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/linter/__init__.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/linter/fixer.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/linter/keys.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/linter/models.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/linter/validator.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/main.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/providers/__init__.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/providers/base.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/providers/gitea.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/providers/github.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/renderer.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/retry.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/state.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross/target.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross.egg-info/SOURCES.txt +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross.egg-info/dependency_links.txt +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross.egg-info/entry_points.txt +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross.egg-info/requires.txt +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/src/gitacross.egg-info/top_level.txt +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_config.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_git.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_integration.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_linter.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_providers.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_renderer.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_state.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_sync_api.py +0 -0
- {gitacross-2.3.1 → gitacross-2.3.2}/tests/test_target.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitacross
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: Mirror releases and git commits across platforms (Gitea, GitHub, local) with transform pipelines.
|
|
5
5
|
Author: Matthew Deik
|
|
6
6
|
License-Expression: MIT
|
|
@@ -223,7 +223,7 @@ Stream each asset upload directly from the temporary download directory on disk
|
|
|
223
223
|
|
|
224
224
|
### Source mode: release, tag, or commit
|
|
225
225
|
|
|
226
|
-
Remote sources sync from the host's **API releases** by default. Two alternatives are available: git tags, or the latest commit of a branch. A `sync_from` key on the source sets the starting point — only releases from that tag onward are synced.
|
|
226
|
+
Remote sources sync from the host's **API releases** by default. Two alternatives are available: git tags, or the latest commit of a branch. A `sync_from` key on the source sets the starting point — only releases from that tag onward are synced. If the exact tag no longer exists (renamed or deleted upstream), releases with a **higher version** are synced instead, so an absent anchor never blocks newer releases. Both SemVer tags (`v1.2.3`) and calendar tags (`2024.05.01`, `2024-05-01`) are compared version-wise; the two schemes are never confused with each other.
|
|
227
227
|
|
|
228
228
|
Synced state is keyed by **tag name**, so switching a repo between `release` and `tag` modes is safe: already-synced tags are skipped regardless of the current mode (older state files keyed by API release id are migrated automatically).
|
|
229
229
|
|
|
@@ -194,7 +194,7 @@ Stream each asset upload directly from the temporary download directory on disk
|
|
|
194
194
|
|
|
195
195
|
### Source mode: release, tag, or commit
|
|
196
196
|
|
|
197
|
-
Remote sources sync from the host's **API releases** by default. Two alternatives are available: git tags, or the latest commit of a branch. A `sync_from` key on the source sets the starting point — only releases from that tag onward are synced.
|
|
197
|
+
Remote sources sync from the host's **API releases** by default. Two alternatives are available: git tags, or the latest commit of a branch. A `sync_from` key on the source sets the starting point — only releases from that tag onward are synced. If the exact tag no longer exists (renamed or deleted upstream), releases with a **higher version** are synced instead, so an absent anchor never blocks newer releases. Both SemVer tags (`v1.2.3`) and calendar tags (`2024.05.01`, `2024-05-01`) are compared version-wise; the two schemes are never confused with each other.
|
|
198
198
|
|
|
199
199
|
Synced state is keyed by **tag name**, so switching a repo between `release` and `tag` modes is safe: already-synced tags are skipped regardless of the current mode (older state files keyed by API release id are migrated automatically).
|
|
200
200
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitacross"
|
|
7
|
-
version = "2.3.
|
|
7
|
+
version = "2.3.2"
|
|
8
8
|
description = "Mirror releases and git commits across platforms (Gitea, GitHub, local) with transform pipelines."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import fnmatch
|
|
8
8
|
import logging
|
|
9
|
+
import re
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import final
|
|
11
12
|
|
|
@@ -31,20 +32,122 @@ def create_source(config, cache_dir, dry_run=False):
|
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
# Calendar versions start with a 4-digit year (YYYY[.MM[.DD[...]]], e.g.
|
|
36
|
+
# 2024.05.01 or 2024-05-01). Every dot/dash/underscore-separated numeric
|
|
37
|
+
# field is part of the date sequence.
|
|
38
|
+
_CALENDAR_RE = re.compile(r"[^0-9]*(\d{4}(?:[._-]\d{1,4})*)")
|
|
39
|
+
# SemVer-style cores are dot-separated numbers; a hyphen introduces a
|
|
40
|
+
# prerelease, never another core field.
|
|
41
|
+
_SEMVER_CORE_RE = re.compile(r"(\d+(?:\.\d+)*)")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _version_key(tag):
|
|
45
|
+
"""Parse a version-like tag into a comparable ``(fields, prerelease)`` key.
|
|
46
|
+
|
|
47
|
+
Two schemes are supported, parsed separately because they are semantically
|
|
48
|
+
different:
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
* **SemVer** — ``v1.2.3``, ``v1.2.3-rc.1``. Dot-separated core; a hyphen
|
|
51
|
+
starts a prerelease that sorts before the final release.
|
|
52
|
+
* **Calendar (CalVer)** — ``2024.05.01``, ``2024-05-01``, ``2024.05``.
|
|
53
|
+
Date-based: a leading 4-digit year marks it, and every numeric field
|
|
54
|
+
(dot *or* dash separated) belongs to the (year, month, day, ...)
|
|
55
|
+
sequence, compared as plain numbers. No major/minor/patch or SemVer
|
|
56
|
+
prerelease semantics apply to the date fields themselves.
|
|
57
|
+
|
|
58
|
+
Returns ``None`` when the tag carries no parseable version.
|
|
59
|
+
"""
|
|
60
|
+
if not isinstance(tag, str):
|
|
61
|
+
return None
|
|
62
|
+
calendar = _CALENDAR_RE.match(tag)
|
|
63
|
+
if calendar:
|
|
64
|
+
fields = tuple(int(part) for part in re.split(r"[._-]", calendar.group(1)))
|
|
65
|
+
rest = tag[calendar.end():].split("+", 1)[0]
|
|
66
|
+
else:
|
|
67
|
+
match = _SEMVER_CORE_RE.search(tag)
|
|
68
|
+
if not match:
|
|
69
|
+
return None
|
|
70
|
+
fields = tuple(int(part) for part in match.group(1).split("."))
|
|
71
|
+
rest = tag[match.end():].split("+", 1)[0]
|
|
72
|
+
# Anything remaining after the version fields is a prerelease; build
|
|
73
|
+
# metadata ("+...") is ignored for ordering, per SemVer.
|
|
74
|
+
ids = tuple(re.findall(r"[0-9A-Za-z]+", rest))
|
|
75
|
+
return fields, (ids or None)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _cmp_version_key(a, b):
|
|
79
|
+
"""Compare two ``_version_key`` results: -1/0/1 (SemVer-ish ordering)."""
|
|
80
|
+
(ca, pa), (cb, pb) = a, b
|
|
81
|
+
width = max(len(ca), len(cb))
|
|
82
|
+
ca += (0,) * (width - len(ca))
|
|
83
|
+
cb += (0,) * (width - len(cb))
|
|
84
|
+
if ca != cb:
|
|
85
|
+
return 1 if ca > cb else -1
|
|
86
|
+
# Same core: a final release outranks any prerelease of it
|
|
87
|
+
if pa is None and pb is None:
|
|
88
|
+
return 0
|
|
89
|
+
if pa is None:
|
|
90
|
+
return 1
|
|
91
|
+
if pb is None:
|
|
92
|
+
return -1
|
|
93
|
+
# Compare prerelease identifiers: numeric < alphanumeric, identifiers
|
|
94
|
+
# compare numerically when both numeric, lexically when both alphanumeric
|
|
95
|
+
for x, y in zip(pa, pb):
|
|
96
|
+
xn, yn = x.isdigit(), y.isdigit()
|
|
97
|
+
if xn and yn:
|
|
98
|
+
xi, yi = int(x), int(y)
|
|
99
|
+
if xi != yi:
|
|
100
|
+
return 1 if xi > yi else -1
|
|
101
|
+
elif xn != yn:
|
|
102
|
+
return -1 if xn else 1
|
|
103
|
+
elif x != y:
|
|
104
|
+
return 1 if x > y else -1
|
|
105
|
+
return 1 if len(pa) > len(pb) else (-1 if len(pa) < len(pb) else 0)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _newer_than_version(tag, cutoff):
|
|
109
|
+
"""True when *tag* parses as a version strictly newer than *cutoff*."""
|
|
110
|
+
key = _version_key(tag)
|
|
111
|
+
return key is not None and _cmp_version_key(key, cutoff) > 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _filter_from_sync_point(releases, sync_from):
|
|
115
|
+
"""Filter to releases at or after the sync_from tag.
|
|
116
|
+
|
|
117
|
+
Releases must already be sorted oldest-first. If sync_from is empty return
|
|
118
|
+
all releases. When the exact tag is present, everything from it onwards is
|
|
119
|
+
included; when it is missing (renamed, deleted, or never released), releases
|
|
120
|
+
whose version is strictly newer than sync_from are synced instead. Tags that
|
|
121
|
+
cannot be compared (no version digits) are skipped rather than silently
|
|
122
|
+
backfilling history.
|
|
40
123
|
"""
|
|
41
124
|
if not sync_from:
|
|
42
125
|
return releases
|
|
43
126
|
for i, rel in enumerate(releases):
|
|
44
127
|
if rel["tag_name"] == sync_from:
|
|
45
128
|
return releases[i:]
|
|
46
|
-
|
|
47
|
-
|
|
129
|
+
|
|
130
|
+
# Exact tag not found — fall back to releases with a newer version.
|
|
131
|
+
cutoff = _version_key(sync_from)
|
|
132
|
+
if cutoff is None:
|
|
133
|
+
logger.warning(
|
|
134
|
+
"sync_from '%s' not found and is not version-like — syncing nothing",
|
|
135
|
+
sync_from,
|
|
136
|
+
)
|
|
137
|
+
return []
|
|
138
|
+
newer = [r for r in releases if _newer_than_version(r.get("tag_name"), cutoff)]
|
|
139
|
+
if not newer:
|
|
140
|
+
logger.warning(
|
|
141
|
+
"sync_from tag '%s' not found and no releases are newer — syncing nothing",
|
|
142
|
+
sync_from,
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
logger.info(
|
|
146
|
+
"sync_from tag '%s' not found — syncing %d release(s) newer than it",
|
|
147
|
+
sync_from,
|
|
148
|
+
len(newer),
|
|
149
|
+
)
|
|
150
|
+
return newer
|
|
48
151
|
|
|
49
152
|
|
|
50
153
|
# ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitacross
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: Mirror releases and git commits across platforms (Gitea, GitHub, local) with transform pipelines.
|
|
5
5
|
Author: Matthew Deik
|
|
6
6
|
License-Expression: MIT
|
|
@@ -223,7 +223,7 @@ Stream each asset upload directly from the temporary download directory on disk
|
|
|
223
223
|
|
|
224
224
|
### Source mode: release, tag, or commit
|
|
225
225
|
|
|
226
|
-
Remote sources sync from the host's **API releases** by default. Two alternatives are available: git tags, or the latest commit of a branch. A `sync_from` key on the source sets the starting point — only releases from that tag onward are synced.
|
|
226
|
+
Remote sources sync from the host's **API releases** by default. Two alternatives are available: git tags, or the latest commit of a branch. A `sync_from` key on the source sets the starting point — only releases from that tag onward are synced. If the exact tag no longer exists (renamed or deleted upstream), releases with a **higher version** are synced instead, so an absent anchor never blocks newer releases. Both SemVer tags (`v1.2.3`) and calendar tags (`2024.05.01`, `2024-05-01`) are compared version-wise; the two schemes are never confused with each other.
|
|
227
227
|
|
|
228
228
|
Synced state is keyed by **tag name**, so switching a repo between `release` and `tag` modes is safe: already-synced tags are skipped regardless of the current mode (older state files keyed by API release id are migrated automatically).
|
|
229
229
|
|
|
@@ -103,9 +103,172 @@ def test_source_local_sync_from():
|
|
|
103
103
|
releases2 = src2.fetch_releases()
|
|
104
104
|
assert releases2 == []
|
|
105
105
|
|
|
106
|
+
# sync_from = missing tag but with newer releases present — the newer
|
|
107
|
+
# releases are synced instead of syncing nothing
|
|
108
|
+
cfg3 = _EndpointConfig(
|
|
109
|
+
{
|
|
110
|
+
"type": "local",
|
|
111
|
+
"path": str(repo),
|
|
112
|
+
"tag_pattern": "v*",
|
|
113
|
+
"sync_from": "v1.5",
|
|
114
|
+
},
|
|
115
|
+
is_source=True,
|
|
116
|
+
)
|
|
117
|
+
src3 = create_source(cfg3, tmp)
|
|
118
|
+
tags3 = [r["tag_name"] for r in src3.fetch_releases()]
|
|
119
|
+
assert tags3 == ["v2.0", "v3.0"]
|
|
120
|
+
|
|
106
121
|
print(" ✓ source: local sync_from")
|
|
107
122
|
|
|
108
123
|
|
|
124
|
+
def test_filter_from_sync_point_fallback_newer_versions():
|
|
125
|
+
"""A missing sync_from tag falls back to releases with a newer version."""
|
|
126
|
+
from gitacross.source import _filter_from_sync_point
|
|
127
|
+
|
|
128
|
+
def rel(tag):
|
|
129
|
+
return {"tag_name": tag}
|
|
130
|
+
|
|
131
|
+
all_rel = [rel("v0.8.0"), rel("v0.9.0"), rel("v1.0.0"), rel("v1.2.0"), rel("v2.0.0")]
|
|
132
|
+
|
|
133
|
+
# Exact match: everything from the tag onwards (unchanged behaviour)
|
|
134
|
+
assert [r["tag_name"] for r in _filter_from_sync_point(all_rel, "v0.9.0")] == [
|
|
135
|
+
"v0.9.0", "v1.0.0", "v1.2.0", "v2.0.0",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
# Missing tag: only strictly-newer versions are kept (v0.8.0 < v0.9.0)
|
|
139
|
+
assert [r["tag_name"] for r in _filter_from_sync_point(all_rel, "v0.9.1")] == [
|
|
140
|
+
"v1.0.0", "v1.2.0", "v2.0.0",
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
# Numeric (not lexicographic) comparison: v0.10.0 > v0.9.9
|
|
144
|
+
assert [
|
|
145
|
+
r["tag_name"]
|
|
146
|
+
for r in _filter_from_sync_point([rel("v0.10.0"), rel("v0.9.0")], "v0.9.9")
|
|
147
|
+
] == ["v0.10.0"]
|
|
148
|
+
|
|
149
|
+
# Zero-padding: v1.2 == v1.2.0, so it is NOT counted as newer
|
|
150
|
+
assert [
|
|
151
|
+
r["tag_name"]
|
|
152
|
+
for r in _filter_from_sync_point([rel("v1.2"), rel("v1.3")], "v1.2.0")
|
|
153
|
+
] == ["v1.3"]
|
|
154
|
+
|
|
155
|
+
# No newer release and nothing comparable -> nothing synced
|
|
156
|
+
assert _filter_from_sync_point([rel("v0.5.0")], "v0.9.0") == []
|
|
157
|
+
assert _filter_from_sync_point([rel("v0.9.0"), rel("latest")], "v0.9.1") == []
|
|
158
|
+
|
|
159
|
+
# A non-version sync_from cannot be compared -> nothing synced
|
|
160
|
+
assert _filter_from_sync_point(all_rel, "alpha") == []
|
|
161
|
+
|
|
162
|
+
# Unversioned / non-string release tags are never counted as newer
|
|
163
|
+
assert _filter_from_sync_point(
|
|
164
|
+
[rel("latest"), {"tag_name": None}, rel("v1.0.0")], "v0.9.0"
|
|
165
|
+
) == [rel("v1.0.0")]
|
|
166
|
+
|
|
167
|
+
# SemVer: a prerelease of a version is OLDER than its final release, so a
|
|
168
|
+
# missing v1.2.3 never pulls in v1.2.3-rc.1 — only truly newer versions do
|
|
169
|
+
assert [
|
|
170
|
+
r["tag_name"]
|
|
171
|
+
for r in _filter_from_sync_point([rel("v1.2.3-rc.1"), rel("v1.2.4")], "v1.2.3")
|
|
172
|
+
] == ["v1.2.4"]
|
|
173
|
+
|
|
174
|
+
# Prerelease identifier ordering: numeric pre identifiers compare
|
|
175
|
+
# numerically, and a final release outranks any prerelease of its version
|
|
176
|
+
assert [
|
|
177
|
+
r["tag_name"]
|
|
178
|
+
for r in _filter_from_sync_point(
|
|
179
|
+
[rel("v1.2.3-rc.2"), rel("v1.2.3")], "v1.2.3-rc.1"
|
|
180
|
+
)
|
|
181
|
+
] == ["v1.2.3-rc.2", "v1.2.3"]
|
|
182
|
+
|
|
183
|
+
# SemVer: numeric prerelease identifiers sort BEFORE alphanumeric ones, and
|
|
184
|
+
# a final release outranks every prerelease of its version
|
|
185
|
+
assert [
|
|
186
|
+
r["tag_name"]
|
|
187
|
+
for r in _filter_from_sync_point(
|
|
188
|
+
[rel("v1.0.0-1"), rel("v1.0.0-10"), rel("v1.0.0-beta"), rel("v1.0.0")],
|
|
189
|
+
"v1.0.0-2",
|
|
190
|
+
)
|
|
191
|
+
] == ["v1.0.0-10", "v1.0.0-beta", "v1.0.0"]
|
|
192
|
+
|
|
193
|
+
# Build metadata is ignored for ordering
|
|
194
|
+
assert [
|
|
195
|
+
r["tag_name"]
|
|
196
|
+
for r in _filter_from_sync_point([rel("v1.2.3+meta"), rel("v1.2.4")], "v1.2.3")
|
|
197
|
+
] == ["v1.2.4"]
|
|
198
|
+
|
|
199
|
+
# Alphanumeric prerelease identifiers compare lexically (beta > alpha), and
|
|
200
|
+
# longer identifier lists after an equal prefix sort later
|
|
201
|
+
assert [
|
|
202
|
+
r["tag_name"]
|
|
203
|
+
for r in _filter_from_sync_point(
|
|
204
|
+
[rel("v1.0.0-alpha.1"), rel("v1.0.0-beta"), rel("v1.0.0")],
|
|
205
|
+
"v1.0.0-alpha",
|
|
206
|
+
)
|
|
207
|
+
] == ["v1.0.0-alpha.1", "v1.0.0-beta", "v1.0.0"]
|
|
208
|
+
assert [
|
|
209
|
+
r["tag_name"]
|
|
210
|
+
for r in _filter_from_sync_point(
|
|
211
|
+
[
|
|
212
|
+
rel("v1.0.0-alpha"),
|
|
213
|
+
rel("v1.0.0-beta"),
|
|
214
|
+
rel("v1.0.0-alpha.2"),
|
|
215
|
+
rel("v1.0.0-alpha.1.beta"),
|
|
216
|
+
],
|
|
217
|
+
"v1.0.0-alpha.1",
|
|
218
|
+
)
|
|
219
|
+
] == ["v1.0.0-beta", "v1.0.0-alpha.2", "v1.0.0-alpha.1.beta"]
|
|
220
|
+
|
|
221
|
+
# ── Calendar versions (CalVer) are date-based, not SemVer ──
|
|
222
|
+
# Dot-shaped dates compare as plain (year, month, day) numbers
|
|
223
|
+
assert [
|
|
224
|
+
r["tag_name"]
|
|
225
|
+
for r in _filter_from_sync_point(
|
|
226
|
+
[rel("2024.01.15"), rel("2024.04.30"), rel("2024.05.01")], "2024.04.01"
|
|
227
|
+
)
|
|
228
|
+
] == ["2024.04.30", "2024.05.01"]
|
|
229
|
+
|
|
230
|
+
# Dash-separated dates: '-' joins date fields; it is NOT a prerelease
|
|
231
|
+
# marker, so 2024-06-01 is newer than 2024-05-01
|
|
232
|
+
assert [
|
|
233
|
+
r["tag_name"]
|
|
234
|
+
for r in _filter_from_sync_point(
|
|
235
|
+
[rel("2024-04-15"), rel("2024-06-01")], "2024-05-01"
|
|
236
|
+
)
|
|
237
|
+
] == ["2024-06-01"]
|
|
238
|
+
|
|
239
|
+
# Month-only anchor (2024.05 = May) vs day releases
|
|
240
|
+
assert [
|
|
241
|
+
r["tag_name"]
|
|
242
|
+
for r in _filter_from_sync_point([rel("2024.04"), rel("2024.05.10")], "2024.05")
|
|
243
|
+
] == ["2024.05.10"]
|
|
244
|
+
|
|
245
|
+
# A prerelease of the same calendar date is not newer than the date itself
|
|
246
|
+
assert [
|
|
247
|
+
r["tag_name"]
|
|
248
|
+
for r in _filter_from_sync_point(
|
|
249
|
+
[rel("2024.05.01-rc.1"), rel("2024.05.02")], "2024.05.01"
|
|
250
|
+
)
|
|
251
|
+
] == ["2024.05.02"]
|
|
252
|
+
|
|
253
|
+
# Scheme switch on one repo: a 2024.x calendar tag clears a v2-era SemVer
|
|
254
|
+
# anchor, while a v9.x tag never clears a 2024 calendar anchor
|
|
255
|
+
assert [
|
|
256
|
+
r["tag_name"]
|
|
257
|
+
for r in _filter_from_sync_point([rel("v2.9.0"), rel("2024.01.15")], "v2.9.9")
|
|
258
|
+
] == ["2024.01.15"]
|
|
259
|
+
assert [
|
|
260
|
+
r["tag_name"]
|
|
261
|
+
for r in _filter_from_sync_point(
|
|
262
|
+
[rel("2024.02.01"), rel("v9.9.9")], "2024.01.15"
|
|
263
|
+
)
|
|
264
|
+
] == ["2024.02.01"]
|
|
265
|
+
|
|
266
|
+
# No sync_from -> everything
|
|
267
|
+
assert len(_filter_from_sync_point(all_rel, "")) == 5
|
|
268
|
+
|
|
269
|
+
print(" ✓ source: missing sync_from falls back to newer versions")
|
|
270
|
+
|
|
271
|
+
|
|
109
272
|
def test_source_remote_mode_tag_sync_from():
|
|
110
273
|
"""mode: tag reads releases straight from git tags, no API involved."""
|
|
111
274
|
from unittest import mock
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|