Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 143 additions & 192 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,10 @@ jobs:
run: |
mkdir -p whatsnew

# Define shared functions (duplicated from release job since jobs run in isolation)
# Google Play whatsnew character limit
MAX_CHARS=500

# Define shared functions
cat > /tmp/release_functions.sh << 'FUNCTIONS_EOF'
# Function to categorize commit message
categorize_commit() {
Expand Down Expand Up @@ -652,221 +655,169 @@ jobs:
echo "$category:$cleaned_msg"
}

# Function to get GitHub username from commit
get_github_username() {
local commit_sha="$1"
local github_repo="${GITHUB_REPOSITORY}"
# Try to get the GitHub username from the commit using gh api
local username=$(gh api "repos/${github_repo}/commits/${commit_sha}" --jq '.author.login // empty' 2>/dev/null || echo "")
if [ -n "$username" ]; then
echo "@$username"
else
# Fallback: try to get committer login if author login is not available
local committer=$(gh api "repos/${github_repo}/commits/${commit_sha}" --jq '.committer.login // empty' 2>/dev/null || echo "")
if [ -n "$committer" ]; then
echo "@$committer"
else
# Last resort: use hardcoded mapping for known authors
local git_author=$(git show -s --format='%an' $commit_sha)
case "$git_author" in
"Gray Zhang" | "gray" | "Gray")
echo "@graycreate"
;;
"github-actions[bot]")
echo "@github-actions[bot]"
;;
*)
# If no mapping found, use git author name without @
echo "$git_author"
;;
esac
fi
fi
}
FUNCTIONS_EOF
# Function to collect commits for a version range
collect_version_commits() {
local range="$1"
local -n feat_ref=$2
local -n bug_ref=$3
local -n improve_ref=$4
local -n perf_ref=$5

# Source the shared functions
source /tmp/release_functions.sh
while IFS= read -r line; do
[ -z "$line" ] && continue
sha=$(echo "$line" | cut -d' ' -f1)
msg=$(echo "$line" | cut -d' ' -f2-)

# Get commits since last tag for categorization
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
# Skip version bump and merge commits
if [[ "$msg" =~ "bump version" ]] || [[ "$msg" =~ "Merge pull request" ]] || [[ "$msg" =~ "Merge branch" ]]; then
continue
fi

# Collect and categorize commits
declare -A features
declare -A bugs
declare -A improvements
declare -A performance
declare -A contributors
categorized=$(categorize_commit "$msg")
category=$(echo "$categorized" | cut -d':' -f1)
clean_msg=$(echo "$categorized" | cut -d':' -f2-)

if [ -n "$LAST_TAG" ]; then
RANGE="$LAST_TAG..HEAD"
else
RANGE="HEAD~5..HEAD"
fi
case "$category" in
feature) feat_ref["$clean_msg"]=1 ;;
bug) bug_ref["$clean_msg"]=1 ;;
improvement) improve_ref["$clean_msg"]=1 ;;
performance) perf_ref["$clean_msg"]=1 ;;
esac
done < <(git log --oneline --no-merges $range 2>/dev/null)
}

