Compare commits

..

45 Commits

Author SHA1 Message Date
740d9850a7 add maintainer-can-modify input 2024-08-16 15:44:43 +01:00
e7f5ea9fd9 use separate client for branch and pull operations 2024-08-16 12:26:38 +00:00
66ddf90dac output retryafter for secondary rate limit 2024-08-15 15:24:58 +00:00
fd3e742ffd default the operation output to none 2024-08-15 13:37:41 +00:00
eea4f44785 fix head sha output 2024-08-15 10:16:22 +00:00
5a9be5875b log outputs 2024-08-14 21:55:31 +00:00
3d665e5aea output head sha and verified status 2024-08-14 21:50:59 +00:00
bb1f2b1327 set default back to false 2024-08-14 21:06:45 +00:00
1da4bbe67c add throttling 2024-08-14 21:10:21 +01:00
d93a919a26 update docs 2024-08-14 17:17:10 +01:00
a2d4746d68 fix capital letter 2024-08-14 17:17:10 +01:00
2c262e8e92 update docs for commit signing 2024-08-14 17:17:10 +01:00
7b7dc5777f update readme link 2024-08-14 17:17:10 +01:00
c1be170c86 remove unused code 2024-08-14 17:17:10 +01:00
822f3b39c1 only build commits when feature enabled 2024-08-14 17:17:10 +01:00
197e74c6e1 limit blob creation concurrency 2024-08-14 17:17:09 +01:00
c7909f9b04 add executable mode file to test 2024-08-14 17:17:09 +01:00
491f77f4d6 fix format and cleanup 2024-08-14 17:17:09 +01:00
93858f721d debug commit verification 2024-08-14 17:17:09 +01:00
2668dc956a debug commit verification 2024-08-14 17:17:09 +01:00
b0303827bb try fix base tree 2024-08-14 17:17:09 +01:00
90b04fe25b force push 2024-08-14 17:17:08 +01:00
2707da835d fix check for branch existence 2024-08-14 17:17:08 +01:00
e4c51477d1 try rest api route 2024-08-14 17:17:08 +01:00
0a237f343d use source mode for deleted files 2024-08-14 17:17:08 +01:00
77c6c11180 build branch commits 2024-08-14 17:17:07 +01:00
477c78c3f2 fix format 2024-08-14 17:17:07 +01:00
7f459482cc add function to get commit detail 2024-08-14 17:17:07 +01:00
018afb52b6 build file changes even when there is no diff 2024-08-14 17:17:07 +01:00
3a7a677a14 refactor graphql code into github helper class 2024-08-14 17:17:07 +01:00
74416df758 add build file changes test for binary files 2024-08-14 17:17:07 +01:00
7575ead361 add tests for building file changes 2024-08-14 17:17:07 +01:00
3d409de49f Try refactor of file changes 2024-08-14 17:17:06 +01:00
743dcd81f7 remove commented code 2024-08-14 17:17:06 +01:00
0f72e35b7f try to fix head repo 2024-08-14 17:17:06 +01:00
4a3e69b7f7 fix filepath when using path input 2024-08-14 17:17:06 +01:00
525f1f0028 disable linter for debug code 2024-08-14 17:17:05 +01:00
36bba202e3 debug payload without contents 2024-08-14 17:17:05 +01:00
3563849c8a read to buffer not string and use non-legacy method to base64 2024-08-14 17:17:05 +01:00
6c03c11aff add debug lines 2024-08-14 17:17:05 +01:00
27642d5a9e sign commits by default for testing 2024-08-14 17:17:05 +01:00
136db6a783 shift setting the base to before the push 2024-08-14 17:17:05 +01:00
43d45f2e88 fix eslint and lint errors 2024-08-14 17:17:04 +01:00
24bfe8de6b formatting 2024-08-14 17:16:38 +01:00
22fb2d9a65 Add support for signed commits (#3055) 2024-08-14 17:16:38 +01:00
18 changed files with 268 additions and 831 deletions

View File

@ -109,9 +109,6 @@ jobs:
``` ```
/test repository=${{ github.event.pull_request.head.repo.full_name }} ref=${{ github.event.pull_request.head.ref }} build=true /test repository=${{ github.event.pull_request.head.repo.full_name }} ref=${{ github.event.pull_request.head.ref }} build=true
``` ```
```
/test repository=${{ github.event.pull_request.head.repo.full_name }} ref=${{ github.event.pull_request.head.ref }} build=true sign-commits=true
```
package: package:
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'

View File

@ -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@v7 uses: peter-evans/create-pull-request@v6
``` ```
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v7.x.x` You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v6.x.x`
### Workflow permissions ### Workflow permissions
@ -48,10 +48,12 @@ 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` | The token that the action will use to create and update the pull request. See [token](#token). | `GITHUB_TOKEN` | | `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` |
| `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` | | `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` |
| `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` |
@ -72,36 +74,9 @@ 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). Valid values are `true` (only on create), `always-true` (on create and update), and `false`. | `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). It is not possible to change draft status after creation except through the web interface. | `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` | | `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
In addition to a message, the `commit-message` input can also be used to populate the commit description. Leave a single blank line between the message and description. In addition to a message, the `commit-message` input can also be used to populate the commit description. Leave a single blank line between the message and description.
@ -131,7 +106,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@v7 uses: peter-evans/create-pull-request@v6
env: env:
https_proxy: http://<proxy_address>:<port> https_proxy: http://<proxy_address>:<port>
``` ```
@ -153,7 +128,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@v7 uses: peter-evans/create-pull-request@v6
- name: Check outputs - name: Check outputs
if: ${{ steps.cpr.outputs.pull-request-number }} if: ${{ steps.cpr.outputs.pull-request-number }}
run: | run: |
@ -216,7 +191,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@v7 uses: peter-evans/create-pull-request@v6
with: with:
add-paths: | add-paths: |
*.java *.java
@ -243,7 +218,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@v7 uses: peter-evans/create-pull-request@v6
``` ```
### Create a project card ### Create a project card
@ -253,7 +228,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@v7 uses: peter-evans/create-pull-request@v6
- 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 }}
@ -288,7 +263,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v6
with: with:
token: ${{ secrets.PAT }} token: ${{ secrets.PAT }}
commit-message: Update report commit-message: Update report

View File

@ -657,76 +657,6 @@ 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
@ -1749,81 +1679,6 @@ 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

View File

@ -22,7 +22,6 @@ 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 echo "#test-base :sparkles:" > README.md
git add . git add .
git commit -m "add sparkles" -m "Change description: git commit -m "add sparkles" -m "Change description:

View File

@ -1,4 +1,4 @@
import {GitCommandManager} from '../lib/git-command-manager' import {GitCommandManager, Commit} from '../lib/git-command-manager'
const REPO_PATH = '/git/local/repos/test-base' const REPO_PATH = '/git/local/repos/test-base'
@ -11,26 +11,15 @@ describe('git-command-manager integration tests', () => {
}) })
it('tests getCommit', async () => { it('tests getCommit', async () => {
const initialCommit = await git.getCommit('HEAD^^') const parent = await git.getCommit('HEAD^')
const emptyCommit = await git.getCommit('HEAD^') const commit = await git.getCommit('HEAD')
const headCommit = await git.getCommit('HEAD') expect(parent.subject).toEqual('initial commit')
expect(parent.changes).toEqual([
expect(initialCommit.subject).toEqual('initial commit')
expect(initialCommit.signed).toBeFalsy()
expect(initialCommit.changes).toEqual([
{mode: '100644', status: 'A', path: 'README.md'} {mode: '100644', status: 'A', path: 'README.md'}
]) ])
expect(commit.subject).toEqual('add sparkles')
expect(emptyCommit.subject).toEqual('empty commit for tests') expect(commit.parents[0]).toEqual(parent.sha)
expect(emptyCommit.tree).toEqual(initialCommit.tree) // empty commits have no tree and reference the parent's expect(commit.changes).toEqual([
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'} {mode: '100644', status: 'M', path: 'README.md'}
]) ])
}) })

View File

@ -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: 'The token that the action will use to create and update the pull request.' description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
default: ${{ github.token }} default: ${{ github.token }}
branch-token: git-token:
description: > description: >
The token that the action will use to create and update the branch. The Personal Access Token (PAT) that the action will use for git operations.
Defaults to the value of `token`. Defaults to the value of `token`.
path: path:
description: > description: >
@ -75,9 +75,7 @@ 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: > description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
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: maintainer-can-modify:
description: 'Indicates whether maintainers can modify the pull request.' description: 'Indicates whether maintainers can modify the pull request.'

298
dist/index.js vendored
View File

@ -93,9 +93,6 @@ function buildBranchCommits(git, base, branch) {
for (const sha of shas) { for (const sha of shas) {
const commit = yield git.getCommit(sha); const commit = yield git.getCommit(sha);
commits.push(commit); commits.push(commit);
for (const unparsedChange of commit.unparsedChanges) {
core.warning(`Skipping unexpected diff entry: ${unparsedChange}`);
}
} }
return commits; return commits;
}); });
@ -133,22 +130,14 @@ function isEven(git, branch1, branch2) {
!(yield isBehind(git, branch1, branch2))); !(yield isBehind(git, branch1, branch2)));
}); });
} }
// Return true if the specified number of commits on branch1 and branch2 have a diff
function commitsHaveDiff(git, branch1, branch2, depth) {
return __awaiter(this, void 0, void 0, function* () {
const diff1 = (yield git.exec(['diff', '--stat', `${branch1}..${branch1}~${depth}`])).stdout.trim();
const diff2 = (yield git.exec(['diff', '--stat', `${branch2}..${branch2}~${depth}`])).stdout.trim();
return diff1 !== diff2;
});
}
function splitLines(multilineString) { function splitLines(multilineString) {
return multilineString return multilineString
.split('\n') .split('\n')
.map(s => s.trim()) .map(s => s.trim())
.filter(x => x !== ''); .filter(x => x !== '');
} }
function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName, signoff, addPaths) { function createOrUpdateBranch(git_1, commitMessage_1, base_1, branch_1, branchRemoteName_1, signoff_1, addPaths_1) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, arguments, void 0, function* (git, commitMessage, base, branch, branchRemoteName, signoff, addPaths, signCommits = false) {
// Get the working base. // Get the working base.
// When a ref, it may or may not be the actual base. // When a ref, it may or may not be the actual base.
// When a commit, we must rebase onto the actual base. // When a commit, we must rebase onto the actual base.
@ -160,6 +149,15 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
// If the base is not specified it is assumed to be the working base. // If the base is not specified it is assumed to be the working base.
base = base ? base : workingBase; base = base ? base : workingBase;
const baseRemote = 'origin'; const baseRemote = 'origin';
// Set the default return values
const result = {
action: 'none',
base: base,
hasDiffWithBase: false,
baseSha: '',
headSha: '',
branchCommits: []
};
// Save the working base changes to a temporary branch // Save the working base changes to a temporary branch
const tempBranch = (0, uuid_1.v4)(); const tempBranch = (0, uuid_1.v4)();
yield git.checkout(tempBranch, 'HEAD'); yield git.checkout(tempBranch, 'HEAD');
@ -224,8 +222,6 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
const fetchDepth = tempBranchCommitsAhead > 0 const fetchDepth = tempBranchCommitsAhead > 0
? 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 (!(yield tryFetch(git, branchRemoteName, branch, fetchDepth))) { if (!(yield tryFetch(git, branchRemoteName, branch, fetchDepth))) {
// The pull request branch does not exist // The pull request branch does not exist
@ -233,9 +229,9 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
// Create the pull request branch // Create the pull request branch
yield git.checkout(branch, tempBranch); yield 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
hasDiffWithBase = yield isAhead(git, base, branch); result.hasDiffWithBase = yield isAhead(git, base, branch);
if (hasDiffWithBase) { if (result.hasDiffWithBase) {
action = 'created'; result.action = 'created';
core.info(`Created branch '${branch}'`); core.info(`Created branch '${branch}'`);
} }
else { else {
@ -249,25 +245,20 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
yield git.checkout(branch); yield git.checkout(branch);
// 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 diff of the commits ahead of the base branch differs between the branch and // - If the number of commits ahead of the base branch differs between the branch and
// temp branch. This catches a case where changes have been partially merged to the // temp branch. This catches a case where the base branch has been force pushed to
// base. The overall diff is the same, but the branch needs to be rebased to show // a new commit.
// 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 = yield commitsAhead(git, base, branch); const branchCommitsAhead = yield commitsAhead(git, base, branch);
if ((yield git.hasDiff([`${branch}..${tempBranch}`])) || if ((yield git.hasDiff([`${branch}..${tempBranch}`])) ||
branchCommitsAhead != tempBranchCommitsAhead || branchCommitsAhead != tempBranchCommitsAhead ||
!(tempBranchCommitsAhead > 0) || // !isAhead !(tempBranchCommitsAhead > 0) // !isAhead
(yield 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
yield git.checkout(branch, tempBranch); yield git.checkout(branch, tempBranch);
@ -276,24 +267,23 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
// 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 (!(yield isEven(git, `${branchRemoteName}/${branch}`, branch))) { if (!(yield isEven(git, `${branchRemoteName}/${branch}`, branch))) {
action = 'updated'; result.action = 'updated';
core.info(`Updated branch '${branch}'`); core.info(`Updated branch '${branch}'`);
} }
else { else {
action = 'not-updated'; result.action = 'not-updated';
core.info(`Branch '${branch}' is even with its remote and will not be updated`); core.info(`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
hasDiffWithBase = yield isAhead(git, base, branch); result.hasDiffWithBase = yield isAhead(git, base, branch);
} }
// Get the base and head SHAs // Get the base and head SHAs
const baseSha = yield git.revParse(base); result.baseSha = yield git.revParse(base);
const baseCommit = yield git.getCommit(baseSha); result.headSha = yield git.revParse(branch);
const headSha = yield git.revParse(branch); // NOTE: This could always be built and returned. Maybe remove when there is confidence in buildBranchCommits.
let branchCommits = []; if (signCommits) {
if (hasDiffWithBase) {
// Build the branch commits // Build the branch commits
branchCommits = yield buildBranchCommits(git, base, branch); result.branchCommits = yield buildBranchCommits(git, base, branch);
} }
// Delete the temporary branch // Delete the temporary branch
yield git.exec(['branch', '--delete', '--force', tempBranch]); yield git.exec(['branch', '--delete', '--force', tempBranch]);
@ -303,14 +293,7 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
if (stashed) { if (stashed) {
yield git.stashPop(); yield git.stashPop();
} }
return { return result;
action: action,
base: base,
hasDiffWithBase: hasDiffWithBase,
baseCommit: baseCommit,
headSha: headSha,
branchCommits: branchCommits
};
}); });
} }
@ -374,7 +357,7 @@ function createPullRequest(inputs) {
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 clients // Init the GitHub clients
const ghBranch = new github_helper_1.GitHubHelper(baseRemote.hostname, inputs.branchToken); const ghBranch = new github_helper_1.GitHubHelper(baseRemote.hostname, inputs.gitToken);
const ghPull = new github_helper_1.GitHubHelper(baseRemote.hostname, inputs.token); const ghPull = new github_helper_1.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';
@ -402,7 +385,7 @@ function createPullRequest(inputs) {
// 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');
yield gitConfigHelper.configureToken(inputs.branchToken); yield gitConfigHelper.configureToken(inputs.gitToken);
core.endGroup(); core.endGroup();
} }
core.startGroup('Checking the base repository state'); core.startGroup('Checking the base repository state');
@ -472,9 +455,10 @@ function createPullRequest(inputs) {
const outputs = new Map(); const outputs = new Map();
outputs.set('pull-request-branch', inputs.branch); outputs.set('pull-request-branch', inputs.branch);
outputs.set('pull-request-operation', 'none'); outputs.set('pull-request-operation', 'none');
outputs.set('pull-request-commits-verified', 'false');
// 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 = yield (0, create_or_update_branch_1.createOrUpdateBranch)(git, inputs.commitMessage, inputs.base, inputs.branch, branchRemoteName, inputs.signoff, inputs.addPaths); const result = yield (0, create_or_update_branch_1.createOrUpdateBranch)(git, inputs.commitMessage, inputs.base, inputs.branch, branchRemoteName, inputs.signoff, inputs.addPaths, inputs.signCommits);
outputs.set('pull-request-head-sha', result.headSha); outputs.set('pull-request-head-sha', result.headSha);
// Set the base. It would have been '' if not specified as an input // Set the base. It would have been '' if not specified as an input
inputs.base = result.base; inputs.base = result.base;
@ -486,7 +470,7 @@ function createPullRequest(inputs) {
// Create signed commits via the GitHub API // Create signed commits via the GitHub API
const stashed = yield git.stashPush(['--include-untracked']); const stashed = yield git.stashPush(['--include-untracked']);
yield git.checkout(inputs.branch); yield git.checkout(inputs.branch);
const pushSignedCommitsResult = yield ghBranch.pushSignedCommits(result.branchCommits, result.baseCommit, repoPath, branchRepository, inputs.branch); const pushSignedCommitsResult = yield ghBranch.pushSignedCommits(result.branchCommits, result.baseSha, repoPath, branchRepository, inputs.branch);
outputs.set('pull-request-head-sha', pushSignedCommitsResult.sha); outputs.set('pull-request-head-sha', pushSignedCommitsResult.sha);
outputs.set('pull-request-commits-verified', pushSignedCommitsResult.verified.toString()); outputs.set('pull-request-commits-verified', pushSignedCommitsResult.verified.toString());
yield git.checkout('-'); yield git.checkout('-');
@ -513,12 +497,9 @@ function createPullRequest(inputs) {
} }
else if (result.action == 'updated') { else if (result.action == 'updated') {
outputs.set('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) {
yield ghPull.convertToDraft(pull.node_id);
}
} }
// Deprecated
core.exportVariable('PULL_REQUEST_NUMBER', pull.number);
core.endGroup(); core.endGroup();
} }
else { else {
@ -538,27 +519,8 @@ function createPullRequest(inputs) {
} }
} }
} }
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 = yield 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 // Set outputs
core.startGroup('Setting outputs');
for (const [key, value] of outputs) { for (const [key, value] of outputs) {
core.info(`${key} = ${value}`); core.info(`${key} = ${value}`);
core.setOutput(key, value); core.setOutput(key, value);
@ -734,20 +696,19 @@ class GitCommandManager {
'show', 'show',
'--raw', '--raw',
'--cc', '--cc',
`--format=%H%n%T%n%P%n%G?%n%s%n%b%n${endOfBody}`, '--diff-filter=AMD',
`--format=%H%n%T%n%P%n%s%n%b%n${endOfBody}`,
ref ref
]); ]);
const lines = output.stdout.split('\n'); const lines = output.stdout.split('\n');
const endOfBodyIndex = lines.lastIndexOf(endOfBody); const endOfBodyIndex = lines.lastIndexOf(endOfBody);
const detailLines = lines.slice(0, endOfBodyIndex); const detailLines = lines.slice(0, endOfBodyIndex);
const unparsedChanges = [];
return { return {
sha: detailLines[0], sha: detailLines[0],
tree: detailLines[1], tree: detailLines[1],
parents: detailLines[2].split(' '), parents: detailLines[2].split(' '),
signed: detailLines[3] !== 'N', subject: detailLines[3],
subject: detailLines[4], body: detailLines.slice(4, endOfBodyIndex).join('\n'),
body: detailLines.slice(5, endOfBodyIndex).join('\n'),
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => { changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
const change = line.match(/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/); const change = line.match(/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/);
if (change) { if (change) {
@ -758,10 +719,9 @@ class GitCommandManager {
}; };
} }
else { else {
unparsedChanges.push(line); throw new Error(`Unexpected line format: ${line}`);
} }
}), })
unparsedChanges: unparsedChanges
}; };
}); });
} }
@ -1242,13 +1202,11 @@ class GitHubHelper {
// Try to create the pull request // Try to create the pull request
try { try {
core.info(`Attempting creation of pull request`); core.info(`Attempting creation of pull request`);
const { data: pull } = yield this.octokit.rest.pulls.create(Object.assign(Object.assign({}, this.parseRepository(baseRepository)), { title: inputs.title, head: headBranch, head_repo: headRepository, base: inputs.base, body: inputs.body, draft: inputs.draft.value, maintainer_can_modify: inputs.maintainerCanModify })); const { data: pull } = yield this.octokit.rest.pulls.create(Object.assign(Object.assign({}, this.parseRepository(baseRepository)), { title: inputs.title, head: headBranch, head_repo: headRepository, base: inputs.base, body: inputs.body, draft: inputs.draft, maintainer_can_modify: inputs.maintainerCanModify }));
core.info(`Created pull request #${pull.number} (${headBranch} => ${inputs.base})`); core.info(`Created pull request #${pull.number} (${headBranch} => ${inputs.base})`);
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
}; };
} }
@ -1275,8 +1233,6 @@ 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
}; };
}); });
@ -1334,40 +1290,32 @@ class GitHubHelper {
return pull; return pull;
}); });
} }
pushSignedCommits(branchCommits, baseCommit, repoPath, branchRepository, branch) { pushSignedCommits(branchCommits, baseSha, repoPath, branchRepository, branch) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
let headCommit = { let headCommit = {
sha: baseCommit.sha, sha: baseSha,
tree: baseCommit.tree,
verified: false verified: false
}; };
for (const commit of branchCommits) { for (const commit of branchCommits) {
headCommit = yield this.createCommit(commit, headCommit, repoPath, branchRepository); headCommit = yield this.createCommit(commit, [headCommit.sha], repoPath, branchRepository);
} }
yield this.createOrUpdateRef(branchRepository, branch, headCommit.sha); yield this.createOrUpdateRef(branchRepository, branch, headCommit.sha);
return headCommit; return headCommit;
}); });
} }
createCommit(commit, parentCommit, repoPath, branchRepository) { createCommit(commit, parents, repoPath, branchRepository) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const repository = this.parseRepository(branchRepository); const repository = this.parseRepository(branchRepository);
// In the case of an empty commit, the tree references the parent's tree let treeSha = commit.tree;
let treeSha = parentCommit.tree;
if (commit.changes.length > 0) { if (commit.changes.length > 0) {
core.info(`Creating tree objects for local commit ${commit.sha}`); core.info(`Creating tree objects for local commit ${commit.sha}`);
const treeObjects = yield Promise.all(commit.changes.map((_a) => __awaiter(this, [_a], void 0, function* ({ path, mode, status }) { const treeObjects = yield Promise.all(commit.changes.map((_a) => __awaiter(this, [_a], void 0, function* ({ path, mode, status }) {
let sha = null; let sha = null;
if (status === 'A' || status === 'M') { if (status === 'A' || status === 'M') {
try { core.info(`Creating blob for file '${path}'`);
const { data: blob } = yield blobCreationLimit(() => this.octokit.rest.git.createBlob(Object.assign(Object.assign({}, repository), { content: utils.readFileBase64([repoPath, path]), encoding: 'base64' }))); const { data: blob } = yield blobCreationLimit(() => this.octokit.rest.git.createBlob(Object.assign(Object.assign({}, repository), { content: utils.readFileBase64([repoPath, path]), encoding: 'base64' })));
sha = blob.sha; 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 { return {
path, path,
mode, mode,
@ -1375,35 +1323,16 @@ class GitHubHelper {
type: 'blob' type: 'blob'
}; };
}))); })));
const chunkSize = 100;
const chunkedTreeObjects = 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}`); core.info(`Creating tree for local commit ${commit.sha}`);
for (let i = 0; i < chunkedTreeObjects.length; i++) { const { data: tree } = yield this.octokit.rest.git.createTree(Object.assign(Object.assign({}, repository), { base_tree: parents[0], tree: treeObjects }));
const { data: tree } = yield this.octokit.rest.git.createTree(Object.assign(Object.assign({}, repository), { base_tree: treeSha, tree: chunkedTreeObjects[i] })); treeSha = tree.sha;
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}`); core.info(`Created tree ${treeSha} for local commit ${commit.sha}`);
} }
const { data: remoteCommit } = yield this.octokit.rest.git.createCommit(Object.assign(Object.assign({}, repository), { parents: [parentCommit.sha], tree: treeSha, message: `${commit.subject}\n\n${commit.body}` })); const { data: remoteCommit } = yield this.octokit.rest.git.createCommit(Object.assign(Object.assign({}, repository), { parents: parents, tree: treeSha, message: `${commit.subject}\n\n${commit.body}` }));
core.info(`Created commit ${remoteCommit.sha} for local commit ${commit.sha}`); core.info(`Created commit ${remoteCommit.sha} for local commit ${commit.sha}`);
core.info(`Commit verified: ${remoteCommit.verification.verified}; reason: ${remoteCommit.verification.reason}`); core.info(`Commit verified: ${remoteCommit.verification.verified}; reason: ${remoteCommit.verification.reason}`);
return { return {
sha: remoteCommit.sha, sha: remoteCommit.sha,
tree: remoteCommit.tree.sha,
verified: remoteCommit.verification.verified
};
});
}
getCommit(sha, branchRepository) {
return __awaiter(this, void 0, void 0, function* () {
const repository = this.parseRepository(branchRepository);
const { data: remoteCommit } = yield this.octokit.rest.git.getCommit(Object.assign(Object.assign({}, repository), { commit_sha: sha }));
return {
sha: remoteCommit.sha,
tree: remoteCommit.tree.sha,
verified: remoteCommit.verification.verified verified: remoteCommit.verification.verified
}; };
}); });
@ -1424,21 +1353,6 @@ class GitHubHelper {
} }
}); });
} }
convertToDraft(id) {
return __awaiter(this, void 0, void 0, function* () {
core.info(`Converting pull request to draft`);
yield this.octokit.graphql({
query: `mutation($pullRequestId: ID!) {
convertPullRequestToDraft(input: {pullRequestId: $pullRequestId}) {
pullRequest {
isDraft
}
}
}`,
pullRequestId: id
});
});
}
} }
exports.GitHubHelper = GitHubHelper; exports.GitHubHelper = GitHubHelper;
@ -1487,20 +1401,12 @@ const core = __importStar(__nccwpck_require__(2186));
const create_pull_request_1 = __nccwpck_require__(3780); const create_pull_request_1 = __nccwpck_require__(3780);
const util_1 = __nccwpck_require__(3837); const util_1 = __nccwpck_require__(3837);
const utils = __importStar(__nccwpck_require__(918)); const utils = __importStar(__nccwpck_require__(918));
function getDraftInput() {
if (core.getInput('draft') === 'always-true') {
return { value: true, always: true };
}
else {
return { value: core.getBooleanInput('draft'), always: false };
}
}
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
const inputs = { const inputs = {
token: core.getInput('token'), token: core.getInput('token'),
branchToken: core.getInput('branch-token'), gitToken: core.getInput('git-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'),
@ -1521,15 +1427,15 @@ function run() {
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: getDraftInput(), draft: core.getBooleanInput('draft'),
maintainerCanModify: core.getBooleanInput('maintainer-can-modify') maintainerCanModify: core.getBooleanInput('maintainer-can-modify')
}; };
core.debug(`Inputs: ${(0, util_1.inspect)(inputs)}`); core.debug(`Inputs: ${(0, util_1.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.branchToken) { if (!inputs.gitToken) {
inputs.branchToken = inputs.token; inputs.gitToken = inputs.token;
} }
if (inputs.bodyPath) { if (inputs.bodyPath) {
if (!utils.fileExistsSync(inputs.bodyPath)) { if (!utils.fileExistsSync(inputs.bodyPath)) {
@ -33535,23 +33441,9 @@ const kWeight = Symbol('kWeight')
const kMaxWeightPerServer = Symbol('kMaxWeightPerServer') const kMaxWeightPerServer = Symbol('kMaxWeightPerServer')
const kErrorPenalty = Symbol('kErrorPenalty') const kErrorPenalty = Symbol('kErrorPenalty')
/**
* Calculate the greatest common divisor of two numbers by
* using the Euclidean algorithm.
*
* @param {number} a
* @param {number} b
* @returns {number}
*/
function getGreatestCommonDivisor (a, b) { function getGreatestCommonDivisor (a, b) {
if (a === 0) return b if (b === 0) return a
return getGreatestCommonDivisor(b, a % b)
while (b !== 0) {
const t = b
b = a % b
a = t
}
return a
} }
function defaultFactory (origin, opts) { function defaultFactory (origin, opts) {
@ -33629,12 +33521,7 @@ class BalancedPool extends PoolBase {
} }
_updateBalancedPoolStats () { _updateBalancedPoolStats () {
let result = 0 this[kGreatestCommonDivisor] = this[kClients].map(p => p[kWeight]).reduce(getGreatestCommonDivisor, 0)
for (let i = 0; i < this[kClients].length; i++) {
result = getGreatestCommonDivisor(this[kClients][i][kWeight], result)
}
this[kGreatestCommonDivisor] = result
} }
removeUpstream (upstream) { removeUpstream (upstream) {
@ -42821,25 +42708,12 @@ const { kState } = __nccwpck_require__(749)
const { webidl } = __nccwpck_require__(4890) const { webidl } = __nccwpck_require__(4890)
const { Blob } = __nccwpck_require__(2254) const { Blob } = __nccwpck_require__(2254)
const assert = __nccwpck_require__(8061) const assert = __nccwpck_require__(8061)
const { isErrored, isDisturbed } = __nccwpck_require__(4492) const { isErrored } = __nccwpck_require__(3983)
const { isArrayBuffer } = __nccwpck_require__(3746) const { isArrayBuffer } = __nccwpck_require__(3746)
const { serializeAMimeType } = __nccwpck_require__(7704) const { serializeAMimeType } = __nccwpck_require__(7704)
const { multipartFormDataParser } = __nccwpck_require__(7991) const { multipartFormDataParser } = __nccwpck_require__(7991)
const textEncoder = new TextEncoder() const textEncoder = new TextEncoder()
function noop () {}
const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0
let streamRegistry
if (hasFinalizationRegistry) {
streamRegistry = new FinalizationRegistry((weakRef) => {
const stream = weakRef.deref()
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
stream.cancel('Response object has been garbage collected').catch(noop)
}
})
}
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
function extractBody (object, keepalive = false) { function extractBody (object, keepalive = false) {
@ -43082,7 +42956,7 @@ function safelyExtractBody (object, keepalive = false) {
return extractBody(object, keepalive) return extractBody(object, keepalive)
} }
function cloneBody (instance, body) { function cloneBody (body) {
// To clone a body body, run these steps: // To clone a body body, run these steps:
// https://fetch.spec.whatwg.org/#concept-body-clone // https://fetch.spec.whatwg.org/#concept-body-clone
@ -43090,10 +42964,6 @@ function cloneBody (instance, body) {
// 1. Let « out1, out2 » be the result of teeing bodys stream. // 1. Let « out1, out2 » be the result of teeing bodys stream.
const [out1, out2] = body.stream.tee() const [out1, out2] = body.stream.tee()
if (hasFinalizationRegistry) {
streamRegistry.register(instance, new WeakRef(out1))
}
// 2. Set bodys stream to out1. // 2. Set bodys stream to out1.
body.stream = out1 body.stream = out1
@ -43236,7 +43106,7 @@ async function consumeBody (object, convertBytesToJSValue, instance) {
// 1. If object is unusable, then return a promise rejected // 1. If object is unusable, then return a promise rejected
// with a TypeError. // with a TypeError.
if (bodyUnusable(object)) { if (bodyUnusable(object[kState].body)) {
throw new TypeError('Body is unusable: Body has already been read') throw new TypeError('Body is unusable: Body has already been read')
} }
@ -43276,9 +43146,7 @@ async function consumeBody (object, convertBytesToJSValue, instance) {
} }
// https://fetch.spec.whatwg.org/#body-unusable // https://fetch.spec.whatwg.org/#body-unusable
function bodyUnusable (object) { function bodyUnusable (body) {
const body = object[kState].body
// An object including the Body interface mixin is // An object including the Body interface mixin is
// said to be unusable if its body is non-null and // said to be unusable if its body is non-null and
// its bodys stream is disturbed or locked. // its bodys stream is disturbed or locked.
@ -43320,10 +43188,7 @@ module.exports = {
extractBody, extractBody,
safelyExtractBody, safelyExtractBody,
cloneBody, cloneBody,
mixinBody, mixinBody
streamRegistry,
hasFinalizationRegistry,
bodyUnusable
} }
@ -48133,7 +47998,7 @@ module.exports = {
const { extractBody, mixinBody, cloneBody, bodyUnusable } = __nccwpck_require__(6682) const { extractBody, mixinBody, cloneBody } = __nccwpck_require__(6682)
const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = __nccwpck_require__(2991) const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = __nccwpck_require__(2991)
const { FinalizationRegistry } = __nccwpck_require__(1922)() const { FinalizationRegistry } = __nccwpck_require__(1922)()
const util = __nccwpck_require__(3983) const util = __nccwpck_require__(3983)
@ -48688,7 +48553,7 @@ class Request {
// 40. If initBody is null and inputBody is non-null, then: // 40. If initBody is null and inputBody is non-null, then:
if (initBody == null && inputBody != null) { if (initBody == null && inputBody != null) {
// 1. If input is unusable, then throw a TypeError. // 1. If input is unusable, then throw a TypeError.
if (bodyUnusable(input)) { if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) {
throw new TypeError( throw new TypeError(
'Cannot construct a Request with a Request object that has already been used.' 'Cannot construct a Request with a Request object that has already been used.'
) )
@ -48890,7 +48755,7 @@ class Request {
webidl.brandCheck(this, Request) webidl.brandCheck(this, Request)
// 1. If this is unusable, then throw a TypeError. // 1. If this is unusable, then throw a TypeError.
if (bodyUnusable(this)) { if (this.bodyUsed || this.body?.locked) {
throw new TypeError('unusable') throw new TypeError('unusable')
} }
@ -49008,7 +48873,7 @@ function cloneRequest (request) {
// 2. If requests body is non-null, set newRequests body to the // 2. If requests body is non-null, set newRequests body to the
// result of cloning requests body. // result of cloning requests body.
if (request.body != null) { if (request.body != null) {
newRequest.body = cloneBody(newRequest, request.body) newRequest.body = cloneBody(request.body)
} }
// 3. Return newRequest. // 3. Return newRequest.
@ -49176,7 +49041,7 @@ module.exports = { Request, makeRequest, fromInnerRequest, cloneRequest }
const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = __nccwpck_require__(2991) const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = __nccwpck_require__(2991)
const { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = __nccwpck_require__(6682) const { extractBody, cloneBody, mixinBody } = __nccwpck_require__(6682)
const util = __nccwpck_require__(3983) const util = __nccwpck_require__(3983)
const nodeUtil = __nccwpck_require__(7261) const nodeUtil = __nccwpck_require__(7261)
const { kEnumerableProperty } = util const { kEnumerableProperty } = util
@ -49201,9 +49066,24 @@ const { URLSerializer } = __nccwpck_require__(7704)
const { kConstruct } = __nccwpck_require__(2785) const { kConstruct } = __nccwpck_require__(2785)
const assert = __nccwpck_require__(8061) const assert = __nccwpck_require__(8061)
const { types } = __nccwpck_require__(7261) const { types } = __nccwpck_require__(7261)
const { isDisturbed, isErrored } = __nccwpck_require__(4492)
const textEncoder = new TextEncoder('utf-8') const textEncoder = new TextEncoder('utf-8')
const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0
let registry
if (hasFinalizationRegistry) {
registry = new FinalizationRegistry((weakRef) => {
const stream = weakRef.deref()
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
stream.cancel('Response object has been garbage collected').catch(noop)
}
})
}
function noop () {}
// https://fetch.spec.whatwg.org/#response-class // https://fetch.spec.whatwg.org/#response-class
class Response { class Response {
// Creates network error Response. // Creates network error Response.
@ -49404,7 +49284,7 @@ class Response {
webidl.brandCheck(this, Response) webidl.brandCheck(this, Response)
// 1. If this is unusable, then throw a TypeError. // 1. If this is unusable, then throw a TypeError.
if (bodyUnusable(this)) { if (this.bodyUsed || this.body?.locked) {
throw webidl.errors.exception({ throw webidl.errors.exception({
header: 'Response.clone', header: 'Response.clone',
message: 'Body has already been consumed.' message: 'Body has already been consumed.'
@ -49487,7 +49367,7 @@ function cloneResponse (response) {
// 3. If responses body is non-null, then set newResponses body to the // 3. If responses body is non-null, then set newResponses body to the
// result of cloning responses body. // result of cloning responses body.
if (response.body != null) { if (response.body != null) {
newResponse.body = cloneBody(newResponse, response.body) newResponse.body = cloneBody(response.body)
} }
// 4. Return newResponse. // 4. Return newResponse.
@ -49692,7 +49572,7 @@ function fromInnerResponse (innerResponse, guard) {
// a primitive or an object, even undefined. If the held value is an object, the registry keeps // a primitive or an object, even undefined. If the held value is an object, the registry keeps
// a strong reference to it (so it can pass it to the cleanup callback later). Reworded from // a strong reference to it (so it can pass it to the cleanup callback later). Reworded from
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
streamRegistry.register(response, new WeakRef(innerResponse.body.stream)) registry.register(response, new WeakRef(innerResponse.body.stream))
} }
return response return response

View File

@ -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@v7 uses: peter-evans/create-pull-request@v6
env: env:
HUSKY: '0' HUSKY: '0'
``` ```

View File

@ -15,9 +15,7 @@ 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)
- [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 signing](#commit-signing)
- [Commit signature verification for bots](#commit-signature-verification-for-bots) - [Commit signature verification for bots](#commit-signature-verification-for-bots)
- [GPG commit signature verification](#gpg-commit-signature-verification) - [GPG commit signature verification](#gpg-commit-signature-verification)
@ -92,7 +90,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@v7 - uses: peter-evans/create-pull-request@v6
with: with:
base: ${{ github.head_ref }} base: ${{ github.head_ref }}
``` ```
@ -100,7 +98,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@v7 - uses: peter-evans/create-pull-request@v6
with: with:
base: main base: main
``` ```
@ -150,15 +148,13 @@ 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).
- 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 `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.
- 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 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. - 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.
### Security ### Security
@ -176,7 +172,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, the `GITHUB_TOKEN` will not work and one of the other [token options](../README.md#token) must be used. 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.
```yml ```yml
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -186,19 +182,16 @@ 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@v7 - uses: peter-evans/create-pull-request@v6
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 Classic [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 `repo` scoped [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:
@ -216,7 +209,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@v7 uses: peter-evans/create-pull-request@v6
``` ```
### Push pull request branches to a fork ### Push pull request branches to a fork
@ -225,13 +218,11 @@ 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 Classic [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with `repo` scope. 3. Create a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
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.
@ -241,60 +232,19 @@ It will use their own fork to push code and create the pull request.
# Make changes to pull request here # Make changes to pull request here
- uses: peter-evans/create-pull-request@v7 - uses: peter-evans/create-pull-request@v6
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
``` ```
> [!TIP] Note: You can also combine `push-to-fork` with [creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository).
> 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.
GitHub App generated tokens can be configured with fine-grained permissions and are scoped to only repositories where the App is installed. 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 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:
@ -303,14 +253,12 @@ GitHub App generated tokens can be configured with fine-grained permissions and
- 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 repositories that the action will require access to in order to create pull requests. 3. Install the App on any repository where workflows will run requiring tokens.
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`.
@ -318,54 +266,25 @@ GitHub App generated tokens can be configured with fine-grained permissions and
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v4
- uses: actions/create-github-app-token@v1 - uses: actions/create-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@v7 uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.generate-token.outputs.token }}
```
#### 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: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
``` ```
### Commit signing ### 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. [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.
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). 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).
@ -375,8 +294,7 @@ The action can sign commits as `github-actions[bot]` when using the repository's
> [!IMPORTANT] > [!IMPORTANT]
> - When setting `sign-commits: true` the action will ignore the `committer` and `author` inputs. > - 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. > - 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]`. 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 ```yaml
@ -386,7 +304,7 @@ In this example the `token` input is not supplied, so the action will use the re
# 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@v7 uses: peter-evans/create-pull-request@v6
with: with:
sign-commits: true sign-commits: true
``` ```
@ -405,7 +323,7 @@ In this example, the `token` input is generated using a GitHub App. This will si
# 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@v7 uses: peter-evans/create-pull-request@v6
with: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
sign-commits: true sign-commits: true
@ -431,8 +349,7 @@ 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.
> [!IMPORTANT] Note that the `committer` email address *MUST* match the email address used to create your GPG key.
> The `committer` email address *MUST* match the email address used to create your GPG key.
```yaml ```yaml
steps: steps:
@ -448,7 +365,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@v7 uses: peter-evans/create-pull-request@v6
with: with:
token: ${{ secrets.PAT }} token: ${{ secrets.PAT }}
committer: example <email@example.com> committer: example <email@example.com>
@ -478,7 +395,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@v7 uses: peter-evans/create-pull-request@v6
``` ```
**Ubuntu container example:** **Ubuntu container example:**
@ -501,5 +418,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@v7 uses: peter-evans/create-pull-request@v6
``` ```

View File

@ -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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
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@v7 uses: peter-evans/create-pull-request@v6
with: with:
body: ${{ steps.template.outputs.result }} body: ${{ steps.template.outputs.result }}
``` ```

View File

@ -1,19 +1,3 @@
## 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

102
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "create-pull-request", "name": "create-pull-request",
"version": "7.0.0", "version": "6.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "create-pull-request", "name": "create-pull-request",
"version": "7.0.0", "version": "6.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
@ -17,17 +17,17 @@
"@octokit/plugin-throttling": "^9.3.1", "@octokit/plugin-throttling": "^9.3.1",
"p-limit": "^6.1.0", "p-limit": "^6.1.0",
"proxy-from-env": "^1.1.0", "proxy-from-env": "^1.1.0",
"undici": "^6.19.8", "undici": "^6.19.7",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^18.19.46", "@types/node": "^18.19.44",
"@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^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.1",
"eslint-plugin-github": "^4.10.2", "eslint-plugin-github": "^4.10.2",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^27.9.0", "eslint-plugin-jest": "^27.9.0",
@ -37,7 +37,7 @@
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
"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.4",
"typescript": "^5.5.4" "typescript": "^5.5.4"
} }
}, },
@ -1261,15 +1261,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@nolyfill/is-core-module": {
"version": "1.0.39",
"resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
"integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
"dev": true,
"engines": {
"node": ">=12.4.0"
}
},
"node_modules/@octokit/auth-token": { "node_modules/@octokit/auth-token": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz",
@ -1554,9 +1545,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.19.46", "version": "18.19.44",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.46.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.44.tgz",
"integrity": "sha512-vnRgMS7W6cKa1/0G3/DTtQYpVrZ8c0Xm6UkLaVFrb9jtcVC3okokW09Ki1Qdrj9ISokszD69nY4WDLRlvHlhAA==", "integrity": "sha512-ZsbGerYg72WMXUIE9fYxtvfzLEuq6q8mKERdWFnqTmOvudMxnz+CBNRoOwJ2kNpFOncrKjT1hZwxjlFgQ9qvQA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -3011,9 +3002,9 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.6", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
@ -3582,18 +3573,17 @@
} }
}, },
"node_modules/eslint-import-resolver-typescript": { "node_modules/eslint-import-resolver-typescript": {
"version": "3.6.3", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz", "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz",
"integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==", "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@nolyfill/is-core-module": "1.0.39", "debug": "^4.3.4",
"debug": "^4.3.5", "enhanced-resolve": "^5.12.0",
"enhanced-resolve": "^5.15.0", "eslint-module-utils": "^2.7.4",
"eslint-module-utils": "^2.8.1", "fast-glob": "^3.3.1",
"fast-glob": "^3.3.2", "get-tsconfig": "^4.5.0",
"get-tsconfig": "^4.7.5", "is-core-module": "^2.11.0",
"is-bun-module": "^1.0.2",
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
}, },
"engines": { "engines": {
@ -3604,22 +3594,13 @@
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "*", "eslint": "*",
"eslint-plugin-import": "*", "eslint-plugin-import": "*"
"eslint-plugin-import-x": "*"
},
"peerDependenciesMeta": {
"eslint-plugin-import": {
"optional": true
},
"eslint-plugin-import-x": {
"optional": true
}
} }
}, },
"node_modules/eslint-module-utils": { "node_modules/eslint-module-utils": {
"version": "2.8.2", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.2.tgz", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
"integrity": "sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==", "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"debug": "^3.2.7" "debug": "^3.2.7"
@ -4804,15 +4785,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-bun-module": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.1.0.tgz",
"integrity": "sha512-4mTAVPlrXpaN3jtF0lsnPCMGnq4+qZjVIKq0HCpfcqf8OC1SM5oATCIAPM5V5FN05qp2NNnFndphmdZS9CV3hA==",
"dev": true,
"dependencies": {
"semver": "^7.6.3"
}
},
"node_modules/is-callable": { "node_modules/is-callable": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@ -7527,20 +7499,20 @@
} }
}, },
"node_modules/ts-jest": { "node_modules/ts-jest": {
"version": "29.2.5", "version": "29.2.4",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz",
"integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"bs-logger": "^0.2.6", "bs-logger": "0.x",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"fast-json-stable-stringify": "^2.1.0", "fast-json-stable-stringify": "2.x",
"jest-util": "^29.0.0", "jest-util": "^29.0.0",
"json5": "^2.2.3", "json5": "^2.2.3",
"lodash.memoize": "^4.1.2", "lodash.memoize": "4.x",
"make-error": "^1.3.6", "make-error": "1.x",
"semver": "^7.6.3", "semver": "^7.5.3",
"yargs-parser": "^21.1.1" "yargs-parser": "^21.0.1"
}, },
"bin": { "bin": {
"ts-jest": "cli.js" "ts-jest": "cli.js"
@ -7777,9 +7749,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.19.8", "version": "6.19.7",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz",
"integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "create-pull-request", "name": "create-pull-request",
"version": "7.0.0", "version": "6.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",
@ -37,17 +37,17 @@
"@octokit/plugin-throttling": "^9.3.1", "@octokit/plugin-throttling": "^9.3.1",
"p-limit": "^6.1.0", "p-limit": "^6.1.0",
"proxy-from-env": "^1.1.0", "proxy-from-env": "^1.1.0",
"undici": "^6.19.8", "undici": "^6.19.7",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^18.19.46", "@types/node": "^18.19.44",
"@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^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.1",
"eslint-plugin-github": "^4.10.2", "eslint-plugin-github": "^4.10.2",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^27.9.0", "eslint-plugin-jest": "^27.9.0",
@ -57,7 +57,7 @@
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
"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.4",
"typescript": "^5.5.4" "typescript": "^5.5.4"
} }
} }

