|
63 | 63 | with: |
64 | 64 | token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} |
65 | 65 |
|
| 66 | + # Remember master's commit. A subsequent dispatch checks out the auto-update branch before |
| 67 | + # regenerating, so "did regeneration change anything vs. master?" is answered against this |
| 68 | + # recorded SHA rather than the current HEAD (which may be the branch tip by then). |
| 69 | + - name: Record master ref |
| 70 | + id: base |
| 71 | + run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" |
| 72 | + |
| 73 | + # Detect whether the auto-update branch already exists on the remote. If it does, a previous |
| 74 | + # dispatch for the same docs PR already opened a PR and we append to it; if not, the |
| 75 | + # signed-commit step creates the branch from master. |
| 76 | + - name: Determine auto-update branch state |
| 77 | + id: branch |
| 78 | + run: | |
| 79 | + if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then |
| 80 | + echo "exists=true" >> "$GITHUB_OUTPUT" |
| 81 | + echo "create=false" >> "$GITHUB_OUTPUT" |
| 82 | + else |
| 83 | + echo "exists=false" >> "$GITHUB_OUTPUT" |
| 84 | + echo "create=true" >> "$GITHUB_OUTPUT" |
| 85 | + fi |
| 86 | +
|
| 87 | + # When the branch already exists, check it out before regenerating so the regenerated models |
| 88 | + # are committed as a NEW commit on top of it — mirroring the new commit just pushed to the |
| 89 | + # apify-docs PR. signed-commit checks out this same branch internally; switching to it now, |
| 90 | + # while the tree is still clean, makes that internal checkout a no-op and keeps the regenerated |
| 91 | + # files staged. Staying on master and letting signed-commit switch branches would fail: the |
| 92 | + # regenerated files differ between master and the branch, and a plain `git checkout` refuses to |
| 93 | + # overwrite local modifications. |
| 94 | + - name: Check out existing auto-update branch |
| 95 | + if: steps.branch.outputs.exists == 'true' |
| 96 | + run: | |
| 97 | + git fetch origin "$BRANCH" |
| 98 | + git checkout -B "$BRANCH" FETCH_HEAD |
| 99 | +
|
66 | 100 | # Download the pre-built OpenAPI spec artifact from the apify-docs workflow run. |
67 | 101 | # Skipped for manual runs — datamodel-codegen will fetch from the published spec URL instead. |
68 | 102 | - name: Download OpenAPI spec artifact |
@@ -93,65 +127,46 @@ jobs: |
93 | 127 | uv run poe generate-models |
94 | 128 | fi |
95 | 129 |
|
96 | | - # Proceed only when regeneration actually changes the models relative to the current master. |
97 | | - # The job runs on a fresh master checkout, so anything already merged into master (e.g. a |
98 | | - # manually merged client PR carrying the same spec change, or a docs PR that merged master in |
99 | | - # and re-emits an already-applied change) produces no diff here and we skip — instead of |
100 | | - # opening a PR that just duplicates what master already has. |
| 130 | + # Proceed only when the regenerated models differ from the current master. Comparing against |
| 131 | + # the recorded master SHA (the working tree may now sit on the auto-update branch) skips runs |
| 132 | + # that would produce an empty PR — a spec change that doesn't affect the client models, or a |
| 133 | + # change already merged into master. On a brand-new branch it also avoids creating the branch |
| 134 | + # at all when there is nothing to regenerate. |
101 | 135 | - name: Check for model changes |
102 | 136 | id: changes |
103 | 137 | run: | |
104 | | - if git diff --quiet -- src/apify_client/_models.py src/apify_client/_typeddicts.py src/apify_client/_literals.py; then |
| 138 | + if git diff --quiet "${{ steps.base.outputs.sha }}" -- src/apify_client/_models.py src/apify_client/_typeddicts.py src/apify_client/_literals.py; then |
105 | 139 | echo "No model changes relative to master — nothing to regenerate." |
106 | 140 | echo "has_changes=false" >> "$GITHUB_OUTPUT" |
107 | 141 | else |
108 | 142 | echo "has_changes=true" >> "$GITHUB_OUTPUT" |
109 | 143 | fi |
110 | 144 |
|
111 | | - # Commit the regenerated models as a single commit on top of the current master and move the |
112 | | - # auto-update branch to it with one atomic force-push. Building on a fresh master checkout (not |
113 | | - # on the possibly stale existing branch) keeps the committed diff relative to the current |
114 | | - # master, so an already-merged change can never reappear as a fresh diff. |
| 145 | + # Append the regenerated models to the auto-update branch as a single signed ("Verified") |
| 146 | + # commit via apify/actions/signed-commit (GitHub's createCommitOnBranch GraphQL mutation). |
115 | 147 | # |
116 | | - # The branch goes straight from its previous state to "master + commit" in a single push — it |
117 | | - # is never published as being equal to master. This is the crux of the fix: the previous flow |
118 | | - # force-pushed the branch to master first and committed on top in a second step, and that |
119 | | - # intermediate "branch == master" state made the open PR's head identical to its base, which |
120 | | - # GitHub auto-closes. The next dispatch then opened a brand-new PR (`gh pr list --head` only |
121 | | - # matches open PRs), and the auto-closed one was left looking empty once its commit was orphaned |
122 | | - # by the following reset. Moving the branch forward in one step keeps the single existing PR |
123 | | - # open and simply updates it. |
| 148 | + # The commit is APPENDED on top of the branch's current tip — the branch is never reset to |
| 149 | + # master. A fresh docs PR creates the branch (create-branch) and its first commit; every later |
| 150 | + # dispatch, triggered by a new commit on the docs PR, adds another commit, so the client PR |
| 151 | + # mirrors the docs PR commit-for-commit and stays open throughout. The previous flow instead |
| 152 | + # force-pushed the branch to master before committing, and that intermediate "branch == master" |
| 153 | + # state made the open PR's head identical to its base, which GitHub auto-closes; each dispatch |
| 154 | + # then opened a brand-new duplicate PR (`gh pr list --head` only matches open PRs) and left the |
| 155 | + # auto-closed one looking empty. |
124 | 156 | # |
125 | | - # Trade-off vs. apify/actions/signed-commit (used before): the commit is authored with a plain |
126 | | - # `git commit` and is therefore not GPG-"Verified". Signed commits are not required on master |
127 | | - # (branch protection has required_signatures disabled) and this branch is always squash-merged |
128 | | - # through a reviewed PR, so the verified badge carries no weight here. |
129 | | - - name: Update branch with regenerated models |
| 157 | + # signed-commit stages the files against the branch tip and sets committed=false when they are |
| 158 | + # identical, so a repeated dispatch that regenerates the same models adds no commit. |
| 159 | + - name: Commit regenerated models |
130 | 160 | id: commit |
131 | 161 | if: steps.changes.outputs.has_changes == 'true' |
132 | | - run: | |
133 | | - git config user.name 'Apify Service Account' |
134 | | - git config user.email '64261774+apify-service-account@users.noreply.github.com' |
135 | | -
|
136 | | - files='src/apify_client/_models.py src/apify_client/_typeddicts.py src/apify_client/_literals.py' |
137 | | -
|
138 | | - # If the remote branch already carries exactly these regenerated files, there is nothing to |
139 | | - # do. Skipping the push avoids a redundant commit and the CI re-run it triggers when the |
140 | | - # same docs PR dispatches this workflow repeatedly without actually changing the spec. |
141 | | - if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then |
142 | | - git fetch --depth=1 origin "$BRANCH" |
143 | | - if git diff --quiet FETCH_HEAD -- $files; then |
144 | | - echo "Remote branch '$BRANCH' already has the current regenerated models — skipping push." |
145 | | - echo "pushed=false" >> "$GITHUB_OUTPUT" |
146 | | - exit 0 |
147 | | - fi |
148 | | - fi |
149 | | -
|
150 | | - git checkout -B "$BRANCH" |
151 | | - git add $files |
152 | | - git commit -m "$TITLE" |
153 | | - git push --force origin "$BRANCH" |
154 | | - echo "pushed=true" >> "$GITHUB_OUTPUT" |
| 162 | + uses: apify/actions/signed-commit@v1.2.0 |
| 163 | + with: |
| 164 | + message: ${{ env.TITLE }} |
| 165 | + add: 'src/apify_client/_models.py src/apify_client/_typeddicts.py src/apify_client/_literals.py' |
| 166 | + github-token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} |
| 167 | + branch: ${{ env.BRANCH }} |
| 168 | + create-branch: ${{ steps.branch.outputs.create }} |
| 169 | + retries: '3' |
155 | 170 |
|
156 | 171 | # Ensure exactly one PR exists for this branch. The branch is never auto-closed now, so an |
157 | 172 | # existing open PR is found and reused; only the first run (or one whose PR step was cancelled |
@@ -191,10 +206,10 @@ jobs: |
191 | 206 |
|
192 | 207 | # Post a cross-repo comment on the original docs PR so reviewers know about the corresponding |
193 | 208 | # client-python PR. Comment only when something actually happened — the companion PR was just |
194 | | - # created, or a real spec change was pushed to it. Repeated dispatches that change nothing |
195 | | - # (pushed=false) post no comment, avoiding noise on the docs PR. |
| 209 | + # created, or a new commit was appended to it. Repeated dispatches that change nothing |
| 210 | + # (committed=false) post no comment, avoiding noise on the docs PR. |
196 | 211 | - name: Comment on apify-docs PR |
197 | | - if: inputs.docs_pr_number && (steps.pr.outputs.created == 'true' || steps.commit.outputs.pushed == 'true') |
| 212 | + if: inputs.docs_pr_number && (steps.pr.outputs.created == 'true' || steps.commit.outputs.committed == 'true') |
198 | 213 | env: |
199 | 214 | GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} |
200 | 215 | PR_CREATED: ${{ steps.pr.outputs.created }} |
|
0 commit comments