Skip to content

Pull from Upstream and Merge PRs #16

Pull from Upstream and Merge PRs

Pull from Upstream and Merge PRs #16

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