|
1 | | -name: Sync .editorconfig from canonical |
| 1 | +name: Sync .editorconfig from canonical (PR-only) |
| 2 | + |
2 | 3 | on: |
| 4 | + # "Git-triggered polling": run on pushes in this repo |
3 | 5 | push: |
| 6 | + branches: |
| 7 | + - "**" |
| 8 | + branches-ignore: |
| 9 | + - "chore/sync-editorconfig" # bot branch created by this workflow |
| 10 | + # Fail early on PRs if branch is out-of-sync |
4 | 11 | pull_request: |
| 12 | + workflow_dispatch: |
5 | 13 |
|
6 | 14 | permissions: |
7 | 15 | contents: write |
8 | 16 | pull-requests: write |
9 | 17 |
|
10 | 18 | env: |
11 | | - # Replace with your canonical repo and branch if different |
12 | | - CANONICAL_REPO: BionicCode/bioniccode-code-style |
13 | | - CANONICAL_BRANCH: main |
| 19 | + CANONICAL_RAW_URL: https://raw.githubusercontent.com/BionicCode/bioniccode-code-style/main/.editorconfig |
| 20 | + SYNC_BRANCH: chore/sync-editorconfig |
| 21 | + |
| 22 | +concurrency: |
| 23 | + group: editorconfig-sync |
| 24 | + cancel-in-progress: true |
14 | 25 |
|
15 | 26 | jobs: |
16 | | - sync-editorconfig: |
17 | | - # Determine the branch to target: for pull_request use the PR head branch, otherwise use ref_name |
18 | | - env: |
19 | | - TARGET_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} |
20 | | - # true when this run is a pull_request from a fork; used to avoid using secrets for forked PRs |
21 | | - IS_FORK: ${{ github.event.pull_request != null && github.event.pull_request.head.repo.fork == true }} |
| 27 | + # 1) PR guard: make out-of-sync immediately visible to developers |
| 28 | + verify-on-pr: |
| 29 | + if: github.event_name == 'pull_request' |
22 | 30 | runs-on: ubuntu-latest |
23 | | - concurrency: |
24 | | - group: sync-editorconfig-${{ github.ref_name }} |
25 | | - cancel-in-progress: true |
26 | | - |
27 | 31 | steps: |
28 | | - - name: Exit if triggered by Actions bot (avoid loops) |
29 | | - if: ${{ github.actor == 'github-actions[bot]' }} |
30 | | - run: | |
31 | | - echo "Triggered by github-actions[bot], exiting to avoid loop." |
32 | | - exit 0 |
33 | | -
|
34 | | - - name: Checkout repository |
| 32 | + - name: Checkout PR head |
35 | 33 | uses: actions/checkout@v4 |
36 | 34 | with: |
37 | | - fetch-depth: 0 |
38 | | - persist-credentials: true |
| 35 | + ref: ${{ github.head_ref }} |
39 | 36 |
|
40 | | - - name: Fetch canonical .editorconfig (token clone if available and not a fork, otherwise raw URL) |
| 37 | + - name: Download canonical .editorconfig |
41 | 38 | run: | |
42 | | - set -e |
43 | | - CANONICAL_PATH="BionicCode.CodeStyle/BionicCode.CodeStyle/.editorconfig" |
44 | | - RAW_URL="https://raw.githubusercontent.com/${{ env.CANONICAL_REPO }}/${{ env.CANONICAL_BRANCH }}/${CANONICAL_PATH}" |
45 | | - echo "Canonical repo: ${{ env.CANONICAL_REPO }} (branch: ${{ env.CANONICAL_BRANCH }})" |
46 | | - echo "Using raw URL: $RAW_URL" |
| 39 | + set -euo pipefail |
| 40 | + curl -fsSL "${CANONICAL_RAW_URL}" -o canonical.editorconfig |
47 | 41 |
|
48 | | - # Only attempt token clone if a token is present AND this is not a forked PR |
49 | | - if [ -n "${{ secrets.CANONICAL_REPO_TOKEN }}" ] && [ "${{ env.IS_FORK }}" != "true" ]; then |
50 | | - echo "Attempting authenticated clone of canonical repository" |
51 | | - git clone --depth 1 https://x-access-token:${{ secrets.CANONICAL_REPO_TOKEN }}@github.com/${{ env.CANONICAL_REPO }} canonical-repo || true |
52 | | - if [ -f canonical-repo/.editorconfig ]; then |
53 | | - cp canonical-repo/.editorconfig canonical.editorconfig |
54 | | - echo "Fetched canonical .editorconfig via authenticated clone." |
55 | | - else |
56 | | - echo "Authenticated clone succeeded but canonical .editorconfig not found in canonical-repo" |
57 | | - fi |
58 | | - else |
59 | | - echo "No usable token (or forked PR). Falling back to public raw URL" |
60 | | - curl -fsSL "$RAW_URL" -o canonical.editorconfig || true |
61 | | - if [ -s canonical.editorconfig ]; then |
62 | | - echo "Fetched canonical .editorconfig via public raw URL." |
63 | | - else |
64 | | - echo "Failed to fetch canonical.editorconfig from raw URL" |
65 | | - fi |
66 | | - fi |
67 | | -
|
68 | | - - name: Ensure we have a canonical file |
| 42 | + - name: Ensure exactly one tracked .editorconfig (optional strictness) |
69 | 43 | run: | |
70 | | - if [ ! -s canonical.editorconfig ]; then |
71 | | - echo "Error: canonical.editorconfig not available. Aborting." >&2 |
| 44 | + set -euo pipefail |
| 45 | + count=$(git ls-files '**/.editorconfig' | wc -l | tr -d ' ') |
| 46 | + if [ "$count" -eq 0 ]; then |
| 47 | + echo "::error::.editorconfig is missing in this repo." |
| 48 | + exit 1 |
| 49 | + fi |
| 50 | + if [ "$count" -gt 1 ]; then |
| 51 | + echo "::error::Multiple .editorconfig files found. Keep only the repo-root .editorconfig to avoid overrides." |
| 52 | + git ls-files '**/.editorconfig' |
72 | 53 | exit 1 |
73 | 54 | fi |
74 | 55 |
|
75 | | - - name: Compare with repository .editorconfig |
76 | | - id: compare |
| 56 | + - name: Fail if branch .editorconfig is out of sync |
77 | 57 | run: | |
78 | | - if [ -f .editorconfig ] && cmp -s .editorconfig canonical.editorconfig; then |
79 | | - echo "same=true" >> $GITHUB_OUTPUT |
| 58 | + set -euo pipefail |
| 59 | + if cmp -s .editorconfig canonical.editorconfig; then |
| 60 | + echo ".editorconfig is in sync with canonical." |
80 | 61 | else |
81 | | - echo "same=false" >> $GITHUB_OUTPUT |
| 62 | + echo "::error::.editorconfig is OUT OF SYNC with canonical. Merge the sync PR (or rerun the sync workflow)." |
| 63 | + exit 1 |
82 | 64 | fi |
83 | 65 |
|
84 | | - - name: Prepare workspace with canonical .editorconfig |
85 | | - if: steps.compare.outputs.same == 'false' |
86 | | - run: | |
87 | | - # Overwrite or create the repo's .editorconfig with the canonical file |
88 | | - cp canonical.editorconfig .editorconfig |
89 | | - # Debug: show file summary to make sure the copy happened |
90 | | - echo "Workspace .editorconfig:" |
91 | | - ls -la .editorconfig || true |
92 | | - echo "---- head of .editorconfig ----" |
93 | | - sed -n '1,40p' .editorconfig || true |
94 | | - echo "--------------------------------" |
| 66 | + # 2) PR creator/updater: always proposes canonical via one rolling PR |
| 67 | + open-or-update-sync-pr: |
| 68 | + if: github.event_name != 'pull_request' && github.actor != 'github-actions[bot]' |
| 69 | + runs-on: ubuntu-latest |
| 70 | + steps: |
| 71 | + - name: Checkout default branch |
| 72 | + uses: actions/checkout@v4 |
| 73 | + with: |
| 74 | + ref: ${{ github.event.repository.default_branch }} |
95 | 75 |
|
96 | | - - name: Commit and attempt to push .editorconfig to target branch (sets push_succeeded) |
97 | | - id: commit_push |
98 | | - if: steps.compare.outputs.same == 'false' |
| 76 | + - name: Download canonical .editorconfig |
99 | 77 | run: | |
100 | | - # We'll always write an output push_succeeded (true/false). The step must succeed |
101 | | - # so the workflow can run a fallback PR when push fails. |
102 | | - set -o pipefail |
103 | | - PUSH_SUCCEEDED=false |
| 78 | + set -euo pipefail |
| 79 | + curl -fsSL "${CANONICAL_RAW_URL}" -o canonical.editorconfig |
104 | 80 |
|
105 | | - echo "Preparing to commit .editorconfig to branch '${{ env.TARGET_BRANCH }}'" |
106 | | -
|
107 | | - # Configure git for the automated commit |
108 | | - git config user.name "github-actions[bot]" |
109 | | - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" |
110 | | -
|
111 | | - # Only attempt push when this run is allowed to push (push events, or PRs from the same repo) |
112 | | - if [ "${{ github.event_name }}" = "push" ] || \ |
113 | | - ( [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.full_name }}" = "${{ github.repository }}" ] ); then |
114 | | -
|
115 | | - echo "Push permitted for this run; attempting to commit and push." |
116 | | -
|
117 | | - # Ensure we have an up-to-date view of the remote target branch |
118 | | - git fetch origin "${{ env.TARGET_BRANCH }}" || true |
119 | | -
|
120 | | - # Checkout the target branch (create it locally if it doesn't exist) |
121 | | - if git rev-parse --verify "${{ env.TARGET_BRANCH }}" >/dev/null 2>&1; then |
122 | | - git checkout "${{ env.TARGET_BRANCH }}" |
123 | | - else |
124 | | - git checkout -b "${{ env.TARGET_BRANCH }}" |
125 | | - fi |
126 | | -
|
127 | | - # Add and commit .editorconfig |
128 | | - git add .editorconfig |
129 | | -
|
130 | | - if git diff --staged --quiet; then |
131 | | - echo "No staged changes to commit. Nothing to push." |
132 | | - PUSH_SUCCEEDED=true |
133 | | - else |
134 | | - git commit -m "chore(sync): update .editorconfig from canonical" --author="github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" || true |
135 | | -
|
136 | | - # Push and capture exit code instead of failing the step |
137 | | - echo "Pushing to origin/${{ env.TARGET_BRANCH }}..." |
138 | | - if git push origin "${{ env.TARGET_BRANCH }}"; then |
139 | | - echo "Push successful." |
140 | | - PUSH_SUCCEEDED=true |
141 | | - else |
142 | | - echo "Push failed (remote rejected or permission error). Will fall back to PR." |
143 | | - PUSH_SUCCEEDED=false |
144 | | - fi |
145 | | - fi |
146 | | - else |
147 | | - echo "This run is not permitted to push (e.g., forked PR). Skipping direct push and will fall back to PR." |
148 | | - PUSH_SUCCEEDED=false |
149 | | - fi |
150 | | -
|
151 | | - # Emit the result for later steps |
152 | | - if [ "$PUSH_SUCCEEDED" = "true" ]; then |
153 | | - echo "push_succeeded=true" >> $GITHUB_OUTPUT |
| 81 | + - name: Detect changes |
| 82 | + id: diff |
| 83 | + run: | |
| 84 | + set -euo pipefail |
| 85 | + if [ -f .editorconfig ] && cmp -s .editorconfig canonical.editorconfig; then |
| 86 | + echo "changed=false" >> "$GITHUB_OUTPUT" |
154 | 87 | else |
155 | | - echo "push_succeeded=false" >> $GITHUB_OUTPUT |
| 88 | + echo "changed=true" >> "$GITHUB_OUTPUT" |
| 89 | + mv canonical.editorconfig .editorconfig |
156 | 90 | fi |
157 | 91 |
|
158 | | - - name: "Fallback: open PR if direct push was not possible/failed" |
159 | | - if: steps.compare.outputs.same == 'false' && steps.commit_push.outputs.push_succeeded != 'true' |
160 | | - uses: peter-evans/create-pull-request@v5 |
| 92 | + - name: Create or update PR |
| 93 | + if: steps.diff.outputs.changed == 'true' |
| 94 | + uses: peter-evans/create-pull-request@v7 |
161 | 95 | with: |
162 | | - branch: sync-editorconfig/${{ github.run_id }} |
163 | | - base: ${{ env.TARGET_BRANCH }} |
164 | | - title: "chore: sync .editorconfig with canonical" |
165 | | - body: | |
166 | | - This automated PR updates or adds .editorconfig from the canonical repository (${{ env.CANONICAL_REPO }}@${{ env.CANONICAL_BRANCH }}). |
167 | | - Please merge or pull this update to ensure your local analyzers and formatting use the canonical rules. |
| 96 | + branch: ${{ env.SYNC_BRANCH }} |
| 97 | + base: ${{ github.event.repository.default_branch }} |
| 98 | + title: "chore: sync .editorconfig from canonical" |
168 | 99 | commit-message: "chore(sync): update .editorconfig from canonical" |
| 100 | + body: | |
| 101 | + Automated sync of `.editorconfig` from the canonical repository. |
| 102 | +
|
| 103 | + Source: BionicCode/bioniccode-code-style (main) |
| 104 | + add-paths: | |
| 105 | + .editorconfig |
169 | 106 | labels: automation |
170 | | - add-paths: .editorconfig |
171 | | - author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" |
0 commit comments