-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrelease.sh
More file actions
executable file
·201 lines (164 loc) · 4.42 KB
/
Copy pathrelease.sh
File metadata and controls
executable file
·201 lines (164 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#!/usr/bin/env bash
set -euo pipefail
# Release automation for noai-watermark.
# Default behavior bumps patch version by 1 and commits version files.
PYPROJECT="pyproject.toml"
INIT_PY="src/__init__.py"
show_help() {
echo ""
echo "Usage: ./release.sh [patch|minor|major|<version>]"
echo ""
echo "Default:"
echo " patch (e.g. 0.1.19 -> 0.1.20)"
echo ""
}
case "${1:-}" in
-h|--help)
show_help
exit 0
;;
esac
require_tools() {
command -v git >/dev/null 2>&1 || { echo "Error: git is not installed."; exit 1; }
command -v sed >/dev/null 2>&1 || { echo "Error: sed is not installed."; exit 1; }
command -v gh >/dev/null 2>&1 || { echo "Error: gh is not installed."; exit 1; }
}
check_paths() {
[[ -f "$PYPROJECT" ]] || { echo "Error: $PYPROJECT not found."; exit 1; }
[[ -f "$INIT_PY" ]] || { echo "Error: $INIT_PY not found."; exit 1; }
}
check_clean_git_state() {
local has_issues=0
if ! git diff --cached --quiet 2>/dev/null; then
echo " - staged changes present"
has_issues=1
fi
if ! git diff --quiet 2>/dev/null; then
echo " - unstaged changes present"
has_issues=1
fi
if [[ -n "$(git ls-files --others --exclude-standard 2>/dev/null)" ]]; then
echo " - untracked files present"
has_issues=1
fi
if [[ "$has_issues" -ne 0 ]]; then
echo "Error: working tree must be clean before release."
exit 1
fi
}
current_branch() {
git rev-parse --abbrev-ref HEAD
}
current_version() {
sed -n 's/^version = "\(.*\)"/\1/p' "$PYPROJECT" | head -n1
}
is_valid_semver() {
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
}
bump_version() {
local cur="$1"
local bump_type="$2"
local major minor patch
IFS='.' read -r major minor patch <<<"$cur"
case "$bump_type" in
major) echo "$((major + 1)).0.0" ;;
minor) echo "${major}.$((minor + 1)).0" ;;
patch) echo "${major}.${minor}.$((patch + 1))" ;;
*) echo "Error: invalid bump type: $bump_type"; exit 1 ;;
esac
}
target_version() {
local old_ver="$1"
local arg="${2:-patch}"
case "$arg" in
patch|minor|major)
bump_version "$old_ver" "$arg"
;;
*)
if is_valid_semver "$arg"; then
echo "$arg"
else
echo "Error: invalid version argument '$arg'. Use patch|minor|major|<x.y.z>."
exit 1
fi
;;
esac
}
set_version() {
local ver="$1"
sed -i '' "s/^version = \".*\"/version = \"${ver}\"/" "$PYPROJECT"
sed -i '' "s/^__version__ = \".*\"/__version__ = \"${ver}\"/" "$INIT_PY"
}
confirm_release() {
local old_ver="$1"
local new_ver="$2"
local tag="$3"
local branch="$4"
echo ""
echo "Release summary"
echo " branch : $branch"
echo " version: $old_ver -> $new_ver"
echo " tag : $tag"
echo ""
printf "Continue? [y/N]: "
local response
read -r response
case "$response" in
[yY]|[yY][eE][sS]) ;;
*)
echo "Release cancelled."
exit 0
;;
esac
}
create_release() {
local tag="$1"
local version="$2"
local branch="$3"
git tag -a "$tag" -m "Release ${tag}"
git push origin "$branch" --tags
gh release create "$tag" \
--title "v${version}" \
--notes "Release ${version}" \
--latest
}
main() {
require_tools
check_paths
check_clean_git_state
local branch old_ver new_ver tag commit_msg arg manual=false
branch="$(current_branch)"
old_ver="$(current_version)"
arg="${1:-patch}"
if ! is_valid_semver "$old_ver"; then
echo "Error: current version '$old_ver' is not semantic version (x.y.z)."
exit 1
fi
case "$arg" in
patch|minor|major) ;;
*) manual=true ;;
esac
new_ver="$(target_version "$old_ver" "$arg")"
tag="v${new_ver}"
if "$manual"; then
if git tag -l "$tag" | grep -q "^${tag}$" || gh release view "$tag" >/dev/null 2>&1; then
echo "Error: ${tag} already exists (tag or release). Choose a different version."
exit 1
fi
else
while git tag -l "$tag" | grep -q "^${tag}$" || gh release view "$tag" >/dev/null 2>&1; do
echo "Warning: ${tag} already exists (tag or release), bumping to next patch..."
new_ver="$(bump_version "$new_ver" "patch")"
tag="v${new_ver}"
done
fi
commit_msg="chore(release): bump version to ${new_ver}"
confirm_release "$old_ver" "$new_ver" "$tag" "$branch"
set_version "$new_ver"
git add "$PYPROJECT" "$INIT_PY"
git commit -m "$commit_msg"
echo "Committed: $commit_msg"
create_release "$tag" "$new_ver" "$branch"
echo "Release completed: ${tag}"
}
main "${1:-}"