View File

@ -61,9 +61,6 @@ export async function buildBranchCommits(
for (const sha of shas) { for (const sha of shas) {
const commit = await git.getCommit(sha) const commit = await git.getCommit(sha)
commits.push(commit) commits.push(commit)
for (const unparsedChange of commit.unparsedChanges) {
core.warning(`Skipping unexpected diff entry: ${unparsedChange}`)
}
} }
return commits return commits
} }
@ -124,22 +121,6 @@ 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')
@ -151,7 +132,7 @@ interface CreateOrUpdateBranchResult {
action: string action: string
base: string base: string
hasDiffWithBase: boolean hasDiffWithBase: boolean
baseCommit: Commit baseSha: string
headSha: string headSha: string
branchCommits: Commit[] branchCommits: Commit[]
} }
@ -163,7 +144,8 @@ export async function createOrUpdateBranch(
branch: string, branch: string,
branchRemoteName: string, branchRemoteName: string,
signoff: boolean, signoff: boolean,
addPaths: string[] addPaths: string[],
signCommits: boolean = false
): Promise<CreateOrUpdateBranchResult> { ): Promise<CreateOrUpdateBranchResult> {
// Get the working base. // Get the working base.
// When a ref, it may or may not be the actual base. // When a ref, it may or may not be the actual base.
@ -178,6 +160,16 @@ 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,
baseSha: '',
headSha: '',
branchCommits: []
}
// 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')
@ -257,9 +249,6 @@ 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
@ -267,9 +256,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
hasDiffWithBase = await isAhead(git, base, branch) result.hasDiffWithBase = await isAhead(git, base, branch)
if (hasDiffWithBase) { if (result.hasDiffWithBase) {
action = 'created' result.action = 'created'
core.info(`Created branch '${branch}'`) core.info(`Created branch '${branch}'`)
} else { } else {
core.info( core.info(
@ -286,26 +275,20 @@ 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 diff of the commits ahead of the base branch differs between the branch and // - If the number of commits ahead of the base branch differs between the branch and
// temp branch. This catches a case where changes have been partially merged to the // temp branch. This catches a case where the base branch has been force pushed to
// base. The overall diff is the same, but the branch needs to be rebased to show // a new commit.
// 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
@ -316,28 +299,27 @@ 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))) {
action = 'updated' result.action = 'updated'
core.info(`Updated branch '${branch}'`) core.info(`Updated branch '${branch}'`)
} else { } else {
action = 'not-updated' result.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
hasDiffWithBase = await isAhead(git, base, branch) result.hasDiffWithBase = await isAhead(git, base, branch)
} }
// Get the base and head SHAs // Get the base and head SHAs
const baseSha = await git.revParse(base) result.baseSha = await git.revParse(base)
const baseCommit = await git.getCommit(baseSha) result.headSha = await git.revParse(branch)
const headSha = await git.revParse(branch)
let branchCommits: Commit[] = [] // NOTE: This could always be built and returned. Maybe remove when there is confidence in buildBranchCommits.
if (hasDiffWithBase) { if (signCommits) {
// Build the branch commits // Build the branch commits
branchCommits = await buildBranchCommits(git, base, branch) result.branchCommits = await buildBranchCommits(git, base, branch)
} }
// Delete the temporary branch // Delete the temporary branch
@ -351,12 +333,5 @@ export async function createOrUpdateBranch(
await git.stashPop() await git.stashPop()
} }
return { return result
action: action,
base: base,
hasDiffWithBase: hasDiffWithBase,
baseCommit: baseCommit,
headSha: headSha,
branchCommits: branchCommits
}
} }

