Skip to content
Merged
Show file tree
Hide file tree
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
44 changes: 32 additions & 12 deletions .github/workflows/merge-pull-requests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Merge pull requests

on:
schedule:
- cron: "30 5 * * 6"
workflow_dispatch:
inputs:
repository:
Expand Down Expand Up @@ -54,10 +56,11 @@ on:

concurrency:
group: "merge-pull-requests"
cancel-in-progress: true
cancel-in-progress: false

jobs:
merge:
if: github.event_name != 'schedule' || vars.AUTO_MERGE_RELEASES == 'true'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
Expand All @@ -68,7 +71,30 @@ jobs:
with:
egress-policy: audit

- name: Check release Saturday
id: cadence
env:
EVENT_NAME: ${{ github.event_name }}
run: |
if [[ "$EVENT_NAME" != "schedule" ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
days=$(( ($(date -u +%s) - $(date -u -d 2026-09-26 +%s)) / 86400 ))
if (( days % 14 == 0 )); then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "Not a release Saturday; skipping." >> "$GITHUB_STEP_SUMMARY"
fi
fi

- name: Merge pull requests
if: steps.cadence.outputs.run == 'true'
env:
SELECTED_REPO: ${{ inputs.repository || 'all' }}
BRANCH: ${{ inputs.branch || 'fossifybot/release' }}
MERGE_METHOD: ${{ inputs.merge_method || 'squash' }}
FORCE_ADMIN: ${{ inputs.force_admin || false }}
PR_AUTHOR: ${{ github.event_name == 'schedule' && 'fossifybot[bot]' || inputs.pr_author }}
run: |
set -uo pipefail

Expand All @@ -92,12 +118,6 @@ jobs:
"FossifyOrg/Voice-Recorder"
)

SELECTED_REPO="${{ github.event.inputs.repository }}"
BRANCH="${{ github.event.inputs.branch }}"
MERGE_METHOD="${{ github.event.inputs.merge_method }}"
FORCE_ADMIN="${{ github.event.inputs.force_admin }}"
PR_AUTHOR="${{ github.event.inputs.pr_author }}"

case "$MERGE_METHOD" in
squash) MERGE_FLAG="--squash" ;;
merge) MERGE_FLAG="--merge" ;;
Expand Down Expand Up @@ -129,24 +149,24 @@ jobs:
for repo in "${REPOS[@]}"; do
echo "Processing: $repo"

PR_NUMBER=$(gh pr list --repo "$repo" --head "$BRANCH" "${SEARCH_ARGS[@]}" --json number --jq '.[0].number // empty' 2>/dev/null)
PR_NUMBER=$(gh pr list --repo "$repo" --head "$BRANCH" "${SEARCH_ARGS[@]}" --json number --jq '.[0].number // empty')
if [[ -z "$PR_NUMBER" ]]; then
echo "[$repo]: No open PR found for branch $BRANCH (author filter: '${PR_AUTHOR:-any}'). Skipping."
continue
fi

echo "[$repo]: Found PR #$PR_NUMBER for branch $BRANCH"

if gh pr review --repo "$repo" --approve "$PR_NUMBER" 2>/dev/null; then
if gh pr review --repo "$repo" --approve "$PR_NUMBER"; then
echo "[$repo]: PR #$PR_NUMBER approved"
else
echo "[$repo]: Failed to approve PR #$PR_NUMBER"
fi

if gh pr merge --repo "$repo" $MERGE_FLAG $ADMIN_FLAG "$PR_NUMBER" 2>/dev/null; then
echo "[$repo]: PR #$PR_NUMBER merged successfully"
if gh pr merge --repo "$repo" "$MERGE_FLAG" "$ADMIN_FLAG" "$PR_NUMBER"; then
echo "[$repo]: PR #$PR_NUMBER merge submitted" | tee -a "$GITHUB_STEP_SUMMARY"
else
echo "[$repo]: Failed to merge PR #$PR_NUMBER. Skipping."
echo "[$repo]: PR #$PR_NUMBER could not be merged. Skipping." | tee -a "$GITHUB_STEP_SUMMARY"
fi

sleep 1
Expand Down
121 changes: 61 additions & 60 deletions .github/workflows/prepare-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,74 @@ jobs:
echo "hasTags=false" >> "$GITHUB_OUTPUT"
fi

- name: Setup NodeJS
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with:
node-version: "20"

- name: Install keep-a-changelog parser
run: npm install keep-a-changelog@2.6.2

- name: Check unreleased changes
id: changelog
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
env:
HAS_TAGS: ${{ steps.release-history.outputs.hasTags }}
with:
script: |
const fs = require('fs');
const { execFileSync } = require('child_process');
const { parser } = require('keep-a-changelog');
const parsed = parser(fs.readFileSync('CHANGELOG.md', 'utf8'));
const unreleased = parsed.findRelease();
if (!unreleased) {
throw new Error('CHANGELOG.md has no Unreleased section.');
}

