Skip to content

Commit 8966a6d

Browse files
committed
fix: 배포 스크립트가 꺠질 수 있던 문제 수정
1 parent 7df0ead commit 8966a6d

2 files changed

Lines changed: 109 additions & 27 deletions

File tree

.github/scripts/get_new_version.py

Lines changed: 106 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
"""
22
Description:
3-
- Print the calculated new version.
3+
- Print the calculated new release tag.
4+
- Reads all existing git tags, picks the highest PEP440 version as the base,
5+
and computes the next version. Optionally removes invalid (b/rc) pre-release
6+
tags so they cannot poison future runs.
47
58
Usage:
6-
- python3 update_version.py --current-version <version> (--stage)
9+
- python3 get_new_version.py [--stage] [--cleanup-invalid]
710
811
Version Scheme:
912
- <year>.<month>.<release count>(a<prerelease count>)
1013
1114
example:
1215
- case 1:
1316
- given:
14-
- current version: 2025.1.1
17+
- latest version: 2025.1.1
1518
- today : YEAR = 2025, MONTH = 1
1619
- then:
1720
- if stage is false:
@@ -20,7 +23,7 @@
2023
- new version: 2025.1.2a0
2124
- case 2:
2225
- given:
23-
- current version: 2025.1.2a0
26+
- latest version: 2025.1.2a0
2427
- today : YEAR = 2025, MONTH = 1
2528
- then:
2629
- if stage is false:
@@ -29,17 +32,28 @@
2932
- new version: 2025.1.2a1
3033
- case 3:
3134
- given:
32-
- current version: 2025.1.1
35+
- latest version: 2025.1.1
3336
- today : YEAR = 2025, MONTH = 2
3437
- then:
3538
- if stage is false:
36-
- new version: 2025.2.0
39+
- new version: 2025.2.1
3740
- if stage is true:
38-
- new version: 2025.2.0a0
41+
- new version: 2025.2.1a0
42+
- case 4:
43+
- given:
44+
- no tags exist
45+
- today : YEAR = 2025, MONTH = 2
46+
- then:
47+
- if stage is false:
48+
- new version: 2025.2.1
49+
- if stage is true:
50+
- new version: 2025.2.1a0
3951
"""
4052

4153
import argparse
4254
import datetime
55+
import subprocess # nosec: B404 — git CLI 호출 전용, 입력은 PEP440 검증을 통과한 태그 이름뿐
56+
import sys
4357
import typing
4458

4559
import packaging.version
@@ -48,28 +62,75 @@
4862

4963

5064
class ArgumentNamespace(argparse.Namespace):
51-
current: str
5265
stage: bool = False
53-
54-
55-
def increment_version_count(version: packaging.version.Version, is_stage: bool) -> str:
56-
if (current_pre := version.pre) and current_pre[0] != "a":
57-
raise ValueError(f"Unsupported pre-release version: {current_pre[0]}")
58-
59-
# Get the current date
66+
cleanup_invalid: bool = False
67+
68+
69+
def list_tags() -> list[str]:
70+
out = subprocess.run( # nosec: B603 B607 — 고정 인자, CI 신뢰 환경
71+
["git", "tag", "-l"],
72+
check=True,
73+
capture_output=True,
74+
text=True,
75+
).stdout
76+
return [line.strip() for line in out.splitlines() if line.strip()]
77+
78+
79+
def collect_versions(tags: list[str]) -> tuple[list[packaging.version.Version], list[str]]:
80+
"""Split tags into (a-only PEP440 versions, invalid b/rc tags). Non-PEP440 tags are ignored."""
81+
valid: list[packaging.version.Version] = []
82+
invalid: list[str] = []
83+
for tag in tags:
84+
try:
85+
version = packaging.version.parse(tag)
86+
except packaging.version.InvalidVersion:
87+
continue
88+
if version.pre and version.pre[0] != "a":
89+
invalid.append(tag)
90+
continue
91+
valid.append(version)
92+
return valid, invalid
93+
94+
95+
def tag_exists(tag: str) -> bool:
96+
return (
97+
subprocess.run( # nosec: B603 B607 — 고정 인자, CI 신뢰 환경
98+
["git", "rev-parse", "--verify", "--quiet", f"refs/tags/{tag}"],
99+
capture_output=True,
100+
).returncode
101+
== 0
102+
)
103+
104+
105+
def delete_tag(tag: str) -> None:
106+
subprocess.run( # nosec: B603 B607 — 고정 인자, CI 신뢰 환경
107+
["git", "tag", "-d", tag],
108+
check=False,
109+
capture_output=True,
110+
)
111+
result = subprocess.run( # nosec: B603 B607 — 고정 인자, CI 신뢰 환경
112+
["git", "push", "origin", f":refs/tags/{tag}"],
113+
capture_output=True,
114+
text=True,
115+
)
116+
if result.returncode != 0:
117+
print(f"Warning: failed to delete remote tag {tag}: {result.stderr.strip()}", file=sys.stderr)
118+
119+
120+
def increment_version_count(version: packaging.version.Version | None, is_stage: bool) -> str:
60121
today: datetime.date = datetime.date.today()
122+
current_pre: PreType = typing.cast(PreType, version.pre) if version else None
61123