View File

@ -11,7 +11,7 @@ import * as utils from './utils'
export interface Inputs { export interface Inputs {
token: string token: string
branchToken: string gitToken: string
path: string path: string
addPaths: string[] addPaths: string[]
commitMessage: string commitMessage: string
@ -32,10 +32,7 @@ export interface Inputs {
reviewers: string[] reviewers: string[]
teamReviewers: string[] teamReviewers: string[]
milestone: number milestone: number
draft: { draft: boolean
value: boolean
always: boolean
}
maintainerCanModify: boolean maintainerCanModify: boolean
} }
@ -51,7 +48,7 @@ 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 clients // Init the GitHub clients
const ghBranch = new GitHubHelper(baseRemote.hostname, inputs.branchToken) const ghBranch = new GitHubHelper(baseRemote.hostname, inputs.gitToken)
const ghPull = new GitHubHelper(baseRemote.hostname, inputs.token) 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'
@ -97,7 +94,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.branchToken) await gitConfigHelper.configureToken(inputs.gitToken)
core.endGroup() core.endGroup()
} }
@ -184,6 +181,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
const outputs = new Map<string, string>() const outputs = new Map<string, string>()
outputs.set('pull-request-branch', inputs.branch) outputs.set('pull-request-branch', inputs.branch)
outputs.set('pull-request-operation', 'none') outputs.set('pull-request-operation', 'none')
outputs.set('pull-request-commits-verified', 'false')
// 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')
@ -194,7 +192,8 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
inputs.branch, inputs.branch,
branchRemoteName, branchRemoteName,
inputs.signoff, inputs.signoff,
inputs.addPaths inputs.addPaths,
inputs.signCommits
) )
outputs.set('pull-request-head-sha', result.headSha) outputs.set('pull-request-head-sha', result.headSha)
// Set the base. It would have been '' if not specified as an input // Set the base. It would have been '' if not specified as an input
@ -212,7 +211,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
await git.checkout(inputs.branch) await git.checkout(inputs.branch)
const pushSignedCommitsResult = await ghBranch.pushSignedCommits( const pushSignedCommitsResult = await ghBranch.pushSignedCommits(
result.branchCommits, result.branchCommits,
result.baseCommit, result.baseSha,
repoPath, repoPath,
branchRepository, branchRepository,
inputs.branch inputs.branch
@ -249,12 +248,9 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
outputs.set('pull-request-operation', 'created') outputs.set('pull-request-operation', 'created')
} else if (result.action == 'updated') { } else if (result.action == 'updated') {
outputs.set('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)
}
} }
// 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
@ -276,35 +272,8 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
} }
} }
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 // Set outputs
core.startGroup('Setting outputs')
for (const [key, value] of outputs) { for (const [key, value] of outputs) {
core.info(`${key} = ${value}`) core.info(`${key} = ${value}`)
core.setOutput(key, value) core.setOutput(key, value)

View File

@ -9,7 +9,6 @@ export type Commit = {
sha: string sha: string
tree: string tree: string
parents: string[] parents: string[]
signed: boolean
subject: string subject: string
body: string body: string
changes: { changes: {
@ -17,7 +16,6 @@ export type Commit = {
status: 'A' | 'M' | 'D' status: 'A' | 'M' | 'D'
path: string path: string
}[] }[]
unparsedChanges: string[]
} }
export class GitCommandManager { export class GitCommandManager {
@ -159,21 +157,20 @@ export class GitCommandManager {
'show', 'show',
'--raw', '--raw',
'--cc', '--cc',
`--format=%H%n%T%n%P%n%G?%n%s%n%b%n${endOfBody}`, '--diff-filter=AMD',
`--format=%H%n%T%n%P%n%s%n%b%n${endOfBody}`,
ref ref
]) ])
const lines = output.stdout.split('\n') const lines = output.stdout.split('\n')
const endOfBodyIndex = lines.lastIndexOf(endOfBody) const endOfBodyIndex = lines.lastIndexOf(endOfBody)
const detailLines = lines.slice(0, endOfBodyIndex) const detailLines = lines.slice(0, endOfBodyIndex)
const unparsedChanges: string[] = []
return <Commit>{ return <Commit>{
sha: detailLines[0], sha: detailLines[0],
tree: detailLines[1], tree: detailLines[1],
parents: detailLines[2].split(' '), parents: detailLines[2].split(' '),
signed: detailLines[3] !== 'N', subject: detailLines[3],
subject: detailLines[4], body: detailLines.slice(4, endOfBodyIndex).join('\n'),
body: detailLines.slice(5, endOfBodyIndex).join('\n'),
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => { changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
const change = line.match( const change = line.match(
/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/ /^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/
@ -185,10 +182,9 @@ export class GitCommandManager {
path: change[4] path: change[4]
} }
} else { } else {
unparsedChanges.push(line) throw new Error(`Unexpected line format: ${line}`)
} }
}), })
unparsedChanges: unparsedChanges
} }
} }