# Process commits
while IFS= read -r line; do
sha=$(echo "$line" | cut -d' ' -f1)
msg=$(echo "$line" | cut -d' ' -f2-)
# Function to generate version notes (compact format for Google Play)
generate_version_notes() {
local -n feat=$1
local -n bugs=$2
local -n improve=$3
local -n perf=$4
local output=""

# Skip version bump and merge commits
if [[ "$msg" =~ "bump version" ]] || [[ "$msg" =~ "Merge pull request" ]] || [[ "$msg" =~ "Merge branch" ]]; then
continue
if [ ${#feat[@]} -gt 0 ]; then
for msg in "${!feat[@]}"; do
output+="• $msg"$'\n'
done
fi

categorized=$(categorize_commit "$msg")
category=$(echo "$categorized" | cut -d':' -f1)
clean_msg=$(echo "$categorized" | cut -d':' -f2-)

# Get author for this commit
author=$(get_github_username "$sha")
if [ -n "$author" ]; then
contributors["$author"]="1"
if [ ${#bugs[@]} -gt 0 ]; then
for msg in "${!bugs[@]}"; do
output+="• $msg"$'\n'
done
fi
Comment on lines +692 to +706
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable naming is inconsistent. In generate_version_notes, the parameter references use bugs (line 692) but in the function body it's referenced as ${#bugs[@]} (line 702). The array is passed as bug_ref in collect_version_commits (line 662) and assigned from curr_bugs, prev_bugs, etc. The naming should be consistent - either use bugs everywhere or bug everywhere to match the pattern of feat/improve/perf.

Copilot uses AI. Check for mistakes.
if [ ${#improve[@]} -gt 0 ]; then
for msg in "${!improve[@]}"; do
output+="• $msg"$'\n'
done
fi
if [ ${#perf[@]} -gt 0 ]; then
for msg in "${!perf[@]}"; do
output+="• $msg"$'\n'
done
fi
echo "$output"
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output variable in generate_version_notes accumulates strings with newlines embedded using $'\n'. However, when echoed at the end, this will produce the correct multi-line output. But there's a subtle issue: if all arrays are empty, the function will echo an empty string which is fine, but the logic doesn't explicitly handle this case, making the function's behavior less clear.

Suggested change
echo "$output"
if [ -z "$output" ]; then
# Explicitly handle the case where there are no version notes
echo ""
else
echo "$output"
fi

Copilot uses AI. Check for mistakes.
}
FUNCTIONS_EOF

case "$category" in
feature)
features["$clean_msg"]="$author"
;;
bug)
bugs["$clean_msg"]="$author"
;;
improvement)
improvements["$clean_msg"]="$author"
;;
performance)
performance["$clean_msg"]="$author"
;;
esac
done < <(git log --oneline --no-merges $RANGE)
# Source the shared functions
source /tmp/release_functions.sh

# Generate English release notes
echo "V2er ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-en-US
echo "" >> whatsnew/whatsnew-en-US
# Get last 3 tags for multi-version release notes
TAGS=($(git tag --sort=-v:refname | head -3))
CURRENT_VERSION="${{ needs.prepare.outputs.version }}"

if [ ${#features[@]} -gt 0 ]; then
echo "🚀 New Features:" >> whatsnew/whatsnew-en-US
for msg in "${!features[@]}"; do
author="${features[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US
else
echo "• $msg" >> whatsnew/whatsnew-en-US
fi
done
echo "" >> whatsnew/whatsnew-en-US
fi
echo "Found tags: ${TAGS[*]}"
echo "Current version: $CURRENT_VERSION"

if [ ${#bugs[@]} -gt 0 ]; then
echo "🐛 Bug Fixes:" >> whatsnew/whatsnew-en-US
for msg in "${!bugs[@]}"; do
author="${bugs[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US
else
echo "• $msg" >> whatsnew/whatsnew-en-US
fi
done
echo "" >> whatsnew/whatsnew-en-US
fi
# Feedback header
FEEDBACK_HEADER_EN="Feedback: https://v2er.app/help"
FEEDBACK_HEADER_ZH="唯一问题反馈渠道:https://v2er.app/help"

if [ ${#improvements[@]} -gt 0 ]; then
echo "💪 Improvements:" >> whatsnew/whatsnew-en-US
for msg in "${!improvements[@]}"; do
author="${improvements[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US
else
echo "• $msg" >> whatsnew/whatsnew-en-US
fi
done
echo "" >> whatsnew/whatsnew-en-US
fi
# Build release notes for each version
declare -a version_notes_en
declare -a version_notes_zh

if [ ${#performance[@]} -gt 0 ]; then
echo "⚡ Performance:" >> whatsnew/whatsnew-en-US
for msg in "${!performance[@]}"; do
author="${performance[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US
else
echo "• $msg" >> whatsnew/whatsnew-en-US
fi
done
echo "" >> whatsnew/whatsnew-en-US
# Current version (HEAD to last tag or all commits if no tags)
declare -A curr_features curr_bugs curr_improvements curr_performance
if [ ${#TAGS[@]} -gt 0 ]; then
collect_version_commits "${TAGS[0]}..HEAD" curr_features curr_bugs curr_improvements curr_performance
else
collect_version_commits "HEAD~10..HEAD" curr_features curr_bugs curr_improvements curr_performance
fi

# Add contributors section if there are any
if [ ${#contributors[@]} -gt 0 ]; then
echo "👥 Contributors: ${!contributors[@]}" >> whatsnew/whatsnew-en-US
echo "" >> whatsnew/whatsnew-en-US
CURR_NOTES=$(generate_version_notes curr_features curr_bugs curr_improvements curr_performance)
if [ -n "$CURR_NOTES" ]; then
version_notes_en+=("$CURR_NOTES")
version_notes_zh+=("$CURR_NOTES")
fi

echo "Thank you for using V2er! Please report any issues on GitHub." >> whatsnew/whatsnew-en-US

# Generate Chinese release notes
echo "V2er ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-zh-CN
echo "" >> whatsnew/whatsnew-zh-CN

if [ ${#features[@]} -gt 0 ]; then
echo "🚀 新功能:" >> whatsnew/whatsnew-zh-CN
for msg in "${!features[@]}"; do
author="${features[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN
else
echo "• $msg" >> whatsnew/whatsnew-zh-CN
# Previous versions (from tags)
for i in 0 1; do
if [ $i -lt ${#TAGS[@]} ] && [ $((i+1)) -lt ${#TAGS[@]} ]; then
declare -A prev_features prev_bugs prev_improvements prev_performance
collect_version_commits "${TAGS[$((i+1))]}..${TAGS[$i]}" prev_features prev_bugs prev_improvements prev_performance
Comment on lines +756 to +757
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The associative arrays are redeclared inside the loop without being cleared between iterations. In bash, when you declare -A an existing associative array, it doesn't clear its contents. This means commits from previous iterations will accumulate in the arrays, leading to duplicate entries in the release notes for version i=1.

Copilot uses AI. Check for mistakes.
PREV_NOTES=$(generate_version_notes prev_features prev_bugs prev_improvements prev_performance)
if [ -n "$PREV_NOTES" ]; then
version_notes_en+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")
version_notes_zh+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")
fi
done
echo "" >> whatsnew/whatsnew-zh-CN
fi

if [ ${#bugs[@]} -gt 0 ]; then
echo "🐛 问题修复:" >> whatsnew/whatsnew-zh-CN
for msg in "${!bugs[@]}"; do
author="${bugs[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN
else
echo "• $msg" >> whatsnew/whatsnew-zh-CN
unset prev_features prev_bugs prev_improvements prev_performance
elif [ $i -lt ${#TAGS[@]} ] && [ $((i+1)) -ge ${#TAGS[@]} ]; then
# Last tag - get commits before it
declare -A prev_features prev_bugs prev_improvements prev_performance
collect_version_commits "${TAGS[$i]}~5..${TAGS[$i]}" prev_features prev_bugs prev_improvements prev_performance
Comment on lines +766 to +767
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The associative arrays are redeclared inside the loop without being cleared between iterations. In bash, when you declare -A an existing associative array, it doesn't clear its contents. This means commits from previous iterations will accumulate in the arrays, leading to duplicate entries in the release notes for version i=1.

Copilot uses AI. Check for mistakes.
Comment on lines +765 to +767
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop logic for handling the last tag (when i+1 >= number of tags) uses ${TAGS[$i]}~5..${TAGS[$i]} which gets commits leading up to the tag, not commits between tags. This will show the wrong commits for the oldest version in the release notes. The range should be from the previous tag (or an appropriate earlier reference) to ${TAGS[$i]}.

Suggested change
# Last tag - get commits before it
declare -A prev_features prev_bugs prev_improvements prev_performance
collect_version_commits "${TAGS[$i]}~5..${TAGS[$i]}" prev_features prev_bugs prev_improvements prev_performance
# Last tag - get commits from repository root to this tag
declare -A prev_features prev_bugs prev_improvements prev_performance
collect_version_commits "$(git rev-list --max-parents=0 ${TAGS[$i]})..${TAGS[$i]}" prev_features prev_bugs prev_improvements prev_performance

Copilot uses AI. Check for mistakes.
PREV_NOTES=$(generate_version_notes prev_features prev_bugs prev_improvements prev_performance)
if [ -n "$PREV_NOTES" ]; then
version_notes_en+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")
version_notes_zh+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")
fi
done
echo "" >> whatsnew/whatsnew-zh-CN
fi
unset prev_features prev_bugs prev_improvements prev_performance
fi
done
Comment on lines +754 to +775
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop iterates over indices 0 and 1 to collect previous versions, but the condition checks if i < ${#TAGS[@]} which may cause issues. When TAGS has only 1 element (index 0), the loop will try to access TAGS[1] in the first condition branch, which doesn't exist. The loop should handle the case where there are fewer than 3 tags more carefully.

Copilot uses AI. Check for mistakes.

if [ ${#improvements[@]} -gt 0 ]; then
echo "💪 改进优化:" >> whatsnew/whatsnew-zh-CN
for msg in "${!improvements[@]}"; do
author="${improvements[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN
else
echo "• $msg" >> whatsnew/whatsnew-zh-CN
fi
done
echo "" >> whatsnew/whatsnew-zh-CN
fi
# Generate English release notes with character limit
{
echo "$FEEDBACK_HEADER_EN"
echo ""
echo "V2er $CURRENT_VERSION"
echo ""
} > whatsnew/whatsnew-en-US

if [ ${#performance[@]} -gt 0 ]; then
echo "⚡ 性能优化:" >> whatsnew/whatsnew-zh-CN
for msg in "${!performance[@]}"; do
author="${performance[$msg]}"
if [ -n "$author" ]; then
echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN
else
echo "• $msg" >> whatsnew/whatsnew-zh-CN
fi
done
echo "" >> whatsnew/whatsnew-zh-CN
fi
CURRENT_LEN=$(wc -c < whatsnew/whatsnew-en-US)
for notes in "${version_notes_en[@]}"; do
NOTES_LEN=${#notes}
if [ $((CURRENT_LEN + NOTES_LEN + 2)) -lt $MAX_CHARS ]; then
echo "$notes" >> whatsnew/whatsnew-en-US
CURRENT_LEN=$((CURRENT_LEN + NOTES_LEN + 2))
Comment on lines +785 to +790
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The character counting logic is incorrect. wc -c counts bytes, not characters. For multi-byte UTF-8 characters (like Chinese characters in the feedback header "唯一问题反馈渠道"), this will count multiple bytes per character. However, ${#notes} counts characters correctly. This mismatch means the character limit enforcement may be inaccurate - the initial length calculation uses byte count while the subsequent additions use character count.

Copilot uses AI. Check for mistakes.
Comment on lines +788 to +790
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The character limit check adds 2 to account for newlines, but this is inconsistent. When writing to the file with echo "$notes", bash adds a single newline character (1 byte), not 2. This will cause the actual file size to be smaller than the calculated CURRENT_LEN, leading to inaccurate character limit enforcement.

Copilot uses AI. Check for mistakes.
else
break
fi
done

# Add contributors section if there are any
if [ ${#contributors[@]} -gt 0 ]; then
echo "👥 贡献者:${!contributors[@]}" >> whatsnew/whatsnew-zh-CN
echo "" >> whatsnew/whatsnew-zh-CN
fi
# Generate Chinese release notes with character limit
{
echo "$FEEDBACK_HEADER_ZH"
echo ""
echo "V2er $CURRENT_VERSION"
echo ""
} > whatsnew/whatsnew-zh-CN

CURRENT_LEN=$(wc -c < whatsnew/whatsnew-zh-CN)
for notes in "${version_notes_zh[@]}"; do
NOTES_LEN=${#notes}
if [ $((CURRENT_LEN + NOTES_LEN + 2)) -lt $MAX_CHARS ]; then
echo "$notes" >> whatsnew/whatsnew-zh-CN
CURRENT_LEN=$((CURRENT_LEN + NOTES_LEN + 2))
Comment on lines +804 to +809
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The character counting logic is incorrect. wc -c counts bytes, not characters. For multi-byte UTF-8 characters (like Chinese characters in the feedback header "唯一问题反馈渠道"), this will count multiple bytes per character. However, ${#notes} counts characters correctly. This mismatch means the character limit enforcement may be inaccurate - the initial length calculation uses byte count while the subsequent additions use character count.

Copilot uses AI. Check for mistakes.
Comment on lines +807 to +809
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The character limit check adds 2 to account for newlines, but this is inconsistent. When writing to the file with echo "$notes", bash adds a single newline character (1 byte), not 2. This will cause the actual file size to be smaller than the calculated CURRENT_LEN, leading to inaccurate character limit enforcement.

Copilot uses AI. Check for mistakes.
else
break
fi
done

echo "感谢您使用 V2er!如遇问题请在 GitHub 上反馈。" >> whatsnew/whatsnew-zh-CN
# Show generated content
echo "=== English whatsnew ($(wc -c < whatsnew/whatsnew-en-US) chars) ==="
cat whatsnew/whatsnew-en-US
echo ""
echo "=== Chinese whatsnew ($(wc -c < whatsnew/whatsnew-zh-CN) chars) ==="
cat whatsnew/whatsnew-zh-CN

- name: Upload to Play Store (with debug symbols)
if: steps.find-files.outputs.symbols_path != ''
Expand Down
Loading