Skip to content

Commit 4320041

Browse files
feat: signed commits (v7) (#3057)
* Add support for signed commits (#3055) * formatting * fix eslint and lint errors * shift setting the base to before the push * sign commits by default for testing * add debug lines * read to buffer not string and use non-legacy method to base64 * debug payload without contents * disable linter for debug code * fix filepath when using path input * try to fix head repo * remove commented code * Try refactor of file changes * add tests for building file changes * add build file changes test for binary files * refactor graphql code into github helper class * build file changes even when there is no diff * add function to get commit detail * fix format * build branch commits * use source mode for deleted files * try rest api route * fix check for branch existence * force push * try fix base tree * debug commit verification * debug commit verification * fix format and cleanup * add executable mode file to test * limit blob creation concurrency * only build commits when feature enabled * remove unused code * update readme link * update docs for commit signing * fix capital letter * update docs * add throttling * set default back to false * output head sha and verified status * log outputs * fix head sha output * default the operation output to none * output retryafter for secondary rate limit * use separate client for branch and pull operations * add maintainer-can-modify input * rename git-token to branch-token * fix branch token input * remove deprecated env output * update docs * fix doc * update docs * build branch commits when there is a diff with the base * check verification status of head commit when not known * fix verified output when no commit signing is being used * draft always-true * convert to draft on branch updates when there is a diff with base * update docs with blob size limit * catch errors during blob creation for debugging * parse empty commits * pass base commit to push signed commits * use parent commit details in create commit * use parent tree for base_tree * multipart tree creation * update docs * update readme about the permissions of the default token * fix edge case where changes are partially merged * add updating documentation * fix typo * update major version --------- Co-authored-by: Ravi <1299606+rustycl0ck@users.noreply.github.com>
1 parent 0c2a66f commit 4320041

20 files changed

+53407
-29242
lines changed

README.md

+42-14
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ Create Pull Request action will:
3232
# Make changes to pull request here
3333

3434
- name: Create Pull Request
35-
uses: peter-evans/create-pull-request@v6
35+
uses: peter-evans/create-pull-request@v7
3636
```
3737
38-
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v6.x.x`
38+
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v7.x.x`
3939

4040
### Workflow permissions
4141

@@ -48,12 +48,10 @@ For repositories belonging to an organization, this setting can be managed by ad
4848

4949
All inputs are **optional**. If not set, sensible defaults will be used.
5050

51-
**Note**: If you want pull requests created by this action to trigger an `on: push` or `on: pull_request` workflow then you cannot use the default `GITHUB_TOKEN`. See the [documentation here](docs/concepts-guidelines.md#triggering-further-workflow-runs) for workarounds.
52-
5351
| Name | Description | Default |
5452
| --- | --- | --- |
55-
| `token` | `GITHUB_TOKEN` (permissions `contents: write` and `pull-requests: write`) or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
56-
| `git-token` | The [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that the action will use for git operations. | Defaults to the value of `token` |
53+
| `token` | The token that the action will use to create and update the pull request. See [token](#token). | `GITHUB_TOKEN` |
54+
| `branch-token` | The token that the action will use to create and update the branch. See [branch-token](#branch-token). | Defaults to the value of `token` |
5755
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
5856
| `add-paths` | A comma or newline-separated list of file paths to commit. Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax. If no paths are specified, all new and modified files are added. See [Add specific paths](#add-specific-paths). | |
5957
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[create-pull-request] automated change` |
@@ -65,6 +63,7 @@ All inputs are **optional**. If not set, sensible defaults will be used.
6563
| `branch-suffix` | The branch suffix type when using the alternative branching strategy. Valid values are `random`, `timestamp` and `short-commit-hash`. See [Alternative strategy](#alternative-strategy---always-create-a-new-pull-request-branch) for details. | |
6664
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
6765
| `push-to-fork` | A fork of the checked-out parent repository to which the pull request branch will be pushed. e.g. `owner/repo-fork`. The pull request will be created to merge the fork's branch into the parent's base. See [push pull request branches to a fork](docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork) for details. | |
66+
| `sign-commits` | Sign commits as `github-actions[bot]` when using `GITHUB_TOKEN`, or your own bot when using [GitHub App tokens](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens). See [commit signing](docs/concepts-guidelines.md#commit-signature-verification-for-bots) for details. | `false` |
6867
| `title` | The title of the pull request. | `Changes by create-pull-request action` |
6968
| `body` | The body of the pull request. | `Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action` |
7069
| `body-path` | The path to a file containing the pull request body. Takes precedence over `body`. | |
@@ -73,7 +72,35 @@ All inputs are **optional**. If not set, sensible defaults will be used.
7372
| `reviewers` | A comma or newline-separated list of reviewers (GitHub usernames) to request a review from. | |
7473
| `team-reviewers` | A comma or newline-separated list of GitHub teams to request a review from. Note that a `repo` scoped [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token), or equivalent [GitHub App permissions](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens), are required. | |
7574
| `milestone` | The number of the milestone to associate this pull request with. | |
76-
| `draft` | Create a [draft pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). It is not possible to change draft status after creation except through the web interface. | `false` |
75+
| `draft` | Create a [draft pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). Valid values are `true` (only on create), `always-true` (on create and update), and `false`. | `false` |
76+
| `maintainer-can-modify` | Indicates whether [maintainers can modify](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) the pull request. | `true` |
77+
78+
#### token
79+
80+
The token input defaults to the repository's `GITHUB_TOKEN`.
81+
82+
> [!IMPORTANT]
83+
> - If you want pull requests created by this action to trigger an `on: push` or `on: pull_request` workflow then you cannot use the default `GITHUB_TOKEN`. See the [documentation here](docs/concepts-guidelines.md#triggering-further-workflow-runs) for further details.
84+
> - If using the repository's `GITHUB_TOKEN` and your repository was created after 2nd February 2023, the [default permission is read-only](https://github.blog/changelog/2023-02-02-github-actions-updating-the-default-github_token-permissions-to-read-only/). Elevate the [permissions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token#defining-access-for-the-github_token-permissions) in your workflow.
85+
> ```yml
86+
> permissions:
87+
> contents: write
88+
> pull-requests: write
89+
> ```
90+
91+
Other token options:
92+
- Classic [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with `repo` scope.
93+
- Fine-grained [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with `contents: write` and `pull-requests: write` scopes.
94+
- [GitHub App tokens](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens) with `contents: write` and `pull-requests: write` scopes.
95+
96+
> [!TIP]
97+
> If pull requests could contain changes to Actions workflows you may also need the `workflows` scope.
98+
99+
#### branch-token
100+
101+
The action first creates a branch, and then creates a pull request for the branch.
102+
For some rare use cases it can be useful, or even neccessary, to use different tokens for these operations.
103+
It is not advisable to use this input unless you know you need to.
77104

78105
#### commit-message
79106

@@ -104,7 +131,7 @@ If you want branches to be deleted immediately on merge then you should use GitH
104131
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
105132
```yml
106133
- name: Create Pull Request
107-
uses: peter-evans/create-pull-request@v6
134+
uses: peter-evans/create-pull-request@v7
108135
env:
109136
https_proxy: http://<proxy_address>:<port>
110137
```
@@ -115,17 +142,18 @@ The following outputs can be used by subsequent workflow steps.
115142

116143
- `pull-request-number` - The pull request number.
117144
- `pull-request-url` - The URL of the pull request.
118-
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated` or `closed`.
145+
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated`, `closed` or `none`.
119146
- `pull-request-head-sha` - The commit SHA of the pull request branch.
120147
- `pull-request-branch` - The branch name of the pull request.
148+
- `pull-request-commits-verified` - Whether GitHub considers the signature of the branch's commits to be verified; `true` or `false`.
121149

122150
Step outputs can be accessed as in the following example.
123151
Note that in order to read the step outputs the action step must have an id.
124152

125153
```yml
126154
- name: Create Pull Request
127155
id: cpr
128-
uses: peter-evans/create-pull-request@v6
156+
uses: peter-evans/create-pull-request@v7
129157
- name: Check outputs
130158
if: ${{ steps.cpr.outputs.pull-request-number }}
131159
run: |
@@ -188,7 +216,7 @@ File changes that do not match one of the paths will be stashed and restored aft
188216

189217
```yml
190218
- name: Create Pull Request
191-
uses: peter-evans/create-pull-request@v6
219+
uses: peter-evans/create-pull-request@v7
192220
with:
193221
add-paths: |
194222
*.java
@@ -215,7 +243,7 @@ Note that the repository must be checked out on a branch with a remote, it won't
215243
- name: Uncommitted change
216244
run: date +%s > report.txt
217245
- name: Create Pull Request
218-
uses: peter-evans/create-pull-request@v6
246+
uses: peter-evans/create-pull-request@v7
219247
```
220248

221249
### Create a project card
@@ -225,7 +253,7 @@ To create a project card for the pull request, pass the `pull-request-number` st
225253
```yml
226254
- name: Create Pull Request
227255
id: cpr
228-
uses: peter-evans/create-pull-request@v6
256+
uses: peter-evans/create-pull-request@v7
229257
230258
- name: Create or Update Project Card
231259
if: ${{ steps.cpr.outputs.pull-request-number }}
@@ -260,7 +288,7 @@ jobs:
260288
261289
- name: Create Pull Request
262290
id: cpr
263-
uses: peter-evans/create-pull-request@v6
291+
uses: peter-evans/create-pull-request@v7
264292
with:
265293
token: ${{ secrets.PAT }}
266294
commit-message: Update report

__test__/create-or-update-branch.int.test.ts

+218-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {
22
createOrUpdateBranch,
33
tryFetch,
4-
getWorkingBaseAndType
4+
getWorkingBaseAndType,
5+
buildBranchCommits
56
} from '../lib/create-or-update-branch'
67
import * as fs from 'fs'
78
import {GitCommandManager} from '../lib/git-command-manager'
@@ -229,6 +230,77 @@ describe('create-or-update-branch tests', () => {
229230
expect(workingBaseType).toEqual('commit')
230231
})
231232

233+
it('tests buildBranchCommits with no diff', async () => {
234+
await git.checkout(BRANCH, BASE)
235+
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
236+
expect(branchCommits.length).toEqual(0)
237+
})
238+
239+
it('tests buildBranchCommits with addition and modification', async () => {
240+
await git.checkout(BRANCH, BASE)
241+
await createChanges()
242+
const UNTRACKED_EXE_FILE = 'a/script.sh'
243+
const filepath = path.join(REPO_PATH, UNTRACKED_EXE_FILE)
244+
await fs.promises.writeFile(filepath, '#!/usr/bin/env bash', {mode: 0o755})
245+
await git.exec(['add', '-A'])
246+
await git.commit(['-m', 'Test changes'])
247+
248+
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
249+
250+
expect(branchCommits.length).toEqual(1)
251+
expect(branchCommits[0].subject).toEqual('Test changes')
252+
expect(branchCommits[0].changes.length).toEqual(3)
253+
expect(branchCommits[0].changes).toEqual([
254+
{mode: '100755', path: UNTRACKED_EXE_FILE, status: 'A'},
255+
{mode: '100644', path: TRACKED_FILE, status: 'M'},
256+
{mode: '100644', path: UNTRACKED_FILE, status: 'A'}
257+
])
258+
})
259+
260+
it('tests buildBranchCommits with addition and deletion', async () => {
261+
await git.checkout(BRANCH, BASE)
262+
await createChanges()
263+
const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt'
264+
const filepath = path.join(REPO_PATH, TRACKED_FILE_NEW_PATH)
265+
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
266+
await fs.promises.rename(path.join(REPO_PATH, TRACKED_FILE), filepath)
267+
await git.exec(['add', '-A'])
268+
await git.commit(['-m', 'Test changes'])
269+
270+
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
271+
272+
expect(branchCommits.length).toEqual(1)
273+
expect(branchCommits[0].subject).toEqual('Test changes')
274+
expect(branchCommits[0].changes.length).toEqual(3)
275+
expect(branchCommits[0].changes).toEqual([
276+
{mode: '100644', path: TRACKED_FILE, status: 'D'},
277+
{mode: '100644', path: UNTRACKED_FILE, status: 'A'},
278+
{mode: '100644', path: TRACKED_FILE_NEW_PATH, status: 'A'}
279+
])
280+
})
281+
282+
it('tests buildBranchCommits with multiple commits', async () => {
283+
await git.checkout(BRANCH, BASE)
284+
for (let i = 0; i < 3; i++) {
285+
await createChanges()
286+
await git.exec(['add', '-A'])
287+
await git.commit(['-m', `Test changes ${i}`])
288+
}
289+
290+
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
291+
292+
expect(branchCommits.length).toEqual(3)
293+
for (let i = 0; i < 3; i++) {
294+
expect(branchCommits[i].subject).toEqual(`Test changes ${i}`)
295+
expect(branchCommits[i].changes.length).toEqual(2)
296+
const untrackedFileStatus = i == 0 ? 'A' : 'M'
297+
expect(branchCommits[i].changes).toEqual([
298+
{mode: '100644', path: TRACKED_FILE, status: 'M'},
299+
{mode: '100644', path: UNTRACKED_FILE, status: untrackedFileStatus}
300+
])
301+
}
302+
})
303+
232304
it('tests no changes resulting in no new branch being created', async () => {
233305
const commitMessage = uuidv4()
234306
const result = await createOrUpdateBranch(
@@ -585,6 +657,76 @@ describe('create-or-update-branch tests', () => {
585657
).toBeTruthy()
586658
})
587659

660+
it('tests create, commit with partial changes on the base, and update', async () => {
661+
// This is an edge case where the changes for a single commit are partially merged to the base
662+
663+
// Create tracked and untracked file changes
664+
const changes = await createChanges()
665+
const commitMessage = uuidv4()
666+
const result = await createOrUpdateBranch(
667+
git,
668+
commitMessage,
669+
'',
670+
BRANCH,
671+
REMOTE_NAME,
672+
false,
673+
ADD_PATHS_DEFAULT
674+
)
675+
await git.checkout(BRANCH)
676+
expect(result.action).toEqual('created')
677+
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
678+
expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked)
679+
expect(
680+
await gitLogMatches([commitMessage, INIT_COMMIT_MESSAGE])
681+
).toBeTruthy()
682+
683+
// Push pull request branch to remote
684+
await git.push([
685+
'--force-with-lease',
686+
REMOTE_NAME,
687+
`HEAD:refs/heads/${BRANCH}`
688+
])
689+
690+
await afterTest(false)
691+
await beforeTest()
692+
693+
// Create a commit on the base with a partial merge of the changes
694+
await createFile(TRACKED_FILE, changes.tracked)
695+
const baseCommitMessage = uuidv4()
696+
await git.exec(['add', '-A'])
697+
await git.commit(['-m', baseCommitMessage])
698+
await git.push([
699+
'--force',
700+
REMOTE_NAME,
701+
`HEAD:refs/heads/${DEFAULT_BRANCH}`
702+
])
703+
704+
// Create the same tracked and untracked file changes
705+
const _changes = await createChanges(changes.tracked, changes.untracked)
706+
const _commitMessage = uuidv4()
707+
const _result = await createOrUpdateBranch(
708+
git,
709+
_commitMessage,
710+
'',
711+
BRANCH,
712+
REMOTE_NAME,
713+
false,
714+
ADD_PATHS_DEFAULT
715+
)
716+
await git.checkout(BRANCH)
717+
expect(_result.action).toEqual('updated')
718+
expect(_result.hasDiffWithBase).toBeTruthy()
719+
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
720+
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
721+
expect(
722+
await gitLogMatches([
723+
_commitMessage,
724+
baseCommitMessage,
725+
INIT_COMMIT_MESSAGE
726+
])
727+
).toBeTruthy()
728+
})
729+
588730
it('tests create, squash merge, and update with identical changes', async () => {
589731
// Branches that have been squash merged appear to have a diff with the base due to
590732
// different commits for the same changes. To prevent creating pull requests
@@ -1607,6 +1749,81 @@ describe('create-or-update-branch tests', () => {
16071749
).toBeTruthy()
16081750
})
16091751

1752+
it('tests create, commit with partial changes on the base, and update (WBNB)', async () => {
1753+
// This is an edge case where the changes for a single commit are partially merged to the base
1754+
1755+
// Set the working base to a branch that is not the pull request base
1756+
await git.checkout(NOT_BASE_BRANCH)
1757+
1758+
// Create tracked and untracked file changes
1759+
const changes = await createChanges()
1760+
const commitMessage = uuidv4()
1761+
const result = await createOrUpdateBranch(
1762+
git,
1763+
commitMessage,
1764+
BASE,
1765+
BRANCH,
1766+
REMOTE_NAME,
1767+
false,
1768+
ADD_PATHS_DEFAULT
1769+
)
1770+
await git.checkout(BRANCH)
1771+
expect(result.action).toEqual('created')
1772+
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
1773+
expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked)
1774+
expect(
1775+
await gitLogMatches([commitMessage, INIT_COMMIT_MESSAGE])
1776+
).toBeTruthy()
1777+
1778+
// Push pull request branch to remote
1779+
await git.push([
1780+
'--force-with-lease',
1781+
REMOTE_NAME,
1782+
`HEAD:refs/heads/${BRANCH}`
1783+
])
1784+
1785+
await afterTest(false)
1786+
await beforeTest()
1787+
1788+
// Create a commit on the base with a partial merge of the changes
1789+
await createFile(TRACKED_FILE, changes.tracked)
1790+
const baseCommitMessage = uuidv4()
1791+
await git.exec(['add', '-A'])
1792+
await git.commit(['-m', baseCommitMessage])
1793+
await git.push([
1794+
'--force',
1795+
REMOTE_NAME,
1796+
`HEAD:refs/heads/${DEFAULT_BRANCH}`
1797+
])
1798+
1799+
// Set the working base to a branch that is not the pull request base
1800+
await git.checkout(NOT_BASE_BRANCH)
1801+
1802+
// Create the same tracked and untracked file changes
1803+
const _changes = await createChanges(changes.tracked, changes.untracked)
1804+
const _commitMessage = uuidv4()
1805+
const _result = await createOrUpdateBranch(
1806+
git,
1807+
_commitMessage,
1808+
BASE,
1809+
BRANCH,
1810+
REMOTE_NAME,
1811+
false,
1812+
ADD_PATHS_DEFAULT
1813+
)
1814+
await git.checkout(BRANCH)
1815+
expect(_result.action).toEqual('updated')
1816+
expect(_result.hasDiffWithBase).toBeTruthy()
1817+
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
1818+
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
1819+
expect(
1820+
await gitLogMatches([
1821+
_commitMessage,
1822+
baseCommitMessage // fetch depth of base is 1
1823+
])
1824+
).toBeTruthy()
1825+
})
1826+
16101827
it('tests create, squash merge, and update with identical changes (WBNB)', async () => {
16111828
// Branches that have been squash merged appear to have a diff with the base due to
16121829
// different commits for the same changes. To prevent creating pull requests

0 commit comments

Comments
 (0)