Sync with Upstream #96
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
| # ============================================================================= | |
| # Upstream Sync Workflow | |
| # ============================================================================= | |
| # Automatically syncs this repository with the upstream repository. | |
| # Creates a PR when new changes are detected from upstream. | |
| # | |
| # Local repository: current default branch (main) | |
| # Upstream source: CJackHwang/AIstudioProxyAPI (main) | |
| # | |
| # CONFLICT HANDLING: | |
| # README.md can conflict when this repository keeps custom docs/content. | |
| # The workflow auto-resolves README.md conflicts by keeping local version. | |
| # Other conflicts will fail the workflow and require manual intervention. | |
| # ============================================================================= | |
| name: Sync with Upstream | |
| on: | |
| # Run every 6 hours (at 00:00, 06:00, 12:00, 18:00 UTC) | |
| # This ensures timely detection of upstream changes while avoiding excessive API calls | |
| schedule: | |
| - cron: '0 */6 * * *' | |
| # Allow manual trigger from GitHub Actions UI | |
| workflow_dispatch: | |
| inputs: | |
| force_sync: | |
| description: 'Force sync even if no new commits detected' | |
| required: false | |
| default: false | |
| type: boolean | |
| # Ensure only one sync workflow runs at a time | |
| concurrency: | |
| group: upstream-sync | |
| cancel-in-progress: true | |
| jobs: | |
| sync: | |
| name: Sync Repository with Upstream | |
| runs-on: ubuntu-latest | |
| # Required permissions for creating PRs and pushing branches | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| # ----------------------------------------------------------------------- | |
| # Step 1: Checkout current repository | |
| # ----------------------------------------------------------------------- | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| # Fetch all history to properly compare with upstream | |
| fetch-depth: 0 | |
| # Use the default GITHUB_TOKEN | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| # ----------------------------------------------------------------------- | |
| # Step 2: Configure Git identity for commits | |
| # ----------------------------------------------------------------------- | |
| - name: Configure Git Identity | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # ----------------------------------------------------------------------- | |
| # Step 3: Add upstream remote and fetch latest changes | |
| # ----------------------------------------------------------------------- | |
| - name: Add Upstream Remote | |
| run: | | |
| echo "Adding upstream remote..." | |
| git remote add upstream https://github.com/CJackHwang/AIstudioProxyAPI.git | |
| echo "Fetching upstream changes..." | |
| git fetch upstream main --tags | |
| echo "Upstream remote added and fetched successfully." | |
| # ----------------------------------------------------------------------- | |
| # Step 4: Check for new commits from upstream | |
| # ----------------------------------------------------------------------- | |
| - name: Check for Upstream Changes | |
| id: check_changes | |
| run: | | |
| echo "Comparing local main with upstream/main..." | |
| # Get commit counts | |
| LOCAL_COMMIT=$(git rev-parse HEAD) | |
| UPSTREAM_COMMIT=$(git rev-parse upstream/main) | |
| echo "Local HEAD: $LOCAL_COMMIT" | |
| echo "Upstream HEAD: $UPSTREAM_COMMIT" | |
| # Count commits ahead and behind | |
| COMMITS_BEHIND=$(git rev-list --count HEAD..upstream/main) | |
| COMMITS_AHEAD=$(git rev-list --count upstream/main..HEAD) | |
| echo "Commits behind upstream: $COMMITS_BEHIND" | |
| echo "Commits ahead of upstream: $COMMITS_AHEAD" | |
| # Determine if sync is needed | |
| if [ "$COMMITS_BEHIND" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT | |
| echo "::notice::Found $COMMITS_BEHIND new commit(s) from upstream" | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "commits_behind=0" >> $GITHUB_OUTPUT | |
| echo "::notice::Fork is up to date with upstream" | |
| fi | |
| # Get the date for PR title | |
| echo "sync_date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT | |
| # Get recent upstream commit messages for PR body | |
| if [ "$COMMITS_BEHIND" -gt 0 ]; then | |
| echo "Getting recent upstream commits..." | |
| COMMIT_LOG=$(git log --oneline HEAD..upstream/main | head -20) | |
| # Write commit log to file for multi-line output | |
| echo "$COMMIT_LOG" > /tmp/commit_log.txt | |
| # Use delimiter for multi-line output | |
| { | |
| echo 'commit_log<<EOF' | |
| cat /tmp/commit_log.txt | |
| echo 'EOF' | |
| } >> $GITHUB_OUTPUT | |
| fi | |
| # ----------------------------------------------------------------------- | |
| # Step 5: Create sync branch with upstream changes | |
| # ----------------------------------------------------------------------- | |
| # CONFLICT RESOLUTION STRATEGY: | |
| # 1. Attempt merge with upstream | |
| # 2. If conflicts occur, check if ONLY expected files conflict (README.md) | |
| # 3. Auto-resolve expected conflicts by keeping fork's version (--ours) | |
| # 4. Fail only if unexpected files have conflicts | |
| # ----------------------------------------------------------------------- | |
| - name: Create Sync Branch | |
| id: create_branch | |
| if: steps.check_changes.outputs.has_changes == 'true' || github.event.inputs.force_sync == 'true' | |
| run: | | |
| BRANCH_NAME="sync/upstream-${{ steps.check_changes.outputs.sync_date }}" | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| echo "Creating sync branch: $BRANCH_NAME" | |
| # Check if branch already exists remotely | |
| if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then | |
| echo "::warning::Sync branch already exists. Deleting and recreating..." | |
| git push origin --delete "$BRANCH_NAME" || true | |
| fi | |
| # Create new branch from current main | |
| git checkout -b "$BRANCH_NAME" | |
| echo "Merging upstream/main into sync branch..." | |
| # ===================================================================== | |
| # EXPECTED CONFLICT FILES (Translation Fork) | |
| # These files are expected to conflict because they are intentionally | |
| # different in this English fork vs the Chinese upstream. | |
| # ===================================================================== | |
| EXPECTED_CONFLICT_FILES="README.md" | |
| # Attempt merge with upstream | |
| # Using --no-edit to auto-generate merge commit message | |
| if git merge upstream/main --no-edit --allow-unrelated-histories; then | |
| echo "merge_success=true" >> $GITHUB_OUTPUT | |
| echo "auto_resolved=false" >> $GITHUB_OUTPUT | |
| echo "::notice::Merge successful (no conflicts)" | |
| else | |
| echo "::warning::Merge conflicts detected. Checking if auto-resolvable..." | |
| # Get list of conflicting files | |
| CONFLICTING_FILES=$(git diff --name-only --diff-filter=U) | |
| echo "Conflicting files:" | |
| echo "$CONFLICTING_FILES" | |
| # Check if all conflicts are in expected files | |
| UNEXPECTED_CONFLICTS="" | |
| for file in $CONFLICTING_FILES; do | |
| IS_EXPECTED=false | |
| for expected in $EXPECTED_CONFLICT_FILES; do | |
| if [ "$file" = "$expected" ]; then | |
| IS_EXPECTED=true | |
| break | |
| fi | |
| done | |
| if [ "$IS_EXPECTED" = false ]; then | |
| UNEXPECTED_CONFLICTS="$UNEXPECTED_CONFLICTS $file" | |
| fi | |
| done | |
| # Trim whitespace | |
| UNEXPECTED_CONFLICTS=$(echo "$UNEXPECTED_CONFLICTS" | xargs) | |
| if [ -n "$UNEXPECTED_CONFLICTS" ]; then | |
| # Unexpected conflicts found - fail the workflow | |
| echo "merge_success=false" >> $GITHUB_OUTPUT | |
| echo "auto_resolved=false" >> $GITHUB_OUTPUT | |
| echo "::error::Unexpected conflicts in: $UNEXPECTED_CONFLICTS" | |
| echo "::error::Manual intervention required for these files." | |
| # Abort the merge to leave clean state | |
| git merge --abort | |
| exit 1 | |
| else | |
| # Only expected conflicts - auto-resolve by keeping fork's version | |
| echo "::notice::All conflicts are in expected files. Auto-resolving..." | |
| for file in $CONFLICTING_FILES; do | |
| echo " Resolving $file by keeping fork's version (--ours)..." | |
| git checkout --ours "$file" | |
| git add "$file" | |
| done | |
| # Complete the merge with resolved conflicts | |
| git commit --no-edit | |
| echo "merge_success=true" >> $GITHUB_OUTPUT | |
| echo "auto_resolved=true" >> $GITHUB_OUTPUT | |
| echo "::notice::Merge successful (auto-resolved expected conflicts)" | |
| fi | |
| fi | |
| # ----------------------------------------------------------------------- | |
| # Step 6: Push sync branch to origin | |
| # ----------------------------------------------------------------------- | |
| - name: Push Sync Branch | |
| if: steps.create_branch.outputs.merge_success == 'true' | |
| run: | | |
| echo "Pushing sync branch to origin..." | |
| git push origin "${{ steps.create_branch.outputs.branch_name }}" | |
| echo "::notice::Sync branch pushed successfully" | |
| # ----------------------------------------------------------------------- | |
| # Step 7: Create Pull Request | |
| # ----------------------------------------------------------------------- | |
| - name: Create Pull Request | |
| if: steps.create_branch.outputs.merge_success == 'true' | |
| uses: peter-evans/create-pull-request@v7 | |
| id: create_pr | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| branch: ${{ steps.create_branch.outputs.branch_name }} | |
| base: main | |
| title: "chore: Sync with upstream [${{ steps.check_changes.outputs.sync_date }}]" | |
| body: | | |
| ## Upstream Sync | |
| This PR syncs the fork with the upstream repository. | |
| ### Summary | |
| - **Upstream Repository**: [CJackHwang/AIstudioProxyAPI](https://github.com/CJackHwang/AIstudioProxyAPI) | |
| - **Commits Behind**: ${{ steps.check_changes.outputs.commits_behind }} | |
| - **Sync Date**: ${{ steps.check_changes.outputs.sync_date }} | |
| - **Auto-resolved Conflicts**: ${{ steps.create_branch.outputs.auto_resolved == 'true' && '✅ Yes (README.md kept from fork)' || '❌ None' }} | |
| ### Recent Upstream Commits | |
| ``` | |
| ${{ steps.check_changes.outputs.commit_log }} | |
| ``` | |
| ### Review Notes | |
| - Please review changes carefully before merging | |
| - Check for any conflicts with fork-specific modifications | |
| - Ensure translation/localization files are preserved | |
| ${{ steps.create_branch.outputs.auto_resolved == 'true' && '- ⚠️ **README.md conflict was auto-resolved** - The English README from this fork was preserved' || '' }} | |
| --- | |
| *This PR was automatically generated by the [Upstream Sync Workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})* | |
| labels: | | |
| upstream-sync | |
| automated | |
| draft: false | |
| delete-branch: false | |
| # ----------------------------------------------------------------------- | |
| # Step 8: Output Results | |
| # ----------------------------------------------------------------------- | |
| - name: Output Results | |
| if: always() | |
| run: | | |
| echo "==========================================" | |
| echo "Upstream Sync Workflow Complete" | |
| echo "==========================================" | |
| echo "" | |
| if [ "${{ steps.check_changes.outputs.has_changes }}" == "true" ]; then | |
| echo "Status: Changes detected from upstream" | |
| echo "Commits behind: ${{ steps.check_changes.outputs.commits_behind }}" | |
| if [ "${{ steps.create_branch.outputs.merge_success }}" == "true" ]; then | |
| echo "Merge: Successful" | |
| if [ "${{ steps.create_branch.outputs.auto_resolved }}" == "true" ]; then | |
| echo "Conflicts: Auto-resolved (expected files only)" | |
| echo " - README.md: Kept fork's English version" | |
| else | |
| echo "Conflicts: None" | |
| fi | |
| echo "PR Created: Yes" | |
| echo "Branch: ${{ steps.create_branch.outputs.branch_name }}" | |
| else | |
| echo "Merge: Failed (unexpected conflicts detected)" | |
| echo "PR Created: No" | |
| echo "" | |
| echo "Manual intervention required to resolve conflicts." | |
| echo "Expected conflict files (auto-resolved): README.md" | |
| echo "Unexpected conflicts require manual resolution." | |
| fi | |
| else | |
| echo "Status: Fork is up to date with upstream" | |
| echo "No action required." | |
| fi | |
| echo "" | |
| echo "==========================================" |