const version = fs.readFileSync('gradle.properties', 'utf8')
.match(/^VERSION_NAME=(.+)$/m)?.[1].trim();
if (!version) {
throw new Error('VERSION_NAME is missing from gradle.properties.');
}
core.setOutput('current_version', version);

if (process.env.HAS_TAGS === 'true') {
const files = execFileSync('git', [
'diff', '--name-only', '--no-renames', `refs/tags/${version}`, 'HEAD', '--',
'app/src/*/res/values-*/strings.xml',
'fastlane/metadata/android/*/title.txt',
'fastlane/metadata/android/*/short_description.txt',
'fastlane/metadata/android/*/full_description.txt',
':(exclude)fastlane/metadata/android/en-US/*',
], { encoding: 'utf8' }).trim();
const entries = unreleased.changes.get('changed') || [];
if (files && !entries.some(entry => entry.toString().includes('Updated translations'))) {
unreleased.changed('Updated translations');
fs.writeFileSync('CHANGELOG.md', parsed.toString());
core.info('Added Updated translations to Unreleased.');
}
}

const hasChanges = [...unreleased.changes.values()].some(entries => entries.length > 0);
core.setOutput('has_changes', hasChanges);
if (!hasChanges) {
core.info('No unreleased changelog entries or translation changes; skipping release preparation.');
}

- name: Calculate next version name
if: steps.release-history.outputs.hasTags == 'true'
if: steps.release-history.outputs.hasTags == 'true' && steps.changelog.outputs.has_changes == 'true'
id: semantic-version
uses: ietf-tools/semver-action@c90370b2958652d71c06a3484129a4d423a6d8a8
with:
token: ${{ github.token }}
branch: ${{ github.event.repository.default_branch }}
fromTag: ${{ steps.changelog.outputs.current_version }}
noNewCommitBehavior: "warn"
noVersionBumpBehavior: "warn"
noVersionBumpBehavior: "patch"
patchList: fix, bugfix, perf, refactor, test, tests, chore

- name: Get next version name
if: steps.changelog.outputs.has_changes == 'true'
id: version-name
env:
HAS_TAGS: ${{ steps.release-history.outputs.hasTags }}
Expand Down Expand Up @@ -96,69 +152,14 @@ jobs:
echo "Release branch doesn't exist yet."
fi

- name: Update files and check changelog
- name: Update version files
if: ${{ steps.skip-update.outputs.needs_update == 'true' && steps.version-name.outputs.nextStrict }}
id: update-version
run: |
chmod +x .github-repo/scripts/update-version.sh
if ! .github-repo/scripts/update-version.sh \
bash .github-repo/scripts/update-version.sh \
"${{ steps.version-name.outputs.nextStrict }}" \
"${{ steps.version-code.outputs.nextVersionCode }}" \
"${{ github.repository }}"; then
echo "::error::Release aborted - No changes found in CHANGELOG.md [Unreleased] section"
echo "::error::Please add your changes to the [Unreleased] section before creating a release"
exit 1
fi

- name: Setup NodeJS
if: ${{ steps.update-version.outcome == 'success' }}
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with:
node-version: "20"

- name: Install keep-a-changelog parser
if: ${{ steps.update-version.outcome == 'success' }}
run: npm install keep-a-changelog@2.6.2

- name: Add translations entry in changelog
if: ${{ steps.update-version.outcome == 'success' }}
id: update_changelog
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
with:
script: |
const fs = require('fs');
const path = require('path');
const { parser } = require('keep-a-changelog');
const changelogPath = path.join(process.env.GITHUB_WORKSPACE, 'CHANGELOG.md');

if (!fs.existsSync(changelogPath)) {
throw new Error(`Changelog file not found at ${changelogPath}`);
}

const changelogContent = fs.readFileSync(changelogPath, 'utf8');
let parsed;
try {
parsed = parser(changelogContent);
console.log(`Parsed ${parsed.releases.length} releases from changelog.`);
} catch (error) {
throw new Error(`Failed to parse CHANGELOG.md: ${error.message}`);
}

const tagVersion = "${{ steps.version-name.outputs.nextStrict }}";
const release = parsed.findRelease(tagVersion);
if (!release) {
throw new Error(`No changelog entry found for version ${tagVersion} in ${changelogPath}.`);
}

const changedEntries = release.changes.get('changed') || [];
if (!changedEntries.some(entry => entry.toString().includes('Updated translations'))) {
release.changed('Updated translations');
const updatedChangelog = parsed.toString();
fs.writeFileSync(changelogPath, updatedChangelog, 'utf8');
console.log(`Added "Updated translations" to version ${tagVersion}`);
} else {
console.log(`"Updated translations" already present for version ${tagVersion}.`);
}
"${{ github.repository }}"

- name: Normalize changelog
if: ${{ steps.update-version.outcome == 'success' }}
Expand Down