View File

@ -20,14 +20,11 @@ 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 { interface CommitResponse {
sha: string sha: string
tree: string
verified: boolean verified: boolean
} }
@ -81,7 +78,7 @@ export class GitHubHelper {
head_repo: headRepository, head_repo: headRepository,
base: inputs.base, base: inputs.base,
body: inputs.body, body: inputs.body,
draft: inputs.draft.value, draft: inputs.draft,
maintainer_can_modify: inputs.maintainerCanModify maintainer_can_modify: inputs.maintainerCanModify
}) })
core.info( core.info(
@ -90,8 +87,6 @@ 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) {
@ -132,8 +127,6 @@ 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
} }
} }
@ -221,20 +214,19 @@ export class GitHubHelper {
async pushSignedCommits( async pushSignedCommits(
branchCommits: Commit[], branchCommits: Commit[],
baseCommit: Commit, baseSha: string,
repoPath: string, repoPath: string,
branchRepository: string, branchRepository: string,
branch: string branch: string
): Promise<CommitResponse> { ): Promise<CommitResponse> {
let headCommit: CommitResponse = { let headCommit: CommitResponse = {
sha: baseCommit.sha, sha: baseSha,
tree: baseCommit.tree,
verified: false verified: false
} }
for (const commit of branchCommits) { for (const commit of branchCommits) {
headCommit = await this.createCommit( headCommit = await this.createCommit(
commit, commit,
headCommit, [headCommit.sha],
repoPath, repoPath,
branchRepository branchRepository
) )
@ -245,36 +237,28 @@ export class GitHubHelper {
private async createCommit( private async createCommit(
commit: Commit, commit: Commit,
parentCommit: CommitResponse, parents: string[],
repoPath: string, repoPath: string,
branchRepository: string branchRepository: string
): Promise<CommitResponse> { ): Promise<CommitResponse> {
const repository = this.parseRepository(branchRepository) const repository = this.parseRepository(branchRepository)
// In the case of an empty commit, the tree references the parent's tree let treeSha = commit.tree
let treeSha = parentCommit.tree
if (commit.changes.length > 0) { if (commit.changes.length > 0) {
core.info(`Creating tree objects for local commit ${commit.sha}`) core.info(`Creating tree objects for local commit ${commit.sha}`)
const treeObjects = await Promise.all( const treeObjects = await Promise.all(
commit.changes.map(async ({path, mode, status}) => { commit.changes.map(async ({path, mode, status}) => {
let sha: string | null = null let sha: string | null = null
if (status === 'A' || status === 'M') { if (status === 'A' || status === 'M') {
try { core.info(`Creating blob for file '${path}'`)
const {data: blob} = await blobCreationLimit(() => const {data: blob} = await blobCreationLimit(() =>
this.octokit.rest.git.createBlob({ this.octokit.rest.git.createBlob({
...repository, ...repository,
content: utils.readFileBase64([repoPath, path]), content: utils.readFileBase64([repoPath, path]),
encoding: 'base64' encoding: 'base64'
}) })
) )
sha = blob.sha 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>{ return <TreeObject>{
path, path,
mode, mode,
@ -283,33 +267,19 @@ export class GitHubHelper {
} }
}) })
) )
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}`) 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({
const {data: tree} = await this.octokit.rest.git.createTree({ ...repository,
...repository, base_tree: parents[0],
base_tree: treeSha, tree: treeObjects
tree: chunkedTreeObjects[i] })
}) treeSha = tree.sha
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}`) core.info(`Created tree ${treeSha} for local commit ${commit.sha}`)
} }
const {data: remoteCommit} = await this.octokit.rest.git.createCommit({ const {data: remoteCommit} = await this.octokit.rest.git.createCommit({
...repository, ...repository,
parents: [parentCommit.sha], parents: parents,
tree: treeSha, tree: treeSha,
message: `${commit.subject}\n\n${commit.body}` message: `${commit.subject}\n\n${commit.body}`
}) })
@ -321,23 +291,6 @@ export class GitHubHelper {
) )
return { return {
sha: remoteCommit.sha, 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 verified: remoteCommit.verification.verified
} }
} }
@ -375,18 +328,4 @@ export class GitHubHelper {
}) })
} }
} }
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
})
}
} }

View File

@ -3,19 +3,11 @@ 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'),
branchToken: core.getInput('branch-token'), gitToken: core.getInput('git-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'),
@ -36,7 +28,7 @@ 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: getDraftInput(), draft: core.getBooleanInput('draft'),
maintainerCanModify: core.getBooleanInput('maintainer-can-modify') maintainerCanModify: core.getBooleanInput('maintainer-can-modify')
} }
core.debug(`Inputs: ${inspect(inputs)}`) core.debug(`Inputs: ${inspect(inputs)}`)
@ -44,8 +36,8 @@ async function run(): Promise<void> {
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.branchToken) { if (!inputs.gitToken) {
inputs.branchToken = inputs.token inputs.gitToken = inputs.token
} }
if (inputs.bodyPath) { if (inputs.bodyPath) {
if (!utils.fileExistsSync(inputs.bodyPath)) { if (!utils.fileExistsSync(inputs.bodyPath)) {