diff --git a/.github/actions/create-pull-request/action.yml b/.github/actions/create-pull-request/action.yml new file mode 100644 index 00000000000..01d287a0a9b --- /dev/null +++ b/.github/actions/create-pull-request/action.yml @@ -0,0 +1,134 @@ +name: 'Create Pull Request' +description: 'Creates or updates a pull request for changes to the repository' +inputs: + token: + description: 'GitHub token for authentication' + required: true + branch: + description: 'The pull request branch name' + required: true + base: + description: 'The base branch for the pull request' + required: false + default: 'main' + title: + description: 'The title of the pull request' + required: true + body: + description: 'The body of the pull request' + required: true + labels: + description: 'A newline-separated list of labels' + required: false + default: '' + commit-message: + description: 'The commit message to use when committing changes' + required: false + default: '[create-pull-request] automated change' + branch-already-exists: + description: 'Set to true if the branch is already pushed remotely (skips commit/push)' + required: false + default: 'false' +outputs: + pull-request-number: + description: 'The pull request number' + value: ${{ steps.create-pr.outputs.pull-request-number }} + pull-request-url: + description: 'The URL of the pull request' + value: ${{ steps.create-pr.outputs.pull-request-url }} + pull-request-operation: + description: 'The pull request operation performed (created, updated, none)' + value: ${{ steps.create-pr.outputs.pull-request-operation }} +runs: + using: 'composite' + steps: + - name: Configure git authentication + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + run: | + # Use the provided token for both git and gh operations + gh auth setup-git + + - name: Commit and push changes + if: inputs.branch-already-exists != 'true' + id: commit-and-push + shell: bash + env: + BRANCH: ${{ inputs.branch }} + COMMIT_MESSAGE: ${{ inputs.commit-message }} + run: | + # Check for any changes (staged, unstaged, or untracked) + if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then + echo "No changes to commit" + echo "has_changes=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "has_changes=true" >> $GITHUB_OUTPUT + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Create or reset branch + git checkout -B "$BRANCH" + git add -A + git commit -m "$COMMIT_MESSAGE" + git push -f origin "$BRANCH" + + - name: Create or update pull request + id: create-pr + if: inputs.branch-already-exists == 'true' || steps.commit-and-push.outputs.has_changes == 'true' + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + BRANCH: ${{ inputs.branch }} + BASE: ${{ inputs.base }} + PR_TITLE: ${{ inputs.title }} + PR_BODY: ${{ inputs.body }} + LABELS: ${{ inputs.labels }} + run: | + # Check if a PR already exists for this branch + EXISTING_PR=$(gh pr list --head "$BRANCH" --base "$BASE" --json number,url --jq '.[0] // empty') + + if [ -n "$EXISTING_PR" ]; then + PR_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number') + PR_URL=$(echo "$EXISTING_PR" | jq -r '.url') + echo "Pull request #$PR_NUMBER already exists: $PR_URL" + echo "pull-request-number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pull-request-url=$PR_URL" >> $GITHUB_OUTPUT + echo "pull-request-operation=none" >> $GITHUB_OUTPUT + else + # Build label arguments as a bash array (avoids eval/injection) + LABEL_ARGS=() + if [ -n "$LABELS" ]; then + while IFS= read -r label; do + label=$(echo "$label" | xargs) # trim whitespace + if [ -n "$label" ]; then + LABEL_ARGS+=(--label "$label") + fi + done <<< "$LABELS" + fi + + # Write body to a temp file to avoid shell quoting issues with special characters + BODY_FILE=$(mktemp) + trap 'rm -f "$BODY_FILE"' EXIT + printf '%s\n' "$PR_BODY" > "$BODY_FILE" + + # Create the pull request without eval — all args are properly quoted + PR_URL=$(gh pr create \ + --title "$PR_TITLE" \ + --body-file "$BODY_FILE" \ + --base "$BASE" \ + --head "$BRANCH" \ + "${LABEL_ARGS[@]}") + + rm -f "$BODY_FILE" + trap - EXIT + + PR_NUMBER=$(gh pr view "$BRANCH" --json number --jq '.number') + echo "Created pull request #$PR_NUMBER: $PR_URL" + echo "pull-request-number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pull-request-url=$PR_URL" >> $GITHUB_OUTPUT + echo "pull-request-operation=created" >> $GITHUB_OUTPUT + fi diff --git a/.github/workflows/generate-api-diffs.yml b/.github/workflows/generate-api-diffs.yml index 5214f1af134..ec531e97cb3 100644 --- a/.github/workflows/generate-api-diffs.yml +++ b/.github/workflows/generate-api-diffs.yml @@ -34,7 +34,7 @@ jobs: private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} - name: Create or update pull request - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} branch: update-api-diffs diff --git a/.github/workflows/generate-ats-diffs.yml b/.github/workflows/generate-ats-diffs.yml index 87b0ac7e545..4d57db40990 100644 --- a/.github/workflows/generate-ats-diffs.yml +++ b/.github/workflows/generate-ats-diffs.yml @@ -84,7 +84,7 @@ jobs: private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} - name: Create or update pull request - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} branch: update-ats-diffs diff --git a/.github/workflows/refresh-manifests.yml b/.github/workflows/refresh-manifests.yml index fa10c66dd91..f8f71afeba3 100644 --- a/.github/workflows/refresh-manifests.yml +++ b/.github/workflows/refresh-manifests.yml @@ -34,7 +34,7 @@ jobs: private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} - name: Create or update pull request - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} branch: update-manifests diff --git a/.github/workflows/refresh-typescript-sdks.yml b/.github/workflows/refresh-typescript-sdks.yml index f7b03d2da51..0ddea32c407 100644 --- a/.github/workflows/refresh-typescript-sdks.yml +++ b/.github/workflows/refresh-typescript-sdks.yml @@ -32,7 +32,7 @@ jobs: ./eng/refreshTypeScriptSdks.ps1 - name: Create or update pull request - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 + uses: ./.github/actions/create-pull-request with: token: ${{ secrets.GITHUB_TOKEN }} branch: update-typescript-playground-sdks diff --git a/.github/workflows/release-github-tasks.yml b/.github/workflows/release-github-tasks.yml index 5a6a26c7357..629187755f3 100644 --- a/.github/workflows/release-github-tasks.yml +++ b/.github/workflows/release-github-tasks.yml @@ -356,7 +356,7 @@ jobs: - name: Create Merge PR if: steps.check-pr.outputs.pr_exists != 'true' && inputs.dry_run != true - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 # v1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} title: "Merge ${{ inputs.release_branch }} to main after v${{ inputs.release_version }} release" @@ -372,6 +372,7 @@ jobs: *Created automatically by the release workflow.* branch: ${{ inputs.release_branch }} base: main + branch-already-exists: 'true' labels: | area-infrastructure release-automation @@ -476,7 +477,7 @@ jobs: - name: Create Baseline PR if: steps.check-pr.outputs.pr_exists != 'true' && inputs.dry_run != true - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 # v1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} title: "Update PackageValidationBaselineVersion to ${{ inputs.release_version }}" @@ -492,6 +493,7 @@ jobs: *Created automatically by the release workflow.* branch: update-baseline-${{ inputs.release_version }} base: main + branch-already-exists: 'true' labels: | area-infrastructure release-automation diff --git a/.github/workflows/update-ai-foundry-models.yml b/.github/workflows/update-ai-foundry-models.yml index 567d7ef0125..720e7c8d340 100644 --- a/.github/workflows/update-ai-foundry-models.yml +++ b/.github/workflows/update-ai-foundry-models.yml @@ -31,7 +31,7 @@ jobs: private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} - name: Create or update pull request - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} branch: update-ai-foundry-models diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 6b6495d279b..440ca9758bc 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -48,7 +48,7 @@ jobs: - name: Create Pull Request id: create-pr - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} branch: update-dependencies diff --git a/.github/workflows/update-github-models.yml b/.github/workflows/update-github-models.yml index 2f6574b7d18..6bb5a4fa7b0 100644 --- a/.github/workflows/update-github-models.yml +++ b/.github/workflows/update-github-models.yml @@ -32,7 +32,7 @@ jobs: private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} - name: Create or update pull request - uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 + uses: ./.github/actions/create-pull-request with: token: ${{ steps.app-token.outputs.token }} branch: update-github-models diff --git a/docs/release-process.md b/docs/release-process.md index 2d3bb07e5bd..56aed02a150 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -150,7 +150,7 @@ The pipeline uses the `Aspire-Release-Secrets` variable group. Note that NuGet p The workflow uses only pre-approved actions: - `actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683` (v4) -- `dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1` (v1) +- `./.github/actions/create-pull-request` (local composite action) ## Troubleshooting