62-
# Calculate the new version
63-
new_count: int = 0
64-
if version.major == today.year and version.minor == today.month:
124+
new_count: int
125+
if version and version.major == today.year and version.minor == today.month:
65126
if current_pre:
66-
# If the current version is a pre-release, do not increment the count
127+
# Same month with a pre-release: keep the same micro
67128
new_count = version.micro
68129
else:
69130
# Same month, increment the count
70131
new_count = version.micro + 1
71132
else:
72-
# Different month, reset the count
133+
# Different month or no prior version: start fresh
73134
new_count = 1
74135
current_pre = None
75136

@@ -78,10 +139,32 @@ def increment_version_count(version: packaging.version.Version, is_stage: bool)
78139
return f"{today.year}.{today.month}.{new_count}{new_pre_str}"
79140

80141

142+
def compute_next_tag(latest: packaging.version.Version | None, is_stage: bool) -> str:
143+
candidate = increment_version_count(latest, is_stage)
144+
while tag_exists(candidate):
145+
print(f"Warning: tag {candidate} already exists, retrying", file=sys.stderr)
146+
candidate = increment_version_count(packaging.version.parse(candidate), is_stage)
147+
return candidate
148+
149+
81150
if __name__ == "__main__":
82-
parser = argparse.ArgumentParser(description="Update version in files.")
83-
parser.add_argument("--current", type=str, required=True)
151+
parser = argparse.ArgumentParser(description="Compute next release tag.")
84152
parser.add_argument("--stage", default=False, action="store_true")
153+
parser.add_argument(
154+
"--cleanup-invalid",
155+
default=False,
156+
action="store_true",
157+
help="Delete any b/rc pre-release tags (locally and on origin) before computing the next tag.",
158+
)
85159

86160
args = parser.parse_args(namespace=ArgumentNamespace())
87-
print(increment_version_count(packaging.version.parse(args.current), args.stage))
161+
162+
valid, invalid = collect_versions(list_tags())
163+
164+
if args.cleanup_invalid:
165+
for tag in invalid:
166+
print(f"Removing invalid pre-release tag: {tag}", file=sys.stderr)
167+
delete_tag(tag)
168+
169+
latest = max(valid) if valid else None
170+
print(compute_next_tag(latest, args.stage))

.github/workflows/release-server.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name: Release-Server
22

33
concurrency:
4-
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
5-
cancel-in-progress: true
4+
group: ${{ github.workflow }}
5+
cancel-in-progress: false
66

77
on:
88
workflow_dispatch:
@@ -54,8 +54,7 @@ jobs:
5454
- name: Get current date, repo name and release tag version
5555
id: info
5656
run: |
57-
LATEST_TAG=$(git tag -l --sort=-creatordate | head -n 1)
58-
echo "TAG=$(uv run python ./.github/scripts/get_new_version.py --current=$LATEST_TAG ${{ env.BUMP_RULE }})" >> "$GITHUB_OUTPUT"
57+
echo "TAG=$(uv run python ./.github/scripts/get_new_version.py --cleanup-invalid ${{ env.BUMP_RULE }})" >> "$GITHUB_OUTPUT"
5958
echo "date=$(date +'%Y-%m-%d_%H:%M:%S')" >> "$GITHUB_OUTPUT"
6059
echo "repository_name=$(echo ${{ github.repository }} | sed -e 's/${{ github.repository_owner }}\///')" >> "$GITHUB_OUTPUT"
6160

0 commit comments

Comments
 (0)