Pull from Upstream and Merge PRs #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Pull from Upstream and Merge PRs | |
on: | |
schedule: | |
- cron: '0 8 * * *' # Runs at midnight EST sunday | |
workflow_dispatch: | |
inputs: | |
pr_numbers: | |
description: 'Comma-separated list of PR numbers to merge' | |
required: false | |
default: '' | |
auto_resolve_conflicts: | |
description: 'Auto-resolve conflicts in favor of our version (HEAD)' | |
required: false | |
type: boolean | |
default: true | |
permissions: | |
contents: write | |
env: | |
# Default PR numbers to use for both scheduled and manual runs (if not specified) | |
# This list is automatically updated - merged PRs are removed after successful runs | |
# To add new PRs: edit this line and add the PR numbers to the list | |
DEFAULT_PR_NUMBERS: '' | |
# Token for authentication | |
GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN || github.token }} | |
jobs: | |
pull-and-merge: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
token: ${{ env.GH_TOKEN }} | |
persist-credentials: true | |
- name: Verify authentication | |
run: | | |
# Check if PAT_TOKEN is available | |
if [ -n "${{ secrets.PAT_TOKEN }}" ]; then | |
echo "✓ PAT_TOKEN is configured" | |
else | |
echo "⚠ PAT_TOKEN not found, using default GITHUB_TOKEN" | |
echo "Note: Without PAT_TOKEN, the workflow cannot update itself" | |
fi | |
- name: Configure Git | |
run: | | |
git config --global user.name "GitHub Action" | |
git config --global user.email "[email protected]" | |
# Set merge strategy to avoid merge commit messages prompts | |
git config pull.rebase false | |
- name: Add upstream remote | |
run: | | |
# Add upstream without authentication (public repo) | |
git remote add upstream https://github.com/jellyfin/jellyfin-web.git || \ | |
git remote set-url upstream https://github.com/jellyfin/jellyfin-web.git | |
# Fix origin URL to include authentication token | |
git remote set-url origin https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git | |
- name: Fetch all remotes | |
run: | | |
echo "Current remotes:" | |
git remote -v | |
echo "Fetching from origin..." | |
git fetch origin | |
echo "Pulling from origin master..." | |
git pull origin master | |
echo "Fetching from upstream..." | |
git fetch upstream | |
echo "Fetched latest changes from all remotes" | |
- name: Merge upstream | |
id: merge_upstream | |
run: | | |
echo "Current branch: $(git branch --show-current)" | |
echo "Current commit: $(git rev-parse HEAD)" | |
# Try to merge upstream changes | |
if git merge upstream/master -m "Merge upstream changes from jellyfin/jellyfin-web"; then | |
echo "Successfully merged upstream changes" | |
echo "merge_success=true" >> $GITHUB_OUTPUT | |
else | |
echo "Merge conflicts detected!" | |
echo "merge_success=false" >> $GITHUB_OUTPUT | |
# Get list of conflicted files | |
echo "## Conflicted Files:" | tee conflict_files.txt | |
git diff --name-only --diff-filter=U | tee -a conflict_files.txt | |
# Save conflict summary for Discord notification | |
CONFLICT_COUNT=$(git diff --name-only --diff-filter=U | wc -l) | |
CONFLICT_FILES=$(git diff --name-only --diff-filter=U | head -5 | tr '\n' ',' | sed 's/,$//') | |
echo "CONFLICT_COUNT=${CONFLICT_COUNT}" >> $GITHUB_ENV | |
echo "CONFLICT_FILES=${CONFLICT_FILES}" >> $GITHUB_ENV | |
# Show detailed conflict info | |
echo "" | |
echo "## Conflict Details:" | |
for file in $(git diff --name-only --diff-filter=U); do | |
echo "### File: $file" | |
echo "Conflicts in file:" | |
grep -n "^<<<<<<< " "$file" 2>/dev/null | cut -d: -f1 | while read line; do | |
echo " - Line $line" | |
done || echo " - Unable to parse conflict markers" | |
done | |
# Check if auto-resolve is enabled | |
if [ "${{ github.event.inputs.auto_resolve_conflicts }}" == "true" ]; then | |
echo "" | |
echo "Auto-resolving conflicts in favor of our version (HEAD)..." | |
git diff --name-only --diff-filter=U | xargs -r git checkout --ours | |
git add . | |
git commit -m "Auto-resolved conflicts in favor of our version" | |
echo "merge_success=true" >> $GITHUB_OUTPUT | |
else | |
# Abort the merge if not auto-resolving | |
git merge --abort | |
exit 1 | |
fi | |
fi | |
- name: Check for already merged PRs | |
if: steps.merge_upstream.outputs.merge_success == 'true' | |
run: | | |
# Use the input PR numbers if provided, otherwise use the default PR numbers | |
PR_NUMBERS="${{ github.event.inputs.pr_numbers }}" | |
if [ -z "$PR_NUMBERS" ]; then | |
PR_NUMBERS="${{ env.DEFAULT_PR_NUMBERS }}" | |
fi | |
IFS=',' read -ra PR_ARRAY <<< "$PR_NUMBERS" | |
echo "## PR Status Check" > pr_status.txt | |
echo "" >> pr_status.txt | |
for PR_NUMBER in "${PR_ARRAY[@]}"; do | |
PR_NUMBER=$(echo $PR_NUMBER | xargs) # Trim whitespace | |
# Check if PR is already merged upstream | |
if git log upstream/master --grep="Merge pull request $PR_NUMBER" --oneline | grep -q "$PR_NUMBER"; then | |
echo "✓ PR $PR_NUMBER is already merged upstream" >> pr_status.txt | |
else | |
echo "○ PR $PR_NUMBER needs to be merged" >> pr_status.txt | |
echo "$PR_NUMBER" >> prs_to_merge.txt | |
fi | |
done | |
echo "PR Status:" | |
cat pr_status.txt | |
- name: Merge PRs | |
if: steps.merge_upstream.outputs.merge_success == 'true' | |
run: | | |
if [ ! -f "prs_to_merge.txt" ]; then | |
echo "All PRs are already merged upstream!" | |
exit 0 | |
fi | |
echo "## Merge Results" > merge_results.txt | |
echo "" >> merge_results.txt | |
while IFS= read -r PR_NUMBER; do | |
echo "Processing PR $PR_NUMBER" | |
# Fetch the PR branch | |
if git fetch upstream pull/$PR_NUMBER/head:pr-$PR_NUMBER; then | |
echo "Fetched PR $PR_NUMBER successfully" | |
# Check if this PR can be merged | |
MERGE_BASE=$(git merge-base HEAD pr-$PR_NUMBER) | |
if git merge-tree $MERGE_BASE HEAD pr-$PR_NUMBER | grep -q '^<<<<<<<'; then | |
echo "⚠️ PR $PR_NUMBER would cause conflicts - skipping" >> merge_results.txt | |
else | |
# Try to merge the PR | |
if git merge pr-$PR_NUMBER --no-ff -m "Merge PR $PR_NUMBER"; then | |
echo "✅ Merged PR $PR_NUMBER successfully" >> merge_results.txt | |
# Clean up the PR branch | |
git branch -D pr-$PR_NUMBER | |
else | |
echo "❌ Failed to merge PR $PR_NUMBER (unexpected error)" >> merge_results.txt | |
git merge --abort | |
fi | |
fi | |
else | |
echo "❌ Failed to fetch PR $PR_NUMBER (PR may not exist or is closed)" >> merge_results.txt | |
fi | |
done < prs_to_merge.txt | |
echo "" | |
echo "Merge Summary:" | |
cat merge_results.txt | |
- name: Update PR list in workflow file | |
if: steps.merge_upstream.outputs.merge_success == 'true' | |
run: | | |
set +e # Don't exit on error, we'll handle errors manually | |
echo "Starting PR list update process..." | |
# Read current PR list | |
CURRENT_PR_NUMBERS="${{ github.event.inputs.pr_numbers }}" | |
if [ -z "$CURRENT_PR_NUMBERS" ]; then | |
CURRENT_PR_NUMBERS="${{ env.DEFAULT_PR_NUMBERS }}" | |
fi | |
echo "Current PR list: $CURRENT_PR_NUMBERS" | |
# Create array of successfully merged PRs | |
MERGED_PRS=() | |
# Check merge results and upstream merges | |
if [ -f "pr_status.txt" ]; then | |
echo "Checking pr_status.txt for already merged PRs..." | |
# Get PRs that are already merged upstream | |
while IFS= read -r line; do | |
if echo "$line" | grep -q "✓ PR #"; then | |
PR_NUM=$(echo "$line" | grep -oE '#[0-9]+' | tr -d '#' || true) | |
if [ -n "$PR_NUM" ]; then | |
echo " Found merged upstream: PR #$PR_NUM" | |
MERGED_PRS+=("$PR_NUM") | |
fi | |
fi | |
done < pr_status.txt | |
else | |
echo "No pr_status.txt file found" | |
fi | |
if [ -f "merge_results.txt" ]; then | |
echo "Checking merge_results.txt for newly merged PRs..." | |
# Get PRs we successfully merged | |
while IFS= read -r line; do | |
if echo "$line" | grep -q "✅ Merged PR #"; then | |
PR_NUM=$(echo "$line" | grep -oE '#[0-9]+' | tr -d '#' || true) | |
if [ -n "$PR_NUM" ]; then | |
echo " Found newly merged: PR #$PR_NUM" | |
MERGED_PRS+=("$PR_NUM") | |
fi | |
fi | |
done < merge_results.txt | |
else | |
echo "No merge_results.txt file found" | |
fi | |
echo "Total merged PRs found: ${#MERGED_PRS[@]}" | |
# Remove merged PRs from the list | |
IFS=',' read -ra PR_ARRAY <<< "$CURRENT_PR_NUMBERS" | |
NEW_PR_LIST="" | |
REMOVED_COUNT=0 | |
for PR in "${PR_ARRAY[@]}"; do | |
PR=$(echo $PR | xargs) # Trim whitespace | |
IS_MERGED=false | |
for MERGED_PR in "${MERGED_PRS[@]}"; do | |
if [ "$PR" == "$MERGED_PR" ]; then | |
IS_MERGED=true | |
((REMOVED_COUNT++)) | |
echo " Will remove PR #$PR from list" | |
break | |
fi | |
done | |
if [ "$IS_MERGED" == "false" ]; then | |
if [ -z "$NEW_PR_LIST" ]; then | |
NEW_PR_LIST="$PR" | |
else | |
NEW_PR_LIST="$NEW_PR_LIST,$PR" | |
fi | |
fi | |
done | |
# Update the workflow file if PRs were removed | |
if [ "$NEW_PR_LIST" != "$CURRENT_PR_NUMBERS" ]; then | |
echo "Updating PR list in workflow file..." | |
echo "Old list: $CURRENT_PR_NUMBERS" | |
echo "New list: $NEW_PR_LIST" | |
echo "Removed $REMOVED_COUNT merged PR(s)" | |
# Update the workflow file | |
WORKFLOW_FILE=".github/workflows/Pull from Upstream and Merge PRs.yml" | |
if [ -f "$WORKFLOW_FILE" ]; then | |
# Create a backup | |
cp "$WORKFLOW_FILE" "${WORKFLOW_FILE}.bak" | |
# Update the DEFAULT_PR_NUMBERS line | |
# Use a more specific pattern to avoid issues | |
sed -i "s|DEFAULT_PR_NUMBERS: '6824,6969']*'|DEFAULT_PR_NUMBERS: '$NEW_PR_LIST'|" "$WORKFLOW_FILE" | |
# Verify the change was made | |
if grep -q "DEFAULT_PR_NUMBERS: '6824,6969'" "$WORKFLOW_FILE"; then | |
echo "Successfully updated workflow file" | |
echo "PR_LIST_UPDATED=true" >> $GITHUB_ENV | |
# Show the updated line | |
grep "DEFAULT_PR_NUMBERS:" "$WORKFLOW_FILE" | |
# Stage the change for commit | |
git add "$WORKFLOW_FILE" | |
# Remove backup | |
rm -f "${WORKFLOW_FILE}.bak" | |
else | |
echo "Error: Failed to update workflow file" | |
echo "Looking for DEFAULT_PR_NUMBERS line:" | |
grep "DEFAULT_PR_NUMBERS:" "$WORKFLOW_FILE" || echo "Line not found!" | |
# Restore from backup | |
mv "${WORKFLOW_FILE}.bak" "$WORKFLOW_FILE" | |
exit 1 | |
fi | |
else | |
echo "Warning: Workflow file not found at $WORKFLOW_FILE" | |
echo "Current directory: $(pwd)" | |
echo "Files in .github/workflows/:" | |
ls -la .github/workflows/ || echo "Directory not found" | |
exit 1 | |
fi | |
else | |
echo "No PRs to remove from the list" | |
fi | |
- name: Remove unwanted upstream workflow files | |
if: steps.merge_upstream.outputs.merge_success == 'true' | |
run: | | |
# Get list of our workflow files to keep | |
OUR_WORKFLOWS=("Reset to Upstream and Merge PRs.yml" "Build and Release.yml" "Pull from Upstream and Merge PRs.yml") | |
# Find and remove upstream workflow files | |
REMOVED_FILES=0 | |
for file in .github/workflows/*.yml .github/workflows/*.yaml; do | |
if [ -f "$file" ]; then | |
filename=$(basename "$file") | |
# Check if this file should be kept | |
KEEP_FILE=false | |
for keeper in "${OUR_WORKFLOWS[@]}"; do | |
if [ "$filename" == "$keeper" ]; then | |
KEEP_FILE=true | |
break | |
fi | |
done | |
if [ "$KEEP_FILE" == "false" ]; then | |
echo "Removing upstream workflow: $filename" | |
rm -f "$file" | |
((REMOVED_FILES++)) | |
fi | |
fi | |
done | |
# Commit if files were removed | |
if [ $REMOVED_FILES -gt 0 ]; then | |
git add .github/workflows | |
git commit -m "Remove $REMOVED_FILES upstream workflow file(s)" | |
fi | |
- name: Push changes | |
if: steps.merge_upstream.outputs.merge_success == 'true' | |
run: | | |
rm -f pr_status.txt merge_results.txt prs_to_merge.txt conflict_files.txt | |
# Check if we have any uncommitted changes (workflow file update) | |
if ! git diff --cached --quiet; then | |
git commit -m "Auto-update PR list: removed successfully merged PRs" | |
fi | |
# Push all changes | |
git push origin master | |
- name: Generate summary | |
if: always() | |
run: | | |
echo "## Workflow Summary" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
if [ "${{ steps.merge_upstream.outputs.merge_success }}" == "true" ]; then | |
echo "✅ Successfully merged from upstream" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "❌ Failed to merge from upstream due to conflicts" >> $GITHUB_STEP_SUMMARY | |
if [ -f "conflict_files.txt" ]; then | |
echo "" >> $GITHUB_STEP_SUMMARY | |
cat conflict_files.txt >> $GITHUB_STEP_SUMMARY | |
fi | |
fi | |
echo "" >> $GITHUB_STEP_SUMMARY | |
if [ -f "pr_status.txt" ]; then | |
cat pr_status.txt >> $GITHUB_STEP_SUMMARY | |
fi | |
if [ -f "merge_results.txt" ]; then | |
echo "" >> $GITHUB_STEP_SUMMARY | |
cat merge_results.txt >> $GITHUB_STEP_SUMMARY | |
fi | |
# Add info about PR list updates | |
if [ -n "${{ env.PR_LIST_UPDATED }}" ]; then | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "## PR List Update" >> $GITHUB_STEP_SUMMARY | |
echo "Automatically removed merged PRs from the workflow file" >> $GITHUB_STEP_SUMMARY | |
fi | |
- name: Send Discord notification on failure | |
uses: Ilshidur/action-discord@master | |
if: failure() | |
env: | |
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} | |
with: | |
args: | | |
❌ Merge from upstream failed! | |
Reason: ${{ steps.merge_upstream.outputs.merge_success == 'false' && 'Merge conflicts' || 'Unknown error' }} | |
${{ env.CONFLICT_COUNT && format('Conflicts in {0} files: {1}', env.CONFLICT_COUNT, env.CONFLICT_FILES) || '' }} | |
PRs attempted: ${{ github.event.inputs.pr_numbers || env.DEFAULT_PR_NUMBERS }} | |
See workflow logs: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
- name: Send Discord notification on success | |
uses: Ilshidur/action-discord@master | |
if: success() | |
env: | |
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} | |
with: | |
args: | | |
✅ Successfully synced with upstream! | |
${{ env.PR_LIST_UPDATED && '📝 PR list auto-updated - merged PRs removed' || '' }} | |
Check the summary: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
- name: Upload conflict information | |
if: failure() && steps.merge_upstream.outputs.merge_success == 'false' | |
uses: actions/upload-artifact@v4 | |
with: | |
name: merge-conflicts | |
path: conflict_files.txt | |
retention-days: 7 |