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>
This commit is contained in:
parent
0c2a66fe4a
commit
4320041ed3
20 changed files with 32759 additions and 8594 deletions
56
README.md
56
README.md
|
@ -32,10 +32,10 @@ Create Pull Request action will:
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v6.x.x`
|
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v7.x.x`
|
||||||
|
|
||||||
### Workflow permissions
|
### Workflow permissions
|
||||||
|
|
||||||
|
@ -48,12 +48,10 @@ For repositories belonging to an organization, this setting can be managed by ad
|
||||||
|
|
||||||
All inputs are **optional**. If not set, sensible defaults will be used.
|
All inputs are **optional**. If not set, sensible defaults will be used.
|
||||||
|
|
||||||
**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.
|
|
||||||
|
|
||||||
| Name | Description | Default |
|
| Name | Description | Default |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `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` |
|
| `token` | The token that the action will use to create and update the pull request. See [token](#token). | `GITHUB_TOKEN` |
|
||||||
| `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` |
|
| `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` |
|
||||||
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
|
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
|
||||||
| `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). | |
|
| `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). | |
|
||||||
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[create-pull-request] automated change` |
|
| `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.
|
||||||
| `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. | |
|
| `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. | |
|
||||||
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
|
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
|
||||||
| `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. | |
|
| `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. | |
|
||||||
|
| `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` |
|
||||||
| `title` | The title of the pull request. | `Changes by create-pull-request action` |
|
| `title` | The title of the pull request. | `Changes by create-pull-request action` |
|
||||||
| `body` | The body of the pull request. | `Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action` |
|
| `body` | The body of the pull request. | `Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action` |
|
||||||
| `body-path` | The path to a file containing the pull request body. Takes precedence over `body`. | |
|
| `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.
|
||||||
| `reviewers` | A comma or newline-separated list of reviewers (GitHub usernames) to request a review from. | |
|
| `reviewers` | A comma or newline-separated list of reviewers (GitHub usernames) to request a review from. | |
|
||||||
| `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. | |
|
| `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. | |
|
||||||
| `milestone` | The number of the milestone to associate this pull request with. | |
|
| `milestone` | The number of the milestone to associate this pull request with. | |
|
||||||
| `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` |
|
| `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` |
|
||||||
|
| `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` |
|
||||||
|
|
||||||
|
#### token
|
||||||
|
|
||||||
|
The token input defaults to the repository's `GITHUB_TOKEN`.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> - 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.
|
||||||
|
> - 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.
|
||||||
|
> ```yml
|
||||||
|
> permissions:
|
||||||
|
> contents: write
|
||||||
|
> pull-requests: write
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Other token options:
|
||||||
|
- Classic [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with `repo` scope.
|
||||||
|
- 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.
|
||||||
|
- [GitHub App tokens](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens) with `contents: write` and `pull-requests: write` scopes.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If pull requests could contain changes to Actions workflows you may also need the `workflows` scope.
|
||||||
|
|
||||||
|
#### branch-token
|
||||||
|
|
||||||
|
The action first creates a branch, and then creates a pull request for the branch.
|
||||||
|
For some rare use cases it can be useful, or even neccessary, to use different tokens for these operations.
|
||||||
|
It is not advisable to use this input unless you know you need to.
|
||||||
|
|
||||||
#### commit-message
|
#### commit-message
|
||||||
|
|
||||||
|
@ -104,7 +131,7 @@ If you want branches to be deleted immediately on merge then you should use GitH
|
||||||
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
env:
|
env:
|
||||||
https_proxy: http://<proxy_address>:<port>
|
https_proxy: http://<proxy_address>:<port>
|
||||||
```
|
```
|
||||||
|
@ -115,9 +142,10 @@ The following outputs can be used by subsequent workflow steps.
|
||||||
|
|
||||||
- `pull-request-number` - The pull request number.
|
- `pull-request-number` - The pull request number.
|
||||||
- `pull-request-url` - The URL of the pull request.
|
- `pull-request-url` - The URL of the pull request.
|
||||||
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated` or `closed`.
|
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated`, `closed` or `none`.
|
||||||
- `pull-request-head-sha` - The commit SHA of the pull request branch.
|
- `pull-request-head-sha` - The commit SHA of the pull request branch.
|
||||||
- `pull-request-branch` - The branch name of the pull request.
|
- `pull-request-branch` - The branch name of the pull request.
|
||||||
|
- `pull-request-commits-verified` - Whether GitHub considers the signature of the branch's commits to be verified; `true` or `false`.
|
||||||
|
|
||||||
Step outputs can be accessed as in the following example.
|
Step outputs can be accessed as in the following example.
|
||||||
Note that in order to read the step outputs the action step must have an id.
|
Note that in order to read the step outputs the action step must have an id.
|
||||||
|
@ -125,7 +153,7 @@ Note that in order to read the step outputs the action step must have an id.
|
||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
- name: Check outputs
|
- name: Check outputs
|
||||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||||
run: |
|
run: |
|
||||||
|
@ -188,7 +216,7 @@ File changes that do not match one of the paths will be stashed and restored aft
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
add-paths: |
|
add-paths: |
|
||||||
*.java
|
*.java
|
||||||
|
@ -215,7 +243,7 @@ Note that the repository must be checked out on a branch with a remote, it won't
|
||||||
- name: Uncommitted change
|
- name: Uncommitted change
|
||||||
run: date +%s > report.txt
|
run: date +%s > report.txt
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create a project card
|
### Create a project card
|
||||||
|
@ -225,7 +253,7 @@ To create a project card for the pull request, pass the `pull-request-number` st
|
||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
|
||||||
- name: Create or Update Project Card
|
- name: Create or Update Project Card
|
||||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||||
|
@ -260,7 +288,7 @@ jobs:
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update report
|
commit-message: Update report
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {
|
import {
|
||||||
createOrUpdateBranch,
|
createOrUpdateBranch,
|
||||||
tryFetch,
|
tryFetch,
|
||||||
getWorkingBaseAndType
|
getWorkingBaseAndType,
|
||||||
|
buildBranchCommits
|
||||||
} from '../lib/create-or-update-branch'
|
} from '../lib/create-or-update-branch'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import {GitCommandManager} from '../lib/git-command-manager'
|
import {GitCommandManager} from '../lib/git-command-manager'
|
||||||
|
@ -229,6 +230,77 @@ describe('create-or-update-branch tests', () => {
|
||||||
expect(workingBaseType).toEqual('commit')
|
expect(workingBaseType).toEqual('commit')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchCommits with no diff', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||||
|
expect(branchCommits.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchCommits with addition and modification', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
await createChanges()
|
||||||
|
const UNTRACKED_EXE_FILE = 'a/script.sh'
|
||||||
|
const filepath = path.join(REPO_PATH, UNTRACKED_EXE_FILE)
|
||||||
|
await fs.promises.writeFile(filepath, '#!/usr/bin/env bash', {mode: 0o755})
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', 'Test changes'])
|
||||||
|
|
||||||
|
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||||
|
|
||||||
|
expect(branchCommits.length).toEqual(1)
|
||||||
|
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||||
|
expect(branchCommits[0].changes.length).toEqual(3)
|
||||||
|
expect(branchCommits[0].changes).toEqual([
|
||||||
|
{mode: '100755', path: UNTRACKED_EXE_FILE, status: 'A'},
|
||||||
|
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||||
|
{mode: '100644', path: UNTRACKED_FILE, status: 'A'}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchCommits with addition and deletion', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
await createChanges()
|
||||||
|
const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt'
|
||||||
|
const filepath = path.join(REPO_PATH, TRACKED_FILE_NEW_PATH)
|
||||||
|
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
|
||||||
|
await fs.promises.rename(path.join(REPO_PATH, TRACKED_FILE), filepath)
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', 'Test changes'])
|
||||||
|
|
||||||
|
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||||
|
|
||||||
|
expect(branchCommits.length).toEqual(1)
|
||||||
|
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||||
|
expect(branchCommits[0].changes.length).toEqual(3)
|
||||||
|
expect(branchCommits[0].changes).toEqual([
|
||||||
|
{mode: '100644', path: TRACKED_FILE, status: 'D'},
|
||||||
|
{mode: '100644', path: UNTRACKED_FILE, status: 'A'},
|
||||||
|
{mode: '100644', path: TRACKED_FILE_NEW_PATH, status: 'A'}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchCommits with multiple commits', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
await createChanges()
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', `Test changes ${i}`])
|
||||||
|
}
|
||||||
|
|
||||||
|
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||||
|
|
||||||
|
expect(branchCommits.length).toEqual(3)
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
expect(branchCommits[i].subject).toEqual(`Test changes ${i}`)
|
||||||
|
expect(branchCommits[i].changes.length).toEqual(2)
|
||||||
|
const untrackedFileStatus = i == 0 ? 'A' : 'M'
|
||||||
|
expect(branchCommits[i].changes).toEqual([
|
||||||
|
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||||
|
{mode: '100644', path: UNTRACKED_FILE, status: untrackedFileStatus}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('tests no changes resulting in no new branch being created', async () => {
|
it('tests no changes resulting in no new branch being created', async () => {
|
||||||
const commitMessage = uuidv4()
|
const commitMessage = uuidv4()
|
||||||
const result = await createOrUpdateBranch(
|
const result = await createOrUpdateBranch(
|
||||||
|
@ -585,6 +657,76 @@ describe('create-or-update-branch tests', () => {
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('tests create, commit with partial changes on the base, and update', async () => {
|
||||||
|
// This is an edge case where the changes for a single commit are partially merged to the base
|
||||||
|
|
||||||
|
// Create tracked and untracked file changes
|
||||||
|
const changes = await createChanges()
|
||||||
|
const commitMessage = uuidv4()
|
||||||
|
const result = await createOrUpdateBranch(
|
||||||
|
git,
|
||||||
|
commitMessage,
|
||||||
|
'',
|
||||||
|
BRANCH,
|
||||||
|
REMOTE_NAME,
|
||||||
|
false,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
await git.checkout(BRANCH)
|
||||||
|
expect(result.action).toEqual('created')
|
||||||
|
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||||
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked)
|
||||||
|
expect(
|
||||||
|
await gitLogMatches([commitMessage, INIT_COMMIT_MESSAGE])
|
||||||
|
).toBeTruthy()
|
||||||
|
|
||||||
|
// Push pull request branch to remote
|
||||||
|
await git.push([
|
||||||
|
'--force-with-lease',
|
||||||
|
REMOTE_NAME,
|
||||||
|
`HEAD:refs/heads/${BRANCH}`
|
||||||
|
])
|
||||||
|
|
||||||
|
await afterTest(false)
|
||||||
|
await beforeTest()
|
||||||
|
|
||||||
|
// Create a commit on the base with a partial merge of the changes
|
||||||
|
await createFile(TRACKED_FILE, changes.tracked)
|
||||||
|
const baseCommitMessage = uuidv4()
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', baseCommitMessage])
|
||||||
|
await git.push([
|
||||||
|
'--force',
|
||||||
|
REMOTE_NAME,
|
||||||
|
`HEAD:refs/heads/${DEFAULT_BRANCH}`
|
||||||
|
])
|
||||||
|
|
||||||
|
// Create the same tracked and untracked file changes
|
||||||
|
const _changes = await createChanges(changes.tracked, changes.untracked)
|
||||||
|
const _commitMessage = uuidv4()
|
||||||
|
const _result = await createOrUpdateBranch(
|
||||||
|
git,
|
||||||
|
_commitMessage,
|
||||||
|
'',
|
||||||
|
BRANCH,
|
||||||
|
REMOTE_NAME,
|
||||||
|
false,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
await git.checkout(BRANCH)
|
||||||
|
expect(_result.action).toEqual('updated')
|
||||||
|
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||||
|
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||||
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||||
|
expect(
|
||||||
|
await gitLogMatches([
|
||||||
|
_commitMessage,
|
||||||
|
baseCommitMessage,
|
||||||
|
INIT_COMMIT_MESSAGE
|
||||||
|
])
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('tests create, squash merge, and update with identical changes', async () => {
|
it('tests create, squash merge, and update with identical changes', async () => {
|
||||||
// Branches that have been squash merged appear to have a diff with the base due to
|
// Branches that have been squash merged appear to have a diff with the base due to
|
||||||
// different commits for the same changes. To prevent creating pull requests
|
// different commits for the same changes. To prevent creating pull requests
|
||||||
|
@ -1607,6 +1749,81 @@ describe('create-or-update-branch tests', () => {
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('tests create, commit with partial changes on the base, and update (WBNB)', async () => {
|
||||||
|
// This is an edge case where the changes for a single commit are partially merged to the base
|
||||||
|
|
||||||
|
// Set the working base to a branch that is not the pull request base
|
||||||
|
await git.checkout(NOT_BASE_BRANCH)
|
||||||
|
|
||||||
|
// Create tracked and untracked file changes
|
||||||
|
const changes = await createChanges()
|
||||||
|
const commitMessage = uuidv4()
|
||||||
|
const result = await createOrUpdateBranch(
|
||||||
|
git,
|
||||||
|
commitMessage,
|
||||||
|
BASE,
|
||||||
|
BRANCH,
|
||||||
|
REMOTE_NAME,
|
||||||
|
false,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
await git.checkout(BRANCH)
|
||||||
|
expect(result.action).toEqual('created')
|
||||||
|
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||||
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked)
|
||||||
|
expect(
|
||||||
|
await gitLogMatches([commitMessage, INIT_COMMIT_MESSAGE])
|
||||||
|
).toBeTruthy()
|
||||||
|
|
||||||
|
// Push pull request branch to remote
|
||||||
|
await git.push([
|
||||||
|
'--force-with-lease',
|
||||||
|
REMOTE_NAME,
|
||||||
|
`HEAD:refs/heads/${BRANCH}`
|
||||||
|
])
|
||||||
|
|
||||||
|
await afterTest(false)
|
||||||
|
await beforeTest()
|
||||||
|
|
||||||
|
// Create a commit on the base with a partial merge of the changes
|
||||||
|
await createFile(TRACKED_FILE, changes.tracked)
|
||||||
|
const baseCommitMessage = uuidv4()
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', baseCommitMessage])
|
||||||
|
await git.push([
|
||||||
|
'--force',
|
||||||
|
REMOTE_NAME,
|
||||||
|
`HEAD:refs/heads/${DEFAULT_BRANCH}`
|
||||||
|
])
|
||||||
|
|
||||||
|
// Set the working base to a branch that is not the pull request base
|
||||||
|
await git.checkout(NOT_BASE_BRANCH)
|
||||||
|
|
||||||
|
// Create the same tracked and untracked file changes
|
||||||
|
const _changes = await createChanges(changes.tracked, changes.untracked)
|
||||||
|
const _commitMessage = uuidv4()
|
||||||
|
const _result = await createOrUpdateBranch(
|
||||||
|
git,
|
||||||
|
_commitMessage,
|
||||||
|
BASE,
|
||||||
|
BRANCH,
|
||||||
|
REMOTE_NAME,
|
||||||
|
false,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
await git.checkout(BRANCH)
|
||||||
|
expect(_result.action).toEqual('updated')
|
||||||
|
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||||
|
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||||
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||||
|
expect(
|
||||||
|
await gitLogMatches([
|
||||||
|
_commitMessage,
|
||||||
|
baseCommitMessage // fetch depth of base is 1
|
||||||
|
])
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('tests create, squash merge, and update with identical changes (WBNB)', async () => {
|
it('tests create, squash merge, and update with identical changes (WBNB)', async () => {
|
||||||
// Branches that have been squash merged appear to have a diff with the base due to
|
// Branches that have been squash merged appear to have a diff with the base due to
|
||||||
// different commits for the same changes. To prevent creating pull requests
|
// different commits for the same changes. To prevent creating pull requests
|
||||||
|
|
|
@ -13,7 +13,7 @@ git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all
|
||||||
# Give the daemon time to start
|
# Give the daemon time to start
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# Create a local clone and make an initial commit
|
# Create a local clone and make initial commits
|
||||||
mkdir -p /git/local/repos
|
mkdir -p /git/local/repos
|
||||||
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
||||||
cd /git/local/repos/test-base
|
cd /git/local/repos/test-base
|
||||||
|
@ -22,6 +22,11 @@ git config --global user.name "Your Name"
|
||||||
echo "#test-base" > README.md
|
echo "#test-base" > README.md
|
||||||
git add .
|
git add .
|
||||||
git commit -m "initial commit"
|
git commit -m "initial commit"
|
||||||
|
git commit --allow-empty -m "empty commit for tests"
|
||||||
|
echo "#test-base :sparkles:" > README.md
|
||||||
|
git add .
|
||||||
|
git commit -m "add sparkles" -m "Change description:
|
||||||
|
- updates README.md to add sparkles to the title"
|
||||||
git push -u
|
git push -u
|
||||||
git log -1 --pretty=oneline
|
git log -1 --pretty=oneline
|
||||||
git config --global --unset user.email
|
git config --global --unset user.email
|
||||||
|
|
37
__test__/git-command-manager.int.test.ts
Normal file
37
__test__/git-command-manager.int.test.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import {GitCommandManager} from '../lib/git-command-manager'
|
||||||
|
|
||||||
|
const REPO_PATH = '/git/local/repos/test-base'
|
||||||
|
|
||||||
|
describe('git-command-manager integration tests', () => {
|
||||||
|
let git: GitCommandManager
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
git = await GitCommandManager.create(REPO_PATH)
|
||||||
|
await git.checkout('main')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests getCommit', async () => {
|
||||||
|
const initialCommit = await git.getCommit('HEAD^^')
|
||||||
|
const emptyCommit = await git.getCommit('HEAD^')
|
||||||
|
const headCommit = await git.getCommit('HEAD')
|
||||||
|
|
||||||
|
expect(initialCommit.subject).toEqual('initial commit')
|
||||||
|
expect(initialCommit.signed).toBeFalsy()
|
||||||
|
expect(initialCommit.changes).toEqual([
|
||||||
|
{mode: '100644', status: 'A', path: 'README.md'}
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(emptyCommit.subject).toEqual('empty commit for tests')
|
||||||
|
expect(emptyCommit.tree).toEqual(initialCommit.tree) // empty commits have no tree and reference the parent's
|
||||||
|
expect(emptyCommit.parents[0]).toEqual(initialCommit.sha)
|
||||||
|
expect(emptyCommit.signed).toBeFalsy()
|
||||||
|
expect(emptyCommit.changes).toEqual([])
|
||||||
|
|
||||||
|
expect(headCommit.subject).toEqual('add sparkles')
|
||||||
|
expect(headCommit.parents[0]).toEqual(emptyCommit.sha)
|
||||||
|
expect(headCommit.signed).toBeFalsy()
|
||||||
|
expect(headCommit.changes).toEqual([
|
||||||
|
{mode: '100644', status: 'M', path: 'README.md'}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
|
@ -7,7 +7,6 @@ const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||||
|
|
||||||
describe('git-config-helper integration tests', () => {
|
describe('git-config-helper integration tests', () => {
|
||||||
let git: GitCommandManager
|
let git: GitCommandManager
|
||||||
let gitConfigHelper: GitConfigHelper
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
git = await GitCommandManager.create(REPO_PATH)
|
git = await GitCommandManager.create(REPO_PATH)
|
||||||
|
|
16
action.yml
16
action.yml
|
@ -2,11 +2,11 @@ name: 'Create Pull Request'
|
||||||
description: 'Creates a pull request for changes to your repository in the actions workspace'
|
description: 'Creates a pull request for changes to your repository in the actions workspace'
|
||||||
inputs:
|
inputs:
|
||||||
token:
|
token:
|
||||||
description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
|
description: 'The token that the action will use to create and update the pull request.'
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
git-token:
|
branch-token:
|
||||||
description: >
|
description: >
|
||||||
The Personal Access Token (PAT) that the action will use for git operations.
|
The token that the action will use to create and update the branch.
|
||||||
Defaults to the value of `token`.
|
Defaults to the value of `token`.
|
||||||
path:
|
path:
|
||||||
description: >
|
description: >
|
||||||
|
@ -51,6 +51,9 @@ inputs:
|
||||||
A fork of the checked out parent repository to which the pull request branch will be pushed.
|
A fork of the checked out parent repository to which the pull request branch will be pushed.
|
||||||
e.g. `owner/repo-fork`.
|
e.g. `owner/repo-fork`.
|
||||||
The pull request will be created to merge the fork's branch into the parent's base.
|
The pull request will be created to merge the fork's branch into the parent's base.
|
||||||
|
sign-commits:
|
||||||
|
description: 'Sign commits as `github-actions[bot]` when using `GITHUB_TOKEN`, or your own bot when using GitHub App tokens.'
|
||||||
|
default: false
|
||||||
title:
|
title:
|
||||||
description: 'The title of the pull request.'
|
description: 'The title of the pull request.'
|
||||||
default: 'Changes by create-pull-request action'
|
default: 'Changes by create-pull-request action'
|
||||||
|
@ -72,8 +75,13 @@ inputs:
|
||||||
milestone:
|
milestone:
|
||||||
description: 'The number of the milestone to associate the pull request with.'
|
description: 'The number of the milestone to associate the pull request with.'
|
||||||
draft:
|
draft:
|
||||||
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
|
description: >
|
||||||
|
Create a draft pull request.
|
||||||
|
Valid values are `true` (only on create), `always-true` (on create and update), and `false`.
|
||||||
default: false
|
default: false
|
||||||
|
maintainer-can-modify:
|
||||||
|
description: 'Indicates whether maintainers can modify the pull request.'
|
||||||
|
default: true
|
||||||
outputs:
|
outputs:
|
||||||
pull-request-number:
|
pull-request-number:
|
||||||
description: 'The pull request number'
|
description: 'The pull request number'
|
||||||
|
|
36513
dist/index.js
vendored
36513
dist/index.js
vendored
File diff suppressed because one or more lines are too long
|
@ -37,7 +37,7 @@ So the straightforward solution is to just not install them during the workflow
|
||||||
|
|
||||||
- If hooks are automatically enabled by a framework, use an option provided by the framework to disable them. For example, for Husky users, they can be disabled with the `--ignore-scripts` flag, or by setting the `HUSKY` environment variable when the action runs.
|
- If hooks are automatically enabled by a framework, use an option provided by the framework to disable them. For example, for Husky users, they can be disabled with the `--ignore-scripts` flag, or by setting the `HUSKY` environment variable when the action runs.
|
||||||
```yml
|
```yml
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
env:
|
env:
|
||||||
HUSKY: '0'
|
HUSKY: '0'
|
||||||
```
|
```
|
||||||
|
|
|
@ -15,8 +15,12 @@ This document covers terminology, how the action works, general usage guidelines
|
||||||
- [Creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository)
|
- [Creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository)
|
||||||
- [Push using SSH (deploy keys)](#push-using-ssh-deploy-keys)
|
- [Push using SSH (deploy keys)](#push-using-ssh-deploy-keys)
|
||||||
- [Push pull request branches to a fork](#push-pull-request-branches-to-a-fork)
|
- [Push pull request branches to a fork](#push-pull-request-branches-to-a-fork)
|
||||||
|
- [Pushing to a fork with fine-grained permissions](#pushing-to-a-fork-with-fine-grained-permissions)
|
||||||
- [Authenticating with GitHub App generated tokens](#authenticating-with-github-app-generated-tokens)
|
- [Authenticating with GitHub App generated tokens](#authenticating-with-github-app-generated-tokens)
|
||||||
- [GPG commit signature verification](#gpg-commit-signature-verification)
|
- [Creating pull requests in a remote repository using GitHub App generated tokens](#creating-pull-requests-in-a-remote-repository-using-github-app-generated-tokens)
|
||||||
|
- [Commit signing](#commit-signing)
|
||||||
|
- [Commit signature verification for bots](#commit-signature-verification-for-bots)
|
||||||
|
- [GPG commit signature verification](#gpg-commit-signature-verification)
|
||||||
- [Running in a container or on self-hosted runners](#running-in-a-container-or-on-self-hosted-runners)
|
- [Running in a container or on self-hosted runners](#running-in-a-container-or-on-self-hosted-runners)
|
||||||
|
|
||||||
## Terminology
|
## Terminology
|
||||||
|
@ -88,7 +92,7 @@ In these cases, you *must supply* the `base` input so the action can rebase chan
|
||||||
Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request) events will by default check out a merge commit. Set the `base` input as follows to base the new pull request on the current pull request's branch.
|
Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request) events will by default check out a merge commit. Set the `base` input as follows to base the new pull request on the current pull request's branch.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
- uses: peter-evans/create-pull-request@v6
|
- uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
base: ${{ github.head_ref }}
|
base: ${{ github.head_ref }}
|
||||||
```
|
```
|
||||||
|
@ -96,7 +100,7 @@ Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/refer
|
||||||
Workflows triggered by [`release`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#release) events will by default check out a tag. For most use cases, you will need to set the `base` input to the branch name of the tagged commit.
|
Workflows triggered by [`release`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#release) events will by default check out a tag. For most use cases, you will need to set the `base` input to the branch name of the tagged commit.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
- uses: peter-evans/create-pull-request@v6
|
- uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
base: main
|
base: main
|
||||||
```
|
```
|
||||||
|
@ -146,13 +150,15 @@ There are a number of workarounds with different pros and cons.
|
||||||
|
|
||||||
- Use the default `GITHUB_TOKEN` and allow the action to create pull requests that have no checks enabled. Manually close pull requests and immediately reopen them. This will enable `on: pull_request` workflows to run and be added as checks. To prevent merging of pull requests without checks erroneously, use [branch protection rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests).
|
- Use the default `GITHUB_TOKEN` and allow the action to create pull requests that have no checks enabled. Manually close pull requests and immediately reopen them. This will enable `on: pull_request` workflows to run and be added as checks. To prevent merging of pull requests without checks erroneously, use [branch protection rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests).
|
||||||
|
|
||||||
- Use a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) created on an account that has write access to the repository that pull requests are being created in. This is the standard workaround and [recommended by GitHub](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow). However, the PAT cannot be scoped to a specific repository so the token becomes a very sensitive secret. If this is a concern, the PAT can instead be created for a dedicated [machine account](https://docs.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements) that has collaborator access to the repository. Also note that because the account that owns the PAT will be the creator of pull requests, that user account will be unable to perform actions such as request changes or approve the pull request.
|
- Create draft pull requests by setting the `draft: always-true` input, and configure your workflow to trigger `on: ready_for_review`. The workflow will run when users manually click the "Ready for review" button on the draft pull requests. If the pull request is updated by the action, the `always-true` mode ensures that the pull request will be converted back to a draft.
|
||||||
|
|
||||||
|
- Use a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) created on an account that has write access to the repository that pull requests are being created in. This is the standard workaround and [recommended by GitHub](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow). It's advisable to use a dedicated [machine account](https://docs.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements) that has collaborator access to the repository, rather than creating a PAT on a personal user account. Also note that because the account that owns the PAT will be the creator of pull requests, that user account will be unable to perform actions such as request changes or approve the pull request.
|
||||||
|
|
||||||
- Use [SSH (deploy keys)](#push-using-ssh-deploy-keys) to push the pull request branch. This is arguably more secure than using a PAT because deploy keys can be set per repository. However, this method will only trigger `on: push` workflows.
|
- Use [SSH (deploy keys)](#push-using-ssh-deploy-keys) to push the pull request branch. This is arguably more secure than using a PAT because deploy keys can be set per repository. However, this method will only trigger `on: push` workflows.
|
||||||
|
|
||||||
- Use a [machine account that creates pull requests from its own fork](#push-pull-request-branches-to-a-fork). This is the most secure because the PAT created only grants access to the machine account's fork, not the main repository. This method will trigger `on: pull_request` workflows to run. Workflows triggered `on: push` will not run because the push event is in the fork.
|
- Use a [machine account that creates pull requests from its own fork](#push-pull-request-branches-to-a-fork). This is the most secure because the PAT created only grants access to the machine account's fork, not the main repository. This method will trigger `on: pull_request` workflows to run. Workflows triggered `on: push` will not run because the push event is in the fork.
|
||||||
|
|
||||||
- Use a [GitHub App to generate a token](#authenticating-with-github-app-generated-tokens) that can be used with this action. GitHub App generated tokens are more secure than using a PAT because GitHub App access permissions can be set with finer granularity and are scoped to only repositories where the App is installed. This method will trigger both `on: push` and `on: pull_request` workflows.
|
- Use a [GitHub App to generate a token](#authenticating-with-github-app-generated-tokens) that can be used with this action. GitHub App generated tokens are more secure than using a Classic PAT because access permissions can be set with finer granularity and are scoped to only repositories where the App is installed. This method will trigger both `on: push` and `on: pull_request` workflows.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
@ -170,7 +176,7 @@ This action uses [ncc](https://github.com/vercel/ncc) to compile the Node.js cod
|
||||||
|
|
||||||
### Creating pull requests in a remote repository
|
### Creating pull requests in a remote repository
|
||||||
|
|
||||||
Checking out a branch from a different repository from where the workflow is executing will make *that repository* the target for the created pull request. In this case, a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) is required.
|
Checking out a branch from a different repository from where the workflow is executing will make *that repository* the target for the created pull request. In this case, the `GITHUB_TOKEN` will not work and one of the other [token options](../README.md#token) must be used.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -180,16 +186,19 @@ Checking out a branch from a different repository from where the workflow is exe
|
||||||
|
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- uses: peter-evans/create-pull-request@v6
|
- uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Push using SSH (deploy keys)
|
### Push using SSH (deploy keys)
|
||||||
|
|
||||||
[Deploy keys](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) can be set per repository and so are arguably more secure than using a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
[Deploy keys](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) can be set per repository and so are arguably more secure than using a Classic [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
||||||
Allowing the action to push with a configured deploy key will trigger `on: push` workflows. This makes it an alternative to using a PAT to trigger checks for pull requests.
|
Allowing the action to push with a configured deploy key will trigger `on: push` workflows. This makes it an alternative to using a PAT to trigger checks for pull requests.
|
||||||
Note that you cannot use deploy keys alone to [create a pull request in a remote repository](#creating-pull-requests-in-a-remote-repository) because then using a PAT would become a requirement. This method only makes sense if creating a pull request in the repository where the workflow is running.
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You cannot use deploy keys alone to [create a pull request in a remote repository](#creating-pull-requests-in-a-remote-repository) because then using a PAT would become a requirement.
|
||||||
|
> This method only makes sense if creating a pull request in the repository where the workflow is running.
|
||||||
|
|
||||||
How to use SSH (deploy keys) with create-pull-request action:
|
How to use SSH (deploy keys) with create-pull-request action:
|
||||||
|
|
||||||
|
@ -207,7 +216,7 @@ How to use SSH (deploy keys) with create-pull-request action:
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
```
|
```
|
||||||
|
|
||||||
### Push pull request branches to a fork
|
### Push pull request branches to a fork
|
||||||
|
@ -216,11 +225,13 @@ Instead of pushing pull request branches to the repository you want to update, y
|
||||||
This allows you to employ the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) by using a dedicated user acting as a [machine account](https://docs.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements).
|
This allows you to employ the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) by using a dedicated user acting as a [machine account](https://docs.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements).
|
||||||
This user only has `read` access to the main repository.
|
This user only has `read` access to the main repository.
|
||||||
It will use their own fork to push code and create the pull request.
|
It will use their own fork to push code and create the pull request.
|
||||||
Note that if you choose to use this method (not give the machine account `write` access to the repository) the following inputs cannot be used: `labels`, `assignees`, `reviewers`, `team-reviewers` and `milestone`.
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If you choose to not give the machine account `write` access to the parent repository, the following inputs cannot be used: `labels`, `assignees`, `reviewers`, `team-reviewers` and `milestone`.
|
||||||
|
|
||||||
1. Create a new GitHub user and login.
|
1. Create a new GitHub user and login.
|
||||||
2. Fork the repository that you will be creating pull requests in.
|
2. Fork the repository that you will be creating pull requests in.
|
||||||
3. Create a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
3. Create a Classic [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with `repo` scope.
|
||||||
4. Logout and log back into your main user account.
|
4. Logout and log back into your main user account.
|
||||||
5. Add a secret to your repository containing the above PAT.
|
5. Add a secret to your repository containing the above PAT.
|
||||||
6. As shown in the following example workflow, set the `push-to-fork` input to the full repository name of the fork.
|
6. As shown in the following example workflow, set the `push-to-fork` input to the full repository name of the fork.
|
||||||
|
@ -230,19 +241,60 @@ Note that if you choose to use this method (not give the machine account `write`
|
||||||
|
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- uses: peter-evans/create-pull-request@v6
|
- uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MACHINE_USER_PAT }}
|
token: ${{ secrets.MACHINE_USER_PAT }}
|
||||||
push-to-fork: machine-user/fork-of-repository
|
push-to-fork: machine-user/fork-of-repository
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: You can also combine `push-to-fork` with [creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository).
|
> [!TIP]
|
||||||
|
> You can also combine `push-to-fork` with [creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository).
|
||||||
|
|
||||||
|
#### Pushing to a fork with fine-grained permissions
|
||||||
|
|
||||||
|
Using a fine-grained [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) or [GitHub App](#authenticating-with-github-app-generated-tokens) with `push-to-fork` can be achieved, but comes with some caveats.
|
||||||
|
|
||||||
|
When using `push-to-fork`, the action needs permissions for two different repositories.
|
||||||
|
It needs `contents: write` for the fork to push the branch, and `pull-requests: write` for the parent repository to create the pull request.
|
||||||
|
|
||||||
|
There are two main scenarios:
|
||||||
|
1. The parent and fork have different owners. In this case, it's not possible to create a token that is scoped to both repositories so different tokens must be used for each.
|
||||||
|
2. The parent and fork both have the same owner (i.e. they exist in the same org). In this case, a single token can be scoped to both repositories, but the permissions granted cannot be different. So it would defeat the purpose of using `push-to-fork`, and you might as well just create the pull request directly on the parent repository.
|
||||||
|
|
||||||
|
For the first scenario, the solution is to scope the token for the fork, and use the `branch-token` input to push the branch.
|
||||||
|
The `token` input will then default to the repository's `GITHUB_TOKEN`, which will be used to create the pull request.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Solution limitations:
|
||||||
|
> - Since `GITHUB_TOKEN` will be used to create the pull request, the workflow *must* be executing in the parent repository where the pull request should be created.
|
||||||
|
> - `maintainer-can-modify` *must* be set to `false`, because the `GITHUB_TOKEN` will not have `write` access to the head branch in the fork.
|
||||||
|
|
||||||
|
The following is an example of pushing to a fork using GitHub App tokens.
|
||||||
|
```yaml
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: generate-token
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.APP_ID }}
|
||||||
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
owner: owner
|
||||||
|
repositories: fork-of-repo
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Make changes to pull request here
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
with:
|
||||||
|
branch-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
push-to-fork: owner/fork-of-repo
|
||||||
|
maintainer-can-modify: false
|
||||||
|
```
|
||||||
|
|
||||||
### Authenticating with GitHub App generated tokens
|
### Authenticating with GitHub App generated tokens
|
||||||
|
|
||||||
A GitHub App can be created for the sole purpose of generating tokens for use with GitHub actions.
|
A GitHub App can be created for the sole purpose of generating tokens for use with GitHub actions.
|
||||||
These tokens can be used in place of `GITHUB_TOKEN` or a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
GitHub App generated tokens can be configured with fine-grained permissions and are scoped to only repositories where the App is installed.
|
||||||
GitHub App generated tokens are more secure than using a PAT because GitHub App access permissions can be set with finer granularity and are scoped to only repositories where the App is installed.
|
|
||||||
|
|
||||||
1. Create a minimal [GitHub App](https://docs.github.com/en/developers/apps/creating-a-github-app), setting the following fields:
|
1. Create a minimal [GitHub App](https://docs.github.com/en/developers/apps/creating-a-github-app), setting the following fields:
|
||||||
|
|
||||||
|
@ -251,36 +303,115 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
||||||
- Uncheck `Active` under `Webhook`. You do not need to enter a `Webhook URL`.
|
- Uncheck `Active` under `Webhook`. You do not need to enter a `Webhook URL`.
|
||||||
- Under `Repository permissions: Contents` select `Access: Read & write`.
|
- Under `Repository permissions: Contents` select `Access: Read & write`.
|
||||||
- Under `Repository permissions: Pull requests` select `Access: Read & write`.
|
- Under `Repository permissions: Pull requests` select `Access: Read & write`.
|
||||||
|
- Under `Repository permissions: Workflows` select `Access: Read-only`.
|
||||||
|
- **NOTE**: Only needed if pull requests could contain changes to Actions workflows.
|
||||||
- Under `Organization permissions: Members` select `Access: Read-only`.
|
- Under `Organization permissions: Members` select `Access: Read-only`.
|
||||||
- **NOTE**: Only needed if you would like add teams as reviewers to PRs.
|
- **NOTE**: Only needed if you would like add teams as reviewers to PRs.
|
||||||
|
|
||||||
2. Create a Private key from the App settings page and store it securely.
|
2. Create a Private key from the App settings page and store it securely.
|
||||||
|
|
||||||
3. Install the App on any repository where workflows will run requiring tokens.
|
3. Install the App on repositories that the action will require access to in order to create pull requests.
|
||||||
|
|
||||||
4. Set secrets on your repository containing the GitHub App ID, and the private key you created in step 2. e.g. `APP_ID`, `APP_PRIVATE_KEY`.
|
4. Set secrets on your repository containing the GitHub App ID, and the private key you created in step 2. e.g. `APP_ID`, `APP_PRIVATE_KEY`.
|
||||||
|
|
||||||
5. The following example workflow shows how to use [tibdex/github-app-token](https://github.com/tibdex/github-app-token) to generate a token for use with this action.
|
5. The following example workflow shows how to use [actions/create-github-app-token](https://github.com/actions/create-github-app-token) to generate a token for use with this action.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/create-github-app-token@v1
|
||||||
|
|
||||||
- uses: tibdex/github-app-token@v1
|
|
||||||
id: generate-token
|
id: generate-token
|
||||||
with:
|
with:
|
||||||
app_id: ${{ secrets.APP_ID }}
|
app-id: ${{ secrets.APP_ID }}
|
||||||
private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### GPG commit signature verification
|
#### Creating pull requests in a remote repository using GitHub App generated tokens
|
||||||
|
|
||||||
|
For this case a token must be generated from the GitHub App installation of the remote repository.
|
||||||
|
|
||||||
|
In the following example, a pull request is being created in remote repo `owner/repo`.
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: generate-token
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.APP_ID }}
|
||||||
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
owner: owner
|
||||||
|
repositories: repo
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }} # necessary if the repo is private
|
||||||
|
repository: owner/repo
|
||||||
|
|
||||||
|
# Make changes to pull request here
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit signing
|
||||||
|
|
||||||
|
[Commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) is a feature where GitHub will mark signed commits as "verified" to give confidence that changes are from a trusted source.
|
||||||
|
Some organizations require commit signing, and enforce it with branch protection rules.
|
||||||
|
|
||||||
|
The action supports two methods to sign commits, [commit signature verification for bots](#commit-signature-verification-for-bots), and [GPG commit signature verification](#gpg-commit-signature-verification).
|
||||||
|
|
||||||
|
#### Commit signature verification for bots
|
||||||
|
|
||||||
|
The action can sign commits as `github-actions[bot]` when using the repository's default `GITHUB_TOKEN`, or your own bot when using [GitHub App tokens](#authenticating-with-github-app-generated-tokens).
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> - When setting `sign-commits: true` the action will ignore the `committer` and `author` inputs.
|
||||||
|
> - If you attempt to use a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) the action will create the pull request, but commits will *not* be signed. Commit signing is only supported with bot generated tokens.
|
||||||
|
> - The GitHub API has a 40MiB limit when creating git blobs. An error will be raised if there are files in the pull request larger than this. If you hit this limit, use [GPG commit signature verification](#gpg-commit-signature-verification) instead.
|
||||||
|
|
||||||
|
In this example the `token` input is not supplied, so the action will use the repository's default `GITHUB_TOKEN`. This will sign commits as `github-actions[bot]`.
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Make changes to pull request here
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
with:
|
||||||
|
sign-commits: true
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, the `token` input is generated using a GitHub App. This will sign commits as `<application-name>[bot]`.
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: generate-token
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.APP_ID }}
|
||||||
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
# Make changes to pull request here
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
sign-commits: true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GPG commit signature verification
|
||||||
|
|
||||||
The action can use GPG to sign commits with a GPG key that you generate yourself.
|
The action can use GPG to sign commits with a GPG key that you generate yourself.
|
||||||
|
|
||||||
|
@ -300,7 +431,8 @@ The action can use GPG to sign commits with a GPG key that you generate yourself
|
||||||
|
|
||||||
6. The following example workflow shows how to use [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) to import your GPG key and allow the action to sign commits.
|
6. The following example workflow shows how to use [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) to import your GPG key and allow the action to sign commits.
|
||||||
|
|
||||||
Note that the `committer` email address *MUST* match the email address used to create your GPG key.
|
> [!IMPORTANT]
|
||||||
|
> The `committer` email address *MUST* match the email address used to create your GPG key.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
steps:
|
steps:
|
||||||
|
@ -316,7 +448,7 @@ The action can use GPG to sign commits with a GPG key that you generate yourself
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
committer: example <email@example.com>
|
committer: example <email@example.com>
|
||||||
|
@ -346,7 +478,7 @@ jobs:
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
```
|
```
|
||||||
|
|
||||||
**Ubuntu container example:**
|
**Ubuntu container example:**
|
||||||
|
@ -369,5 +501,5 @@ jobs:
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
```
|
```
|
||||||
|
|
|
@ -49,7 +49,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
git log --format='%aN <%aE>%n%cN <%cE>' | sort -u > AUTHORS
|
git log --format='%aN <%aE>%n%cN <%cE>' | sort -u > AUTHORS
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
commit-message: update authors
|
commit-message: update authors
|
||||||
title: Update AUTHORS
|
title: Update AUTHORS
|
||||||
|
@ -81,7 +81,7 @@ jobs:
|
||||||
git fetch origin main:main
|
git fetch origin main:main
|
||||||
git reset --hard main
|
git reset --hard main
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
branch: production-promotion
|
branch: production-promotion
|
||||||
```
|
```
|
||||||
|
@ -116,7 +116,7 @@ jobs:
|
||||||
./git-chglog -o CHANGELOG.md
|
./git-chglog -o CHANGELOG.md
|
||||||
rm git-chglog
|
rm git-chglog
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
commit-message: update changelog
|
commit-message: update changelog
|
||||||
title: Update Changelog
|
title: Update Changelog
|
||||||
|
@ -153,7 +153,7 @@ jobs:
|
||||||
npx -p npm-check-updates ncu -u
|
npx -p npm-check-updates ncu -u
|
||||||
npm install
|
npm install
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update dependencies
|
commit-message: Update dependencies
|
||||||
|
@ -214,7 +214,7 @@ jobs:
|
||||||
- name: Perform dependency resolution and write new lockfiles
|
- name: Perform dependency resolution and write new lockfiles
|
||||||
run: ./gradlew dependencies --write-locks
|
run: ./gradlew dependencies --write-locks
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update dependencies
|
commit-message: Update dependencies
|
||||||
|
@ -249,7 +249,7 @@ jobs:
|
||||||
cargo update
|
cargo update
|
||||||
cargo upgrade --to-lockfile
|
cargo upgrade --to-lockfile
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update dependencies
|
commit-message: Update dependencies
|
||||||
|
@ -307,7 +307,7 @@ jobs:
|
||||||
# Update current release
|
# Update current release
|
||||||
echo ${{ steps.swagger-ui.outputs.release_tag }} > swagger-ui.version
|
echo ${{ steps.swagger-ui.outputs.release_tag }} > swagger-ui.version
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||||
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||||
|
@ -351,7 +351,7 @@ jobs:
|
||||||
git fetch upstream main:upstream-main
|
git fetch upstream main:upstream-main
|
||||||
git reset --hard upstream-main
|
git reset --hard upstream-main
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
branch: upstream-changes
|
branch: upstream-changes
|
||||||
|
@ -384,7 +384,7 @@ jobs:
|
||||||
--domains quotes.toscrape.com \
|
--domains quotes.toscrape.com \
|
||||||
http://quotes.toscrape.com/
|
http://quotes.toscrape.com/
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
commit-message: update local website copy
|
commit-message: update local website copy
|
||||||
title: Automated Updates to Local Website Copy
|
title: Automated Updates to Local Website Copy
|
||||||
|
@ -481,7 +481,7 @@ jobs:
|
||||||
echo "branch-name=$branch-name" >> $GITHUB_OUTPUT
|
echo "branch-name=$branch-name" >> $GITHUB_OUTPUT
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: steps.autopep8.outputs.exit-code == 2
|
if: steps.autopep8.outputs.exit-code == 2
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
commit-message: autopep8 action fixes
|
commit-message: autopep8 action fixes
|
||||||
title: Fixes by autopep8 action
|
title: Fixes by autopep8 action
|
||||||
|
@ -540,7 +540,7 @@ Note that the step where output variables are defined must have an id.
|
||||||
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT
|
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT
|
||||||
echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
|
echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
title: ${{ steps.vars.outputs.pr_title }}
|
title: ${{ steps.vars.outputs.pr_title }}
|
||||||
body: ${{ steps.vars.outputs.pr_body }}
|
body: ${{ steps.vars.outputs.pr_body }}
|
||||||
|
@ -566,7 +566,7 @@ The template is rendered using the [render-template](https://github.com/chuhlomi
|
||||||
bar: that
|
bar: that
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
body: ${{ steps.template.outputs.result }}
|
body: ${{ steps.template.outputs.result }}
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
## Updating from `v6` to `v7`
|
||||||
|
|
||||||
|
### Behaviour changes
|
||||||
|
|
||||||
|
- Action input `git-token` has been renamed `branch-token`, to be more clear about its purpose. The `branch-token` is the token that the action will use to create and update the branch.
|
||||||
|
- The action now handles requests that have been rate-limited by GitHub. Requests hitting a primary rate limit will retry twice, for a total of three attempts. Requests hitting a secondary rate limit will not be retried.
|
||||||
|
- The `pull-request-operation` output now returns `none` when no operation was executed.
|
||||||
|
- Removed deprecated output environment variable `PULL_REQUEST_NUMBER`. Please use the `pull-request-number` action output instead.
|
||||||
|
|
||||||
|
### What's new
|
||||||
|
|
||||||
|
- The action can now sign commits as `github-actions[bot]` when using `GITHUB_TOKEN`, or your own bot when using [GitHub App tokens](concepts-guidelines.md#authenticating-with-github-app-generated-tokens). See [commit signing](concepts-guidelines.md#commit-signature-verification-for-bots) for details.
|
||||||
|
- Action input `draft` now accepts a new value `always-true`. This will set the pull request to draft status when the pull request is updated, as well as on creation.
|
||||||
|
- A new action input `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. The default is `true`, which retains the existing behaviour of the action.
|
||||||
|
- A new output `pull-request-commits-verified` returns `true` or `false`, indicating whether GitHub considers the signature of the branch's commits to be verified.
|
||||||
|
|
||||||
## Updating from `v5` to `v6`
|
## Updating from `v5` to `v6`
|
||||||
|
|
||||||
### Behaviour changes
|
### Behaviour changes
|
||||||
|
|
3713
package-lock.json
generated
3713
package-lock.json
generated
File diff suppressed because it is too large
Load diff
15
package.json
15
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "create-pull-request",
|
"name": "create-pull-request",
|
||||||
"version": "6.0.0",
|
"version": "7.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
||||||
"main": "lib/main.js",
|
"main": "lib/main.js",
|
||||||
|
@ -31,9 +31,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"@octokit/core": "^4.2.4",
|
"@octokit/core": "^6.1.2",
|
||||||
"@octokit/plugin-paginate-rest": "^5.0.1",
|
"@octokit/plugin-paginate-rest": "^11.3.3",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
"@octokit/plugin-rest-endpoint-methods": "^13.2.4",
|
||||||
|
"@octokit/plugin-throttling": "^9.3.1",
|
||||||
|
"p-limit": "^6.1.0",
|
||||||
"proxy-from-env": "^1.1.0",
|
"proxy-from-env": "^1.1.0",
|
||||||
"undici": "^6.19.8",
|
"undici": "^6.19.8",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
|
@ -41,7 +43,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^18.19.46",
|
"@types/node": "^18.19.46",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||||
|
"@typescript-eslint/parser": "^7.17.0",
|
||||||
"@vercel/ncc": "^0.38.1",
|
"@vercel/ncc": "^0.38.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.3",
|
"eslint-import-resolver-typescript": "^3.6.3",
|
||||||
|
@ -55,6 +58,6 @@
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {GitCommandManager} from './git-command-manager'
|
import {GitCommandManager, Commit} from './git-command-manager'
|
||||||
import {v4 as uuidv4} from 'uuid'
|
import {v4 as uuidv4} from 'uuid'
|
||||||
|
|
||||||
const CHERRYPICK_EMPTY =
|
const CHERRYPICK_EMPTY =
|
||||||
|
@ -47,6 +47,27 @@ export async function tryFetch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function buildBranchCommits(
|
||||||
|
git: GitCommandManager,
|
||||||
|
base: string,
|
||||||
|
branch: string
|
||||||
|
): Promise<Commit[]> {
|
||||||
|
const output = await git.exec(['log', '--format=%H', `${base}..${branch}`])
|
||||||
|
const shas = output.stdout
|
||||||
|
.split('\n')
|
||||||
|
.filter(x => x !== '')
|
||||||
|
.reverse()
|
||||||
|
const commits: Commit[] = []
|
||||||
|
for (const sha of shas) {
|
||||||
|
const commit = await git.getCommit(sha)
|
||||||
|
commits.push(commit)
|
||||||
|
for (const unparsedChange of commit.unparsedChanges) {
|
||||||
|
core.warning(`Skipping unexpected diff entry: ${unparsedChange}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commits
|
||||||
|
}
|
||||||
|
|
||||||
// Return the number of commits that branch2 is ahead of branch1
|
// Return the number of commits that branch2 is ahead of branch1
|
||||||
async function commitsAhead(
|
async function commitsAhead(
|
||||||
git: GitCommandManager,
|
git: GitCommandManager,
|
||||||
|
@ -103,6 +124,22 @@ async function isEven(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return true if the specified number of commits on branch1 and branch2 have a diff
|
||||||
|
async function commitsHaveDiff(
|
||||||
|
git: GitCommandManager,
|
||||||
|
branch1: string,
|
||||||
|
branch2: string,
|
||||||
|
depth: number
|
||||||
|
): Promise<boolean> {
|
||||||
|
const diff1 = (
|
||||||
|
await git.exec(['diff', '--stat', `${branch1}..${branch1}~${depth}`])
|
||||||
|
).stdout.trim()
|
||||||
|
const diff2 = (
|
||||||
|
await git.exec(['diff', '--stat', `${branch2}..${branch2}~${depth}`])
|
||||||
|
).stdout.trim()
|
||||||
|
return diff1 !== diff2
|
||||||
|
}
|
||||||
|
|
||||||
function splitLines(multilineString: string): string[] {
|
function splitLines(multilineString: string): string[] {
|
||||||
return multilineString
|
return multilineString
|
||||||
.split('\n')
|
.split('\n')
|
||||||
|
@ -114,7 +151,9 @@ interface CreateOrUpdateBranchResult {
|
||||||
action: string
|
action: string
|
||||||
base: string
|
base: string
|
||||||
hasDiffWithBase: boolean
|
hasDiffWithBase: boolean
|
||||||
|
baseCommit: Commit
|
||||||
headSha: string
|
headSha: string
|
||||||
|
branchCommits: Commit[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createOrUpdateBranch(
|
export async function createOrUpdateBranch(
|
||||||
|
@ -139,14 +178,6 @@ export async function createOrUpdateBranch(
|
||||||
base = base ? base : workingBase
|
base = base ? base : workingBase
|
||||||
const baseRemote = 'origin'
|
const baseRemote = 'origin'
|
||||||
|
|
||||||
// Set the default return values
|
|
||||||
const result: CreateOrUpdateBranchResult = {
|
|
||||||
action: 'none',
|
|
||||||
base: base,
|
|
||||||
hasDiffWithBase: false,
|
|
||||||
headSha: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the working base changes to a temporary branch
|
// Save the working base changes to a temporary branch
|
||||||
const tempBranch = uuidv4()
|
const tempBranch = uuidv4()
|
||||||
await git.checkout(tempBranch, 'HEAD')
|
await git.checkout(tempBranch, 'HEAD')
|
||||||
|
@ -226,6 +257,9 @@ export async function createOrUpdateBranch(
|
||||||
? tempBranchCommitsAhead + FETCH_DEPTH_MARGIN
|
? tempBranchCommitsAhead + FETCH_DEPTH_MARGIN
|
||||||
: FETCH_DEPTH_MARGIN
|
: FETCH_DEPTH_MARGIN
|
||||||
|
|
||||||
|
let action = 'none'
|
||||||
|
let hasDiffWithBase = false
|
||||||
|
|
||||||
// Try to fetch the pull request branch
|
// Try to fetch the pull request branch
|
||||||
if (!(await tryFetch(git, branchRemoteName, branch, fetchDepth))) {
|
if (!(await tryFetch(git, branchRemoteName, branch, fetchDepth))) {
|
||||||
// The pull request branch does not exist
|
// The pull request branch does not exist
|
||||||
|
@ -233,9 +267,9 @@ export async function createOrUpdateBranch(
|
||||||
// Create the pull request branch
|
// Create the pull request branch
|
||||||
await git.checkout(branch, tempBranch)
|
await git.checkout(branch, tempBranch)
|
||||||
// Check if the pull request branch is ahead of the base
|
// Check if the pull request branch is ahead of the base
|
||||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
hasDiffWithBase = await isAhead(git, base, branch)
|
||||||
if (result.hasDiffWithBase) {
|
if (hasDiffWithBase) {
|
||||||
result.action = 'created'
|
action = 'created'
|
||||||
core.info(`Created branch '${branch}'`)
|
core.info(`Created branch '${branch}'`)
|
||||||
} else {
|
} else {
|
||||||
core.info(
|
core.info(
|
||||||
|
@ -252,20 +286,26 @@ export async function createOrUpdateBranch(
|
||||||
|
|
||||||
// Reset the branch if one of the following conditions is true.
|
// Reset the branch if one of the following conditions is true.
|
||||||
// - If the branch differs from the recreated temp branch.
|
// - If the branch differs from the recreated temp branch.
|
||||||
|
// - If the number of commits ahead of the base branch differs between the branch and
|
||||||
|
// temp branch. This catches a case where the base branch has been force pushed to
|
||||||
|
// a new commit.
|
||||||
// - If the recreated temp branch is not ahead of the base. This means there will be
|
// - If the recreated temp branch is not ahead of the base. This means there will be
|
||||||
// no pull request diff after the branch is reset. This will reset any undeleted
|
// no pull request diff after the branch is reset. This will reset any undeleted
|
||||||
// branches after merging. In particular, it catches a case where the branch was
|
// branches after merging. In particular, it catches a case where the branch was
|
||||||
// squash merged but not deleted. We need to reset to make sure it doesn't appear
|
// squash merged but not deleted. We need to reset to make sure it doesn't appear
|
||||||
// to have a diff with the base due to different commits for the same changes.
|
// to have a diff with the base due to different commits for the same changes.
|
||||||
// - If the number of commits ahead of the base branch differs between the branch and
|
// - If the diff of the commits ahead of the base branch differs between the branch and
|
||||||
// temp branch. This catches a case where the base branch has been force pushed to
|
// temp branch. This catches a case where changes have been partially merged to the
|
||||||
// a new commit.
|
// base. The overall diff is the same, but the branch needs to be rebased to show
|
||||||
|
// the correct diff.
|
||||||
|
//
|
||||||
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
||||||
const branchCommitsAhead = await commitsAhead(git, base, branch)
|
const branchCommitsAhead = await commitsAhead(git, base, branch)
|
||||||
if (
|
if (
|
||||||
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||||
branchCommitsAhead != tempBranchCommitsAhead ||
|
branchCommitsAhead != tempBranchCommitsAhead ||
|
||||||
!(tempBranchCommitsAhead > 0) // !isAhead
|
!(tempBranchCommitsAhead > 0) || // !isAhead
|
||||||
|
(await commitsHaveDiff(git, branch, tempBranch, tempBranchCommitsAhead))
|
||||||
) {
|
) {
|
||||||
core.info(`Resetting '${branch}'`)
|
core.info(`Resetting '${branch}'`)
|
||||||
// Alternatively, git switch -C branch tempBranch
|
// Alternatively, git switch -C branch tempBranch
|
||||||
|
@ -276,21 +316,29 @@ export async function createOrUpdateBranch(
|
||||||
// If the branch was reset or updated it will be ahead
|
// If the branch was reset or updated it will be ahead
|
||||||
// It may be behind if a reset now results in no diff with the base
|
// It may be behind if a reset now results in no diff with the base
|
||||||
if (!(await isEven(git, `${branchRemoteName}/${branch}`, branch))) {
|
if (!(await isEven(git, `${branchRemoteName}/${branch}`, branch))) {
|
||||||
result.action = 'updated'
|
action = 'updated'
|
||||||
core.info(`Updated branch '${branch}'`)
|
core.info(`Updated branch '${branch}'`)
|
||||||
} else {
|
} else {
|
||||||
result.action = 'not-updated'
|
action = 'not-updated'
|
||||||
core.info(
|
core.info(
|
||||||
`Branch '${branch}' is even with its remote and will not be updated`
|
`Branch '${branch}' is even with its remote and will not be updated`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the pull request branch is ahead of the base
|
// Check if the pull request branch is ahead of the base
|
||||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
hasDiffWithBase = await isAhead(git, base, branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the pull request branch SHA
|
// Get the base and head SHAs
|
||||||
result.headSha = await git.revParse('HEAD')
|
const baseSha = await git.revParse(base)
|
||||||
|
const baseCommit = await git.getCommit(baseSha)
|
||||||
|
const headSha = await git.revParse(branch)
|
||||||
|
|
||||||
|
let branchCommits: Commit[] = []
|
||||||
|
if (hasDiffWithBase) {
|
||||||
|
// Build the branch commits
|
||||||
|
branchCommits = await buildBranchCommits(git, base, branch)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the temporary branch
|
// Delete the temporary branch
|
||||||
await git.exec(['branch', '--delete', '--force', tempBranch])
|
await git.exec(['branch', '--delete', '--force', tempBranch])
|
||||||
|
@ -303,5 +351,12 @@ export async function createOrUpdateBranch(
|
||||||
await git.stashPop()
|
await git.stashPop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return {
|
||||||
|
action: action,
|
||||||
|
base: base,
|
||||||
|
hasDiffWithBase: hasDiffWithBase,
|
||||||
|
baseCommit: baseCommit,
|
||||||
|
headSha: headSha,
|
||||||
|
branchCommits: branchCommits
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import * as utils from './utils'
|
||||||
|
|
||||||
export interface Inputs {
|
export interface Inputs {
|
||||||
token: string
|
token: string
|
||||||
gitToken: string
|
branchToken: string
|
||||||
path: string
|
path: string
|
||||||
addPaths: string[]
|
addPaths: string[]
|
||||||
commitMessage: string
|
commitMessage: string
|
||||||
|
@ -23,6 +23,7 @@ export interface Inputs {
|
||||||
branchSuffix: string
|
branchSuffix: string
|
||||||
base: string
|
base: string
|
||||||
pushToFork: string
|
pushToFork: string
|
||||||
|
signCommits: boolean
|
||||||
title: string
|
title: string
|
||||||
body: string
|
body: string
|
||||||
bodyPath: string
|
bodyPath: string
|
||||||
|
@ -31,7 +32,11 @@ export interface Inputs {
|
||||||
reviewers: string[]
|
reviewers: string[]
|
||||||
teamReviewers: string[]
|
teamReviewers: string[]
|
||||||
milestone: number
|
milestone: number
|
||||||
draft: boolean
|
draft: {
|
||||||
|
value: boolean
|
||||||
|
always: boolean
|
||||||
|
}
|
||||||
|
maintainerCanModify: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
|
@ -45,8 +50,9 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
|
|
||||||
core.startGroup('Determining the base and head repositories')
|
core.startGroup('Determining the base and head repositories')
|
||||||
const baseRemote = gitConfigHelper.getGitRemote()
|
const baseRemote = gitConfigHelper.getGitRemote()
|
||||||
// Init the GitHub client
|
// Init the GitHub clients
|
||||||
const githubHelper = new GitHubHelper(baseRemote.hostname, inputs.token)
|
const ghBranch = new GitHubHelper(baseRemote.hostname, inputs.branchToken)
|
||||||
|
const ghPull = new GitHubHelper(baseRemote.hostname, inputs.token)
|
||||||
// Determine the head repository; the target for the pull request branch
|
// Determine the head repository; the target for the pull request branch
|
||||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
||||||
const branchRepository = inputs.pushToFork
|
const branchRepository = inputs.pushToFork
|
||||||
|
@ -57,11 +63,11 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
core.info(
|
core.info(
|
||||||
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
||||||
)
|
)
|
||||||
const baseParentRepository = await githubHelper.getRepositoryParent(
|
const baseParentRepository = await ghBranch.getRepositoryParent(
|
||||||
baseRemote.repository
|
baseRemote.repository
|
||||||
)
|
)
|
||||||
const branchParentRepository =
|
const branchParentRepository =
|
||||||
await githubHelper.getRepositoryParent(branchRepository)
|
await ghBranch.getRepositoryParent(branchRepository)
|
||||||
if (branchParentRepository == null) {
|
if (branchParentRepository == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Repository '${branchRepository}' is not a fork. Unable to continue.`
|
`Repository '${branchRepository}' is not a fork. Unable to continue.`
|
||||||
|
@ -91,7 +97,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
// Configure auth
|
// Configure auth
|
||||||
if (baseRemote.protocol == 'HTTPS') {
|
if (baseRemote.protocol == 'HTTPS') {
|
||||||
core.startGroup('Configuring credential for HTTPS authentication')
|
core.startGroup('Configuring credential for HTTPS authentication')
|
||||||
await gitConfigHelper.configureToken(inputs.gitToken)
|
await gitConfigHelper.configureToken(inputs.branchToken)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +180,11 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
)
|
)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
|
// Action outputs
|
||||||
|
const outputs = new Map<string, string>()
|
||||||
|
outputs.set('pull-request-branch', inputs.branch)
|
||||||
|
outputs.set('pull-request-operation', 'none')
|
||||||
|
|
||||||
// Create or update the pull request branch
|
// Create or update the pull request branch
|
||||||
core.startGroup('Create or update the pull request branch')
|
core.startGroup('Create or update the pull request branch')
|
||||||
const result = await createOrUpdateBranch(
|
const result = await createOrUpdateBranch(
|
||||||
|
@ -185,6 +196,9 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
inputs.signoff,
|
inputs.signoff,
|
||||||
inputs.addPaths
|
inputs.addPaths
|
||||||
)
|
)
|
||||||
|
outputs.set('pull-request-head-sha', result.headSha)
|
||||||
|
// Set the base. It would have been '' if not specified as an input
|
||||||
|
inputs.base = result.base
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
if (['created', 'updated'].includes(result.action)) {
|
if (['created', 'updated'].includes(result.action)) {
|
||||||
|
@ -192,40 +206,55 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
core.startGroup(
|
core.startGroup(
|
||||||
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
||||||
)
|
)
|
||||||
await git.push([
|
if (inputs.signCommits) {
|
||||||
'--force-with-lease',
|
// Create signed commits via the GitHub API
|
||||||
branchRemoteName,
|
const stashed = await git.stashPush(['--include-untracked'])
|
||||||
`${inputs.branch}:refs/heads/${inputs.branch}`
|
await git.checkout(inputs.branch)
|
||||||
])
|
const pushSignedCommitsResult = await ghBranch.pushSignedCommits(
|
||||||
|
result.branchCommits,
|
||||||
|
result.baseCommit,
|
||||||
|
repoPath,
|
||||||
|
branchRepository,
|
||||||
|
inputs.branch
|
||||||
|
)
|
||||||
|
outputs.set('pull-request-head-sha', pushSignedCommitsResult.sha)
|
||||||
|
outputs.set(
|
||||||
|
'pull-request-commits-verified',
|
||||||
|
pushSignedCommitsResult.verified.toString()
|
||||||
|
)
|
||||||
|
await git.checkout('-')
|
||||||
|
if (stashed) {
|
||||||
|
await git.stashPop()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await git.push([
|
||||||
|
'--force-with-lease',
|
||||||
|
branchRemoteName,
|
||||||
|
`${inputs.branch}:refs/heads/${inputs.branch}`
|
||||||
|
])
|
||||||
|
}
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the base. It would have been '' if not specified as an input
|
|
||||||
inputs.base = result.base
|
|
||||||
|
|
||||||
if (result.hasDiffWithBase) {
|
if (result.hasDiffWithBase) {
|
||||||
// Create or update the pull request
|
|
||||||
core.startGroup('Create or update the pull request')
|
core.startGroup('Create or update the pull request')
|
||||||
const pull = await githubHelper.createOrUpdatePullRequest(
|
const pull = await ghPull.createOrUpdatePullRequest(
|
||||||
inputs,
|
inputs,
|
||||||
baseRemote.repository,
|
baseRemote.repository,
|
||||||
branchRepository
|
branchRepository
|
||||||
)
|
)
|
||||||
core.endGroup()
|
outputs.set('pull-request-number', pull.number.toString())
|
||||||
|
outputs.set('pull-request-url', pull.html_url)
|
||||||
// Set outputs
|
|
||||||
core.startGroup('Setting outputs')
|
|
||||||
core.setOutput('pull-request-number', pull.number)
|
|
||||||
core.setOutput('pull-request-url', pull.html_url)
|
|
||||||
if (pull.created) {
|
if (pull.created) {
|
||||||
core.setOutput('pull-request-operation', 'created')
|
outputs.set('pull-request-operation', 'created')
|
||||||
} else if (result.action == 'updated') {
|
} else if (result.action == 'updated') {
|
||||||
core.setOutput('pull-request-operation', 'updated')
|
outputs.set('pull-request-operation', 'updated')
|
||||||
|
// The pull request was updated AND the branch was updated.
|
||||||
|
// Convert back to draft if 'draft: always-true' is set.
|
||||||
|
if (inputs.draft.always && pull.draft !== undefined && !pull.draft) {
|
||||||
|
await ghPull.convertToDraft(pull.node_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
core.setOutput('pull-request-head-sha', result.headSha)
|
|
||||||
core.setOutput('pull-request-branch', inputs.branch)
|
|
||||||
// Deprecated
|
|
||||||
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
|
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
} else {
|
} else {
|
||||||
// There is no longer a diff with the base
|
// There is no longer a diff with the base
|
||||||
|
@ -242,13 +271,45 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
branchRemoteName,
|
branchRemoteName,
|
||||||
`refs/heads/${inputs.branch}`
|
`refs/heads/${inputs.branch}`
|
||||||
])
|
])
|
||||||
// Set outputs
|
outputs.set('pull-request-operation', 'closed')
|
||||||
core.startGroup('Setting outputs')
|
|
||||||
core.setOutput('pull-request-operation', 'closed')
|
|
||||||
core.endGroup()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core.startGroup('Setting outputs')
|
||||||
|
// If the head commit is signed, get its verification status if we don't already know it.
|
||||||
|
// This can happen if the branch wasn't updated (action = 'not-updated'), or GPG commit signing is in use.
|
||||||
|
if (
|
||||||
|
!outputs.has('pull-request-commits-verified') &&
|
||||||
|
result.branchCommits.length > 0 &&
|
||||||
|
result.branchCommits[result.branchCommits.length - 1].signed
|
||||||
|
) {
|
||||||
|
// Using the local head commit SHA because in this case commits have not been pushed via the API.
|
||||||
|
core.info(`Checking verification status of head commit ${result.headSha}`)
|
||||||
|
try {
|
||||||
|
const headCommit = await ghBranch.getCommit(
|
||||||
|
result.headSha,
|
||||||
|
branchRepository
|
||||||
|
)
|
||||||
|
outputs.set(
|
||||||
|
'pull-request-commits-verified',
|
||||||
|
headCommit.verified.toString()
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
core.warning('Failed to check verification status of head commit.')
|
||||||
|
core.debug(utils.getErrorMessage(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!outputs.has('pull-request-commits-verified')) {
|
||||||
|
outputs.set('pull-request-commits-verified', 'false')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set outputs
|
||||||
|
for (const [key, value] of outputs) {
|
||||||
|
core.info(`${key} = ${value}`)
|
||||||
|
core.setOutput(key, value)
|
||||||
|
}
|
||||||
|
core.endGroup()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(utils.getErrorMessage(error))
|
core.setFailed(utils.getErrorMessage(error))
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -5,6 +5,21 @@ import * as path from 'path'
|
||||||
|
|
||||||
const tagsRefSpec = '+refs/tags/*:refs/tags/*'
|
const tagsRefSpec = '+refs/tags/*:refs/tags/*'
|
||||||
|
|
||||||
|
export type Commit = {
|
||||||
|
sha: string
|
||||||
|
tree: string
|
||||||
|
parents: string[]
|
||||||
|
signed: boolean
|
||||||
|
subject: string
|
||||||
|
body: string
|
||||||
|
changes: {
|
||||||
|
mode: string
|
||||||
|
status: 'A' | 'M' | 'D'
|
||||||
|
path: string
|
||||||
|
}[]
|
||||||
|
unparsedChanges: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export class GitCommandManager {
|
export class GitCommandManager {
|
||||||
private gitPath: string
|
private gitPath: string
|
||||||
private workingDirectory: string
|
private workingDirectory: string
|
||||||
|
@ -138,6 +153,45 @@ export class GitCommandManager {
|
||||||
await this.exec(args)
|
await this.exec(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCommit(ref: string): Promise<Commit> {
|
||||||
|
const endOfBody = '###EOB###'
|
||||||
|
const output = await this.exec([
|
||||||
|
'show',
|
||||||
|
'--raw',
|
||||||
|
'--cc',
|
||||||
|
`--format=%H%n%T%n%P%n%G?%n%s%n%b%n${endOfBody}`,
|
||||||
|
ref
|
||||||
|
])
|
||||||
|
const lines = output.stdout.split('\n')
|
||||||
|
const endOfBodyIndex = lines.lastIndexOf(endOfBody)
|
||||||
|
const detailLines = lines.slice(0, endOfBodyIndex)
|
||||||
|
|
||||||
|
const unparsedChanges: string[] = []
|
||||||
|
return <Commit>{
|
||||||
|
sha: detailLines[0],
|
||||||
|
tree: detailLines[1],
|
||||||
|
parents: detailLines[2].split(' '),
|
||||||
|
signed: detailLines[3] !== 'N',
|
||||||
|
subject: detailLines[4],
|
||||||
|
body: detailLines.slice(5, endOfBodyIndex).join('\n'),
|
||||||
|
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
|
||||||
|
const change = line.match(
|
||||||
|
/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/
|
||||||
|
)
|
||||||
|
if (change) {
|
||||||
|
return {
|
||||||
|
mode: change[3] === 'D' ? change[1] : change[2],
|
||||||
|
status: change[3],
|
||||||
|
path: change[4]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unparsedChanges.push(line)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
unparsedChanges: unparsedChanges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
||||||
const output = await this.exec([
|
const output = await this.exec([
|
||||||
'config',
|
'config',
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {Inputs} from './create-pull-request'
|
import {Inputs} from './create-pull-request'
|
||||||
import {Octokit, OctokitOptions} from './octokit-client'
|
import {Commit} from './git-command-manager'
|
||||||
|
import {Octokit, OctokitOptions, throttleOptions} from './octokit-client'
|
||||||
|
import pLimit from 'p-limit'
|
||||||
import * as utils from './utils'
|
import * as utils from './utils'
|
||||||
|
|
||||||
|
const ERROR_PR_ALREADY_EXISTS = 'A pull request already exists for'
|
||||||
const ERROR_PR_REVIEW_TOKEN_SCOPE =
|
const ERROR_PR_REVIEW_TOKEN_SCOPE =
|
||||||
'Validation Failed: "Could not resolve to a node with the global id of'
|
'Validation Failed: "Could not resolve to a node with the global id of'
|
||||||
|
const ERROR_PR_FORK_COLLAB = `Fork collab can't be granted by someone without permission`
|
||||||
|
|
||||||
|
const blobCreationLimit = pLimit(8)
|
||||||
|
|
||||||
interface Repository {
|
interface Repository {
|
||||||
owner: string
|
owner: string
|
||||||
|
@ -14,9 +20,24 @@ interface Repository {
|
||||||
interface Pull {
|
interface Pull {
|
||||||
number: number
|
number: number
|
||||||
html_url: string
|
html_url: string
|
||||||
|
node_id: string
|
||||||
|
draft?: boolean
|
||||||
created: boolean
|
created: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CommitResponse {
|
||||||
|
sha: string
|
||||||
|
tree: string
|
||||||
|
verified: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeObject = {
|
||||||
|
path: string
|
||||||
|
mode: '100644' | '100755' | '040000' | '160000' | '120000'
|
||||||
|
sha: string | null
|
||||||
|
type: 'blob'
|
||||||
|
}
|
||||||
|
|
||||||
export class GitHubHelper {
|
export class GitHubHelper {
|
||||||
private octokit: InstanceType<typeof Octokit>
|
private octokit: InstanceType<typeof Octokit>
|
||||||
|
|
||||||
|
@ -30,6 +51,7 @@ export class GitHubHelper {
|
||||||
} else {
|
} else {
|
||||||
options.baseUrl = 'https://api.github.com'
|
options.baseUrl = 'https://api.github.com'
|
||||||
}
|
}
|
||||||
|
options.throttle = throttleOptions
|
||||||
this.octokit = new Octokit(options)
|
this.octokit = new Octokit(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +81,8 @@ export class GitHubHelper {
|
||||||
head_repo: headRepository,
|
head_repo: headRepository,
|
||||||
base: inputs.base,
|
base: inputs.base,
|
||||||
body: inputs.body,
|
body: inputs.body,
|
||||||
draft: inputs.draft
|
draft: inputs.draft.value,
|
||||||
|
maintainer_can_modify: inputs.maintainerCanModify
|
||||||
})
|
})
|
||||||
core.info(
|
core.info(
|
||||||
`Created pull request #${pull.number} (${headBranch} => ${inputs.base})`
|
`Created pull request #${pull.number} (${headBranch} => ${inputs.base})`
|
||||||
|
@ -67,13 +90,22 @@ export class GitHubHelper {
|
||||||
return {
|
return {
|
||||||
number: pull.number,
|
number: pull.number,
|
||||||
html_url: pull.html_url,
|
html_url: pull.html_url,
|
||||||
|
node_id: pull.node_id,
|
||||||
|
draft: pull.draft,
|
||||||
created: true
|
created: true
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (
|
const errorMessage = utils.getErrorMessage(e)
|
||||||
utils.getErrorMessage(e).includes(`A pull request already exists for`)
|
if (errorMessage.includes(ERROR_PR_ALREADY_EXISTS)) {
|
||||||
) {
|
|
||||||
core.info(`A pull request already exists for ${headBranch}`)
|
core.info(`A pull request already exists for ${headBranch}`)
|
||||||
|
} else if (errorMessage.includes(ERROR_PR_FORK_COLLAB)) {
|
||||||
|
core.warning(
|
||||||
|
'An attempt was made to create a pull request using a token that does not have write access to the head branch.'
|
||||||
|
)
|
||||||
|
core.warning(
|
||||||
|
`For this case, set input 'maintainer-can-modify' to 'false' to allow pull request creation.`
|
||||||
|
)
|
||||||
|
throw e
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
@ -100,6 +132,8 @@ export class GitHubHelper {
|
||||||
return {
|
return {
|
||||||
number: pull.number,
|
number: pull.number,
|
||||||
html_url: pull.html_url,
|
html_url: pull.html_url,
|
||||||
|
node_id: pull.node_id,
|
||||||
|
draft: pull.draft,
|
||||||
created: false
|
created: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,4 +218,175 @@ export class GitHubHelper {
|
||||||
|
|
||||||
return pull
|
return pull
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async pushSignedCommits(
|
||||||
|
branchCommits: Commit[],
|
||||||
|
baseCommit: Commit,
|
||||||
|
repoPath: string,
|
||||||
|
branchRepository: string,
|
||||||
|
branch: string
|
||||||
|
): Promise<CommitResponse> {
|
||||||
|
let headCommit: CommitResponse = {
|
||||||
|
sha: baseCommit.sha,
|
||||||
|
tree: baseCommit.tree,
|
||||||
|
verified: false
|
||||||
|
}
|
||||||
|
for (const commit of branchCommits) {
|
||||||
|
headCommit = await this.createCommit(
|
||||||
|
commit,
|
||||||
|
headCommit,
|
||||||
|
repoPath,
|
||||||
|
branchRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await this.createOrUpdateRef(branchRepository, branch, headCommit.sha)
|
||||||
|
return headCommit
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createCommit(
|
||||||
|
commit: Commit,
|
||||||
|
parentCommit: CommitResponse,
|
||||||
|
repoPath: string,
|
||||||
|
branchRepository: string
|
||||||
|
): Promise<CommitResponse> {
|
||||||
|
const repository = this.parseRepository(branchRepository)
|
||||||
|
// In the case of an empty commit, the tree references the parent's tree
|
||||||
|
let treeSha = parentCommit.tree
|
||||||
|
if (commit.changes.length > 0) {
|
||||||
|
core.info(`Creating tree objects for local commit ${commit.sha}`)
|
||||||
|
const treeObjects = await Promise.all(
|
||||||
|
commit.changes.map(async ({path, mode, status}) => {
|
||||||
|
let sha: string | null = null
|
||||||
|
if (status === 'A' || status === 'M') {
|
||||||
|
try {
|
||||||
|
const {data: blob} = await blobCreationLimit(() =>
|
||||||
|
this.octokit.rest.git.createBlob({
|
||||||
|
...repository,
|
||||||
|
content: utils.readFileBase64([repoPath, path]),
|
||||||
|
encoding: 'base64'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
sha = blob.sha
|
||||||
|
} catch (error) {
|
||||||
|
core.error(
|
||||||
|
`Error creating blob for file '${path}': ${utils.getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core.info(`Created blob for file '${path}'`)
|
||||||
|
return <TreeObject>{
|
||||||
|
path,
|
||||||
|
mode,
|
||||||
|
sha,
|
||||||
|
type: 'blob'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const chunkSize = 100
|
||||||
|
const chunkedTreeObjects: TreeObject[][] = Array.from(
|
||||||
|
{length: Math.ceil(treeObjects.length / chunkSize)},
|
||||||
|
(_, i) => treeObjects.slice(i * chunkSize, i * chunkSize + chunkSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
core.info(`Creating tree for local commit ${commit.sha}`)
|
||||||
|
for (let i = 0; i < chunkedTreeObjects.length; i++) {
|
||||||
|
const {data: tree} = await this.octokit.rest.git.createTree({
|
||||||
|
...repository,
|
||||||
|
base_tree: treeSha,
|
||||||
|
tree: chunkedTreeObjects[i]
|
||||||
|
})
|
||||||
|
treeSha = tree.sha
|
||||||
|
if (chunkedTreeObjects.length > 1) {
|
||||||
|
core.info(
|
||||||
|
`Created tree ${treeSha} of multipart tree (${i + 1} of ${chunkedTreeObjects.length})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core.info(`Created tree ${treeSha} for local commit ${commit.sha}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {data: remoteCommit} = await this.octokit.rest.git.createCommit({
|
||||||
|
...repository,
|
||||||
|
parents: [parentCommit.sha],
|
||||||
|
tree: treeSha,
|
||||||
|
message: `${commit.subject}\n\n${commit.body}`
|
||||||
|
})
|
||||||
|
core.info(
|
||||||
|
`Created commit ${remoteCommit.sha} for local commit ${commit.sha}`
|
||||||
|
)
|
||||||
|
core.info(
|
||||||
|
`Commit verified: ${remoteCommit.verification.verified}; reason: ${remoteCommit.verification.reason}`
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
sha: remoteCommit.sha,
|
||||||
|
tree: remoteCommit.tree.sha,
|
||||||
|
verified: remoteCommit.verification.verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCommit(
|
||||||
|
sha: string,
|
||||||
|
branchRepository: string
|
||||||
|
): Promise<CommitResponse> {
|
||||||
|
const repository = this.parseRepository(branchRepository)
|
||||||
|
const {data: remoteCommit} = await this.octokit.rest.git.getCommit({
|
||||||
|
...repository,
|
||||||
|
commit_sha: sha
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
sha: remoteCommit.sha,
|
||||||
|
tree: remoteCommit.tree.sha,
|
||||||
|
verified: remoteCommit.verification.verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createOrUpdateRef(
|
||||||
|
branchRepository: string,
|
||||||
|
branch: string,
|
||||||
|
newHead: string
|
||||||
|
) {
|
||||||
|
const repository = this.parseRepository(branchRepository)
|
||||||
|
const branchExists = await this.octokit.rest.repos
|
||||||
|
.getBranch({
|
||||||
|
...repository,
|
||||||
|
branch: branch
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
() => true,
|
||||||
|
() => false
|
||||||
|
)
|
||||||
|
|
||||||
|
if (branchExists) {
|
||||||
|
core.info(`Branch ${branch} exists; Updating ref`)
|
||||||
|
await this.octokit.rest.git.updateRef({
|
||||||
|
...repository,
|
||||||
|
sha: newHead,
|
||||||
|
ref: `heads/${branch}`,
|
||||||
|
force: true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
core.info(`Branch ${branch} does not exist; Creating ref`)
|
||||||
|
await this.octokit.rest.git.createRef({
|
||||||
|
...repository,
|
||||||
|
sha: newHead,
|
||||||
|
ref: `refs/heads/${branch}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async convertToDraft(id: string): Promise<void> {
|
||||||
|
core.info(`Converting pull request to draft`)
|
||||||
|
await this.octokit.graphql({
|
||||||
|
query: `mutation($pullRequestId: ID!) {
|
||||||
|
convertPullRequestToDraft(input: {pullRequestId: $pullRequestId}) {
|
||||||
|
pullRequest {
|
||||||
|
isDraft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
pullRequestId: id
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/main.ts
18
src/main.ts
|
@ -3,11 +3,19 @@ import {Inputs, createPullRequest} from './create-pull-request'
|
||||||
import {inspect} from 'util'
|
import {inspect} from 'util'
|
||||||
import * as utils from './utils'
|
import * as utils from './utils'
|
||||||
|
|
||||||
|
function getDraftInput(): {value: boolean; always: boolean} {
|
||||||
|
if (core.getInput('draft') === 'always-true') {
|
||||||
|
return {value: true, always: true}
|
||||||
|
} else {
|
||||||
|
return {value: core.getBooleanInput('draft'), always: false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const inputs: Inputs = {
|
const inputs: Inputs = {
|
||||||
token: core.getInput('token'),
|
token: core.getInput('token'),
|
||||||
gitToken: core.getInput('git-token'),
|
branchToken: core.getInput('branch-token'),
|
||||||
path: core.getInput('path'),
|
path: core.getInput('path'),
|
||||||
addPaths: utils.getInputAsArray('add-paths'),
|
addPaths: utils.getInputAsArray('add-paths'),
|
||||||
commitMessage: core.getInput('commit-message'),
|
commitMessage: core.getInput('commit-message'),
|
||||||
|
@ -19,6 +27,7 @@ async function run(): Promise<void> {
|
||||||
branchSuffix: core.getInput('branch-suffix'),
|
branchSuffix: core.getInput('branch-suffix'),
|
||||||
base: core.getInput('base'),
|
base: core.getInput('base'),
|
||||||
pushToFork: core.getInput('push-to-fork'),
|
pushToFork: core.getInput('push-to-fork'),
|
||||||
|
signCommits: core.getBooleanInput('sign-commits'),
|
||||||
title: core.getInput('title'),
|
title: core.getInput('title'),
|
||||||
body: core.getInput('body'),
|
body: core.getInput('body'),
|
||||||
bodyPath: core.getInput('body-path'),
|
bodyPath: core.getInput('body-path'),
|
||||||
|
@ -27,15 +36,16 @@ async function run(): Promise<void> {
|
||||||
reviewers: utils.getInputAsArray('reviewers'),
|
reviewers: utils.getInputAsArray('reviewers'),
|
||||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||||
milestone: Number(core.getInput('milestone')),
|
milestone: Number(core.getInput('milestone')),
|
||||||
draft: core.getBooleanInput('draft')
|
draft: getDraftInput(),
|
||||||
|
maintainerCanModify: core.getBooleanInput('maintainer-can-modify')
|
||||||
}
|
}
|
||||||
core.debug(`Inputs: ${inspect(inputs)}`)
|
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||||
|
|
||||||
if (!inputs.token) {
|
if (!inputs.token) {
|
||||||
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
||||||
}
|
}
|
||||||
if (!inputs.gitToken) {
|
if (!inputs.branchToken) {
|
||||||
inputs.gitToken = inputs.token
|
inputs.branchToken = inputs.token
|
||||||
}
|
}
|
||||||
if (inputs.bodyPath) {
|
if (inputs.bodyPath) {
|
||||||
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
||||||
|
|
|
@ -1,17 +1,38 @@
|
||||||
import {Octokit as Core} from '@octokit/core'
|
import * as core from '@actions/core'
|
||||||
|
import {Octokit as OctokitCore} from '@octokit/core'
|
||||||
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
||||||
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
||||||
|
import {throttling} from '@octokit/plugin-throttling'
|
||||||
import {getProxyForUrl} from 'proxy-from-env'
|
import {getProxyForUrl} from 'proxy-from-env'
|
||||||
import {ProxyAgent, fetch as undiciFetch} from 'undici'
|
import {ProxyAgent, fetch as undiciFetch} from 'undici'
|
||||||
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||||
|
|
||||||
export const Octokit = Core.plugin(
|
export const Octokit = OctokitCore.plugin(
|
||||||
paginateRest,
|
paginateRest,
|
||||||
restEndpointMethods,
|
restEndpointMethods,
|
||||||
|
throttling,
|
||||||
autoProxyAgent
|
autoProxyAgent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const throttleOptions = {
|
||||||
|
onRateLimit: (retryAfter, options, _, retryCount) => {
|
||||||
|
core.debug(`Hit rate limit for request ${options.method} ${options.url}`)
|
||||||
|
// Retries twice for a total of three attempts
|
||||||
|
if (retryCount < 2) {
|
||||||
|
core.debug(`Retrying after ${retryAfter} seconds!`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSecondaryRateLimit: (retryAfter, options) => {
|
||||||
|
core.warning(
|
||||||
|
`Hit secondary rate limit for request ${options.method} ${options.url}`
|
||||||
|
)
|
||||||
|
core.warning(`Requests may be retried after ${retryAfter} seconds.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const proxyFetch =
|
const proxyFetch =
|
||||||
(proxyUrl: string): typeof undiciFetch =>
|
(proxyUrl: string): typeof undiciFetch =>
|
||||||
(url, opts) => {
|
(url, opts) => {
|
||||||
|
@ -24,7 +45,7 @@ const proxyFetch =
|
||||||
}
|
}
|
||||||
|
|
||||||
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
|
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
|
||||||
function autoProxyAgent(octokit: Core) {
|
function autoProxyAgent(octokit: OctokitCore) {
|
||||||
octokit.hook.before('request', options => {
|
octokit.hook.before('request', options => {
|
||||||
const proxy = getProxyForUrl(options.baseUrl)
|
const proxy = getProxyForUrl(options.baseUrl)
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
|
|
|
@ -126,6 +126,10 @@ export function readFile(path: string): string {
|
||||||
return fs.readFileSync(path, 'utf-8')
|
return fs.readFileSync(path, 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readFileBase64(pathParts: string[]): string {
|
||||||
|
return fs.readFileSync(path.resolve(...pathParts)).toString('base64')
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
function hasErrorCode(error: any): error is {code: string} {
|
function hasErrorCode(error: any): error is {code: string} {
|
||||||
return typeof (error && error.code) === 'string'
|
return typeof (error && error.code) === 'string'
|
||||||
|
|
Loading…
Reference in a new issue