Compare commits
155 Commits
Author | SHA1 | Date | |
---|---|---|---|
18f7dc018c | |||
89265e8d24 | |||
a7bb76508d | |||
357cebe268 | |||
f22a7da129 | |||
3f60247108 | |||
dcd5fd746d | |||
4b53b6fd1a | |||
10a1849302 | |||
c209d72428 | |||
ef9e028216 | |||
f8f85df783 | |||
b9117f2e0c | |||
598ffcd35c | |||
d8e8e547cc | |||
507420e035 | |||
67df31e08a | |||
f530141cd3 | |||
a5a72ba246 | |||
a6c8b3814a | |||
c0a9598b0e | |||
3c3d696d5b | |||
bd0f84d69c | |||
ad71e1f128 | |||
368c9da6f6 | |||
d8a389d1fa | |||
225cf628aa | |||
d7a8d0affc | |||
2dc79e58de | |||
95767e7d51 | |||
3263596ac4 | |||
3e21ec0c82 | |||
5050a372c9 | |||
7380612b49 | |||
771ad1b5f4 | |||
093c191148 | |||
00cb0abb4d | |||
fa0950476f | |||
b90b9c1e20 | |||
028a63020c | |||
9d59234a82 | |||
210f7aab2c | |||
6bb7394339 | |||
a518698c07 | |||
8be395fdd3 | |||
36d063872e | |||
9825ae65b1 | |||
243251cf92 | |||
28beef91aa | |||
01f7dd1d28 | |||
32c71c837c | |||
a6d621d73e | |||
09f51e6391 | |||
ed04db61de | |||
ea6b55fc9d | |||
88896f707d | |||
423630f7c0 | |||
66cc0cc1e2 | |||
2a63057d1e | |||
f83efdc34a | |||
fff683e9ca | |||
9f83247b25 | |||
670e508eb6 | |||
d9d6fd980e | |||
8bb8511e4d | |||
c1d92ef456 | |||
1ff93da091 | |||
0524c01297 | |||
548adff9dc | |||
28674474a4 | |||
4fb90330a4 | |||
e4c811acf5 | |||
99ccb3479b | |||
13616a4432 | |||
b5b91bc2b0 | |||
5666cd8fe9 | |||
ad897490d5 | |||
0735106af9 | |||
9aeedaa8c2 | |||
52d31873b6 | |||
09b9ac155b | |||
6ec5e3e26b | |||
8b46437b6d | |||
e361fd1788 | |||
052fc72b41 | |||
ed00d4629c | |||
34371f09e5 | |||
c27ea51ae0 | |||
5e9d0ee9ea | |||
b5f41d9b08 | |||
2455e15969 | |||
05bc46786e | |||
adc6552966 | |||
171fc6cce4 | |||
3fb765f674 | |||
d95c81ee98 | |||
8d5ed6557f | |||
7b1819c092 | |||
be0a8c9666 | |||
a0a6157bf1 | |||
9c5ec2e07d | |||
45c510e1f6 | |||
249b80db6b | |||
6c2b44c6ac | |||
76c58cf6a9 | |||
8c603dbb04 | |||
d01e0807ef | |||
ce699aa2d1 | |||
9984f611a7 | |||
ff0beed1b2 | |||
ddeca94037 | |||
0fd77ba8cc | |||
c7f493a800 | |||
91664dfb28 | |||
13ec5274b1 | |||
bcf9790963 | |||
88ea447de7 | |||
da928d5fcc | |||
2465e435b9 | |||
37b2bd1eca | |||
eb13e17e17 | |||
a1ecc20658 | |||
ffcad23634 | |||
f4b52b768a | |||
af682c8fcb | |||
7378b23cb0 | |||
370ae6d537 | |||
ae0797ee12 | |||
e05457394a | |||
44f76dd5b3 | |||
279e66ed27 | |||
ce9dd3641e | |||
1a00b34382 | |||
e17bb55cb7 | |||
1890e1ec35 | |||
a49ee3308e | |||
16fa12ee5f | |||
5ea31358e9 | |||
105f0d3816 | |||
8fb2374109 | |||
a68328a1ee | |||
9bf4b302a5 | |||
da6a868b86 | |||
cbd5e97fef | |||
095c53659b | |||
fc687f898b | |||
3b32bfa4f7 | |||
9c3bf74d10 | |||
b9ed02107c | |||
2f31350598 | |||
d14206169d | |||
82c423c73b | |||
7cf22579c6 | |||
5893420513 | |||
8bdec9b230 |
@ -9,11 +9,15 @@
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/@typescript-eslint"
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/camelcase": "off"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -1,12 +1,12 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'docs/**'
|
||||
@ -45,7 +45,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
ref: main
|
||||
- if: matrix.target == 'built' || github.event_name == 'pull_request'
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
@ -108,7 +108,7 @@ jobs:
|
||||
```
|
||||
|
||||
package:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -120,10 +120,10 @@ jobs:
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
commit-message: Update distribution
|
||||
commit-message: 'build: update distribution'
|
||||
title: Update distribution
|
||||
body: |
|
||||
- Updates the distribution for changes on `master`
|
||||
- Updates the distribution for changes on `main`
|
||||
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
|
2
.github/workflows/cpr-example-command.yml
vendored
2
.github/workflows/cpr-example-command.yml
vendored
@ -34,10 +34,12 @@ jobs:
|
||||
milestone: 1
|
||||
draft: false
|
||||
branch: example-patches
|
||||
delete-branch: true
|
||||
|
||||
- name: Check output
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
|
||||
- name: Add reaction
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
|
6
.github/workflows/slash-command-dispatch.yml
vendored
6
.github/workflows/slash-command-dispatch.yml
vendored
@ -18,12 +18,6 @@ jobs:
|
||||
"repository": "peter-evans/create-pull-request-tests",
|
||||
"named_args": true
|
||||
},
|
||||
{
|
||||
"command": "pytest",
|
||||
"permission": "admin",
|
||||
"repository": "peter-evans/create-pull-request-tests",
|
||||
"named_args": true
|
||||
},
|
||||
{
|
||||
"command": "clean",
|
||||
"permission": "admin",
|
||||
|
31
.github/workflows/update-dep.yml
vendored
31
.github/workflows/update-dep.yml
vendored
@ -1,31 +0,0 @@
|
||||
name: Update Dependencies
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * 4'
|
||||
jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- name: Update dependencies
|
||||
run: |
|
||||
npx -p npm-check-updates ncu -u
|
||||
npm install
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
commit-message: Update dependencies
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: actions-bot <actions-bot@users.noreply.github.com>
|
||||
title: Update dependencies
|
||||
body: |
|
||||
- Dependency updates
|
||||
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
branch: update-dependencies
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ lib/
|
||||
node_modules/
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
|
104
README.md
104
README.md
@ -1,6 +1,6 @@
|
||||
# <img width="24" height="24" src="docs/assets/logo.svg"> Create Pull Request
|
||||
[](https://github.com/peter-evans/create-pull-request/actions?query=workflow%3ACI)
|
||||
[](https://github.com/marketplace/actions/create-pull-request)
|
||||
[](https://github.com/marketplace/actions/create-pull-request)
|
||||
|
||||
A GitHub action to create a pull request for changes to your repository in the actions workspace.
|
||||
|
||||
@ -26,6 +26,10 @@ Create Pull Request action will:
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
```
|
||||
@ -40,13 +44,15 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
||||
|
||||
| Name | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `token` | `GITHUB_TOKEN` or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
|
||||
| `token` | `GITHUB_TOKEN` (`contents: write`, `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` |
|
||||
| `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. Defaults to adding all new and modified files. See [Add specific paths](#add-specific-paths). | `-A` |
|
||||
| `commit-message` | The message to use when committing changes. | `[create-pull-request] automated change` |
|
||||
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the GitHub Actions bot user. | `GitHub <noreply@github.com>` |
|
||||
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>` |
|
||||
| `signoff` | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line by the committer at the end of the commit log message. | `false` |
|
||||
| `branch` | The pull request branch name. | `create-pull-request/patch` |
|
||||
| `delete-branch` | Delete the `branch` when closing pull requests, and when undeleted after merging. Recommend `true`. | `false` |
|
||||
| `branch-suffix` | The branch suffix type when using the alternative branching strategy. Valid values are `random`, `timestamp` and `short-commit-hash`. See [Alternative strategy](#alternative-strategy---always-create-a-new-pull-request-branch) for details. | |
|
||||
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
|
||||
| `push-to-fork` | A fork of the checked-out parent repository to which the pull request branch will be pushed. e.g. `owner/repo-fork`. The pull request will be created to merge the fork's branch into the parent's base. See [push pull request branches to a fork](docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork) for details. | |
|
||||
@ -55,14 +61,29 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
||||
| `labels` | A comma or newline-separated list of labels. | |
|
||||
| `assignees` | A comma or newline-separated list of assignees (GitHub usernames). | |
|
||||
| `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) may be required. See [this issue](https://github.com/peter-evans/create-pull-request/issues/155). | |
|
||||
| `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) may be required. See [this issue](https://github.com/peter-evans/create-pull-request/issues/155). If using a GitHub App, refer to [Authenticating with GitHub App generated tokens](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens) for the proper permissions. | |
|
||||
| `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). | `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` |
|
||||
|
||||
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
env:
|
||||
https_proxy: http://<proxy_address>:<port>
|
||||
```
|
||||
|
||||
### Action outputs
|
||||
|
||||
The pull request number is output as a step output.
|
||||
Note that in order to read the step output the action step must have an id.
|
||||
The following outputs can be used by subsequent workflow steps.
|
||||
|
||||
- `pull-request-number` - The pull request number.
|
||||
- `pull-request-url` - The URL of the pull request.
|
||||
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated` or `closed`.
|
||||
- `pull-request-head-sha` - The commit SHA of the pull request branch.
|
||||
|
||||
Step outputs can be accessed as in the following example.
|
||||
Note that in order to read the step outputs the action step must have an id.
|
||||
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
@ -71,17 +92,7 @@ Note that in order to read the step output the action step must have an id.
|
||||
- name: Check outputs
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
```
|
||||
|
||||
### Checkout
|
||||
|
||||
This action expects repositories to be checked out with `actions/checkout@v2`.
|
||||
|
||||
If there is some reason you need to use `actions/checkout@v1` the following step can be added to checkout the branch.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v1
|
||||
- run: git checkout "${GITHUB_REF:11}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
```
|
||||
|
||||
### Action behaviour
|
||||
@ -95,7 +106,7 @@ How the action behaves:
|
||||
- If there are changes (i.e. a diff exists with the checked-out base branch), the changes will be pushed to a new `branch` and a pull request created.
|
||||
- If there are no changes (i.e. no diff exists with the checked-out base branch), no pull request will be created and the action exits silently.
|
||||
- If a pull request already exists and there are no further changes (i.e. no diff with the current pull request branch) then the action exits silently.
|
||||
- If a pull request exists and new changes on the base branch make the pull request unnecessary (i.e. there is no longer a diff between the base and pull request branch), the pull request is automatically closed and the branch deleted.
|
||||
- If a pull request exists and new changes on the base branch make the pull request unnecessary (i.e. there is no longer a diff between the pull request branch and the base), the pull request is automatically closed. Additionally, if `delete-branch` is set to `true` the `branch` will be deleted.
|
||||
|
||||
For further details about how the action works and usage guidelines, see [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md).
|
||||
|
||||
@ -112,9 +123,45 @@ To use this strategy, set input `branch-suffix` with one of the following option
|
||||
|
||||
- `short-commit-hash` - Commits will be made to a branch suffixed with the short SHA1 commit hash. e.g. `create-pull-request/patch-fcdfb59`, `create-pull-request/patch-394710b`
|
||||
|
||||
### Controlling commits
|
||||
### Controlling committed files
|
||||
|
||||
The action defaults to adding all new and modified files.
|
||||
If there are files that should not be included in the pull request, you can use the following methods to control the committed content.
|
||||
|
||||
#### Remove files
|
||||
|
||||
The most straightforward way to handle unwanted files is simply to remove them in a step before the action runs.
|
||||
|
||||
```yml
|
||||
- run: |
|
||||
rm -rf temp-dir
|
||||
rm temp-file.txt
|
||||
```
|
||||
|
||||
#### Ignore files
|
||||
|
||||
If there are files or directories you want to ignore you can simply add them to a `.gitignore` file at the root of your repository. The action will respect this file.
|
||||
|
||||
#### Add specific paths
|
||||
|
||||
You can control which files are committed with the `add-paths` input.
|
||||
Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax.
|
||||
Each path must resolve to a least one new or modified file to add.
|
||||
All file changes that do not match one of the paths will be discarded.
|
||||
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
add-paths: |
|
||||
*.java
|
||||
docs/*.md
|
||||
```
|
||||
|
||||
#### Create your own commits
|
||||
|
||||
As well as relying on the action to handle uncommitted changes, you can additionally make your own commits before the action runs.
|
||||
Note that the repository must be checked out on a branch with a remote, it won't work for [events which checkout a commit](docs/concepts-guidelines.md#events-which-checkout-a-commit).
|
||||
|
||||
```yml
|
||||
steps:
|
||||
@ -134,10 +181,6 @@ As well as relying on the action to handle uncommitted changes, you can addition
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
```
|
||||
|
||||
### Ignoring files
|
||||
|
||||
If there are files or directories you want to ignore you can simply add them to a `.gitignore` file at the root of your repository. The action will respect this file.
|
||||
|
||||
### Create a project card
|
||||
|
||||
To create a project card for the pull request, pass the `pull-request-number` step output to [create-or-update-project-card](https://github.com/peter-evans/create-or-update-project-card) action.
|
||||
@ -155,15 +198,19 @@ To create a project card for the pull request, pass the `pull-request-number` st
|
||||
issue-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
```
|
||||
|
||||
### Auto-merge
|
||||
|
||||
Auto-merge can be enabled on a pull request allowing it to be automatically merged once requirements have been satisfied.
|
||||
See [enable-pull-request-automerge](https://github.com/peter-evans/enable-pull-request-automerge) action for usage details.
|
||||
|
||||
## Reference Example
|
||||
|
||||
The following workflow is a reference example that sets many of the main inputs.
|
||||
The following workflow sets many of the action's inputs for reference purposes.
|
||||
Check the [defaults](#action-inputs) to avoid setting inputs unnecessarily.
|
||||
|
||||
See [examples](docs/examples.md) for more realistic use cases.
|
||||
|
||||
```yml
|
||||
name: Create Pull Request
|
||||
on: push
|
||||
jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
@ -183,6 +230,7 @@ jobs:
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: example-patches
|
||||
delete-branch: true
|
||||
title: '[Example] Update report'
|
||||
body: |
|
||||
Update report
|
||||
@ -200,10 +248,6 @@ jobs:
|
||||
maintainers
|
||||
milestone: 1
|
||||
draft: false
|
||||
|
||||
- name: Check output
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
```
|
||||
|
||||
An example based on the above reference configuration creates pull requests that look like this:
|
||||
|
@ -1,4 +1,8 @@
|
||||
import {createOrUpdateBranch, tryFetch} from '../lib/create-or-update-branch'
|
||||
import {
|
||||
createOrUpdateBranch,
|
||||
tryFetch,
|
||||
getWorkingBaseAndType
|
||||
} from '../lib/create-or-update-branch'
|
||||
import * as fs from 'fs'
|
||||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
import * as path from 'path'
|
||||
@ -21,6 +25,9 @@ const BASE = DEFAULT_BRANCH
|
||||
const FORK_REMOTE_URL = 'git://127.0.0.1/test-fork.git'
|
||||
const FORK_REMOTE_NAME = 'fork'
|
||||
|
||||
const ADD_PATHS = ['-A']
|
||||
const ADD_PATHS_WILDCARD = ['*.txt']
|
||||
|
||||
async function createFile(filename: string, content?: string): Promise<string> {
|
||||
const _content = content ? content : uuidv4()
|
||||
const filepath = path.join(REPO_PATH, filename)
|
||||
@ -193,6 +200,21 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_EXIST_BRANCH)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('tests getWorkingBaseAndType on a checked out ref', async () => {
|
||||
const [workingBase, workingBaseType] = await getWorkingBaseAndType(git)
|
||||
expect(workingBase).toEqual(BASE)
|
||||
expect(workingBaseType).toEqual('branch')
|
||||
})
|
||||
|
||||
it('tests getWorkingBaseAndType on a checked out commit', async () => {
|
||||
// Checkout the HEAD commit SHA
|
||||
const headSha = await git.revParse('HEAD')
|
||||
await git.exec(['checkout', headSha])
|
||||
const [workingBase, workingBaseType] = await getWorkingBaseAndType(git)
|
||||
expect(workingBase).toEqual(headSha)
|
||||
expect(workingBaseType).toEqual('commit')
|
||||
})
|
||||
|
||||
it('tests no changes resulting in no new branch being created', async () => {
|
||||
const commitMessage = uuidv4()
|
||||
const result = await createOrUpdateBranch(
|
||||
@ -201,7 +223,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('none')
|
||||
expect(await gitLogMatches([INIT_COMMIT_MESSAGE])).toBeTruthy()
|
||||
@ -217,7 +240,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(trackedContent)
|
||||
@ -244,7 +268,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -264,7 +289,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(untrackedContent)
|
||||
@ -291,7 +317,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -313,7 +340,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -341,9 +369,10 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('none')
|
||||
expect(_result.action).toEqual('not-updated')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked)
|
||||
expect(
|
||||
@ -361,7 +390,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -397,7 +427,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -427,7 +458,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -454,7 +486,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeFalsy()
|
||||
@ -474,7 +507,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -513,7 +547,78 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeFalsy()
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||
expect(
|
||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
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
|
||||
// different commits for the same changes. To prevent creating pull requests
|
||||
// unnecessarily we reset (rebase) the pull request branch when a reset would result
|
||||
// in no diff with the base. This will reset any undeleted branches after merging.
|
||||
|
||||
// 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
|
||||
)
|
||||
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 the same changes as the branch
|
||||
// This simulates squash merge of the pull request
|
||||
const commits = await createCommits(
|
||||
git,
|
||||
1,
|
||||
changes.tracked,
|
||||
changes.untracked
|
||||
)
|
||||
await git.push([
|
||||
'--force',
|
||||
REMOTE_NAME,
|
||||
`HEAD:refs/heads/${DEFAULT_BRANCH}`
|
||||
])
|
||||
|
||||
// Create the same tracked and untracked file changes (no change on update)
|
||||
const _changes = await createChanges(changes.tracked, changes.untracked)
|
||||
const _commitMessage = uuidv4()
|
||||
const _result = await createOrUpdateBranch(
|
||||
git,
|
||||
_commitMessage,
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeFalsy()
|
||||
@ -534,7 +639,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(commits.changes.tracked)
|
||||
@ -564,7 +670,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -589,7 +696,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -623,7 +731,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -650,7 +759,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -692,7 +802,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -718,7 +829,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
FORK_REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -746,7 +858,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
FORK_REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -767,7 +880,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
true
|
||||
true,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -802,7 +916,8 @@ describe('create-or-update-branch tests', () => {
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
true
|
||||
true,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -820,6 +935,59 @@ describe('create-or-update-branch tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('tests create and update with wildcard add-paths', async () => {
|
||||
// The pull request branch will not be updated
|
||||
|
||||
// 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_WILDCARD
|
||||
)
|
||||
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 tracked and untracked file changes
|
||||
const _changes = await createChanges()
|
||||
const _commitMessage = uuidv4()
|
||||
const _result = await createOrUpdateBranch(
|
||||
git,
|
||||
_commitMessage,
|
||||
'',
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false,
|
||||
ADD_PATHS_WILDCARD
|
||||
)
|
||||
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, INIT_COMMIT_MESSAGE])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
// Working Base is Not Base (WBNB)
|
||||
|
||||
it('tests no changes resulting in no new branch being created (WBNB)', async () => {
|
||||
@ -833,7 +1001,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('none')
|
||||
expect(await gitLogMatches([INIT_COMMIT_MESSAGE])).toBeTruthy()
|
||||
@ -852,7 +1021,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(trackedContent)
|
||||
@ -882,7 +1052,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -905,7 +1076,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(untrackedContent)
|
||||
@ -935,7 +1107,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -960,7 +1133,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -991,9 +1165,10 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('none')
|
||||
expect(_result.action).toEqual('not-updated')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked)
|
||||
expect(
|
||||
@ -1014,7 +1189,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -1053,7 +1229,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -1086,7 +1263,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -1116,7 +1294,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeFalsy()
|
||||
@ -1141,7 +1320,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -1183,7 +1363,84 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeFalsy()
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||
expect(
|
||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
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
|
||||
// different commits for the same changes. To prevent creating pull requests
|
||||
// unnecessarily we reset (rebase) the pull request branch when a reset would result
|
||||
// in no diff with the base. This will reset any undeleted branches after merging.
|
||||
|
||||
// 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
|
||||
)
|
||||
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 the same changes as the branch
|
||||
// This simulates squash merge of the pull request
|
||||
const commits = await createCommits(
|
||||
git,
|
||||
1,
|
||||
changes.tracked,
|
||||
changes.untracked
|
||||
)
|
||||
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 (no change on update)
|
||||
const _changes = await createChanges(changes.tracked, changes.untracked)
|
||||
const _commitMessage = uuidv4()
|
||||
const _result = await createOrUpdateBranch(
|
||||
git,
|
||||
_commitMessage,
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeFalsy()
|
||||
@ -1207,7 +1464,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(commits.changes.tracked)
|
||||
@ -1240,7 +1498,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -1268,7 +1527,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -1305,7 +1565,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -1335,7 +1596,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -1380,7 +1642,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -1409,7 +1672,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
FORK_REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(result.action).toEqual('created')
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked)
|
||||
@ -1440,7 +1704,8 @@ describe('create-or-update-branch tests', () => {
|
||||
BASE,
|
||||
BRANCH,
|
||||
FORK_REMOTE_NAME,
|
||||
false
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
expect(_result.action).toEqual('updated')
|
||||
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||
@ -1450,4 +1715,160 @@ describe('create-or-update-branch tests', () => {
|
||||
await gitLogMatches([_commitMessage, INIT_COMMIT_MESSAGE])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
// Working Base is Not a Ref (WBNR)
|
||||
// A commit is checked out leaving the repository in a "detached HEAD" state
|
||||
|
||||
it('tests create and update in detached HEAD state (WBNR)', async () => {
|
||||
// Checkout the HEAD commit SHA
|
||||
const headSha = await git.revParse('HEAD')
|
||||
await git.checkout(headSha)
|
||||
|
||||
// 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
|
||||
)
|
||||
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()
|
||||
|
||||
// Checkout the HEAD commit SHA
|
||||
const _headSha = await git.revParse('HEAD')
|
||||
await git.checkout(_headSha)
|
||||
|
||||
// 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
|
||||
)
|
||||
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, INIT_COMMIT_MESSAGE])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('tests create and update with commits on the base inbetween, in detached HEAD state (WBNR)', async () => {
|
||||
// Checkout the HEAD commit SHA
|
||||
const headSha = await git.revParse('HEAD')
|
||||
await git.checkout(headSha)
|
||||
|
||||
// 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
|
||||
)
|
||||
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 commits on the base
|
||||
const commitsOnBase = await createCommits(git)
|
||||
await git.push([
|
||||
'--force',
|
||||
REMOTE_NAME,
|
||||
`HEAD:refs/heads/${DEFAULT_BRANCH}`
|
||||
])
|
||||
|
||||
// Checkout the HEAD commit SHA
|
||||
const _headSha = await git.revParse('HEAD')
|
||||
await git.checkout(_headSha)
|
||||
|
||||
// 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
|
||||
)
|
||||
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,
|
||||
...commitsOnBase.commitMsgs,
|
||||
INIT_COMMIT_MESSAGE
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
// This failure mode is a limitation of the action. Controlling your own commits cannot be used in detached HEAD state.
|
||||
// https://github.com/peter-evans/create-pull-request/issues/902
|
||||
it('tests failure to create with commits on the working base (during the workflow) in detached HEAD state (WBNR)', async () => {
|
||||
// Checkout the HEAD commit SHA
|
||||
const headSha = await git.revParse('HEAD')
|
||||
await git.checkout(headSha)
|
||||
|
||||
// Create commits on the working base
|
||||
const commits = await createCommits(git)
|
||||
const commitMessage = uuidv4()
|
||||
const result = await createOrUpdateBranch(
|
||||
git,
|
||||
commitMessage,
|
||||
BASE,
|
||||
BRANCH,
|
||||
REMOTE_NAME,
|
||||
false,
|
||||
ADD_PATHS
|
||||
)
|
||||
// The action cannot successfully create the branch
|
||||
expect(result.action).toEqual('none')
|
||||
})
|
||||
})
|
||||
|
@ -16,7 +16,7 @@ COPY __test__/entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
EOF
|
||||
|
||||
docker build -t $IMAGE .
|
||||
docker build --no-cache -t $IMAGE .
|
||||
rm Dockerfile
|
||||
fi
|
||||
|
||||
|
@ -58,7 +58,7 @@ describe('utils tests', () => {
|
||||
utils.getRemoteDetail(remoteUrl)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
@ -104,7 +104,7 @@ describe('utils tests', () => {
|
||||
utils.parseDisplayNameEmail(displayNameEmail1)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${displayNameEmail1}' is not a valid email address with display name`
|
||||
)
|
||||
@ -115,7 +115,7 @@ describe('utils tests', () => {
|
||||
utils.parseDisplayNameEmail(displayNameEmail2)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${displayNameEmail2}' is not a valid email address with display name`
|
||||
)
|
||||
|
20
action.yml
20
action.yml
@ -8,6 +8,13 @@ inputs:
|
||||
description: >
|
||||
Relative path under $GITHUB_WORKSPACE to the repository.
|
||||
Defaults to $GITHUB_WORKSPACE.
|
||||
add-paths:
|
||||
description: >
|
||||
A comma or newline-separated list of file paths to commit.
|
||||
Paths should follow git's pathspec syntax.
|
||||
Defaults to adding all new and modified files.
|
||||
default: |
|
||||
-A
|
||||
commit-message:
|
||||
description: 'The message to use when committing changes.'
|
||||
default: '[create-pull-request] automated change'
|
||||
@ -27,6 +34,11 @@ inputs:
|
||||
branch:
|
||||
description: 'The pull request branch name.'
|
||||
default: 'create-pull-request/patch'
|
||||
delete-branch:
|
||||
description: >
|
||||
Delete the `branch` when closing pull requests, and when undeleted after merging.
|
||||
Recommend `true`.
|
||||
default: false
|
||||
branch-suffix:
|
||||
description: 'The branch suffix type when using the alternative branching strategy.'
|
||||
base:
|
||||
@ -57,11 +69,17 @@ inputs:
|
||||
milestone:
|
||||
description: 'The number of the milestone to associate the pull request with.'
|
||||
draft:
|
||||
description: 'Create a draft pull request'
|
||||
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
|
||||
default: false
|
||||
outputs:
|
||||
pull-request-number:
|
||||
description: 'The pull request number'
|
||||
pull-request-url:
|
||||
description: 'The URL of the pull request.'
|
||||
pull-request-operation:
|
||||
description: 'The pull request operation performed by the action, `created`, `updated` or `closed`.'
|
||||
pull-request-head-sha:
|
||||
description: 'The commit SHA of the pull request branch.'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
|
14915
dist/index.js
vendored
14915
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
@ -29,37 +29,37 @@
|
||||
orientation: "vertical-reverse"
|
||||
});
|
||||
|
||||
const master = gitgraph.branch("master");
|
||||
master.commit("Last commit on base");
|
||||
const localMaster = gitgraph.branch("<#1> master (local)");
|
||||
localMaster.commit({
|
||||
const main = gitgraph.branch("main");
|
||||
main.commit("Last commit on base");
|
||||
const localMain = gitgraph.branch("<#1> main (local)");
|
||||
localMain.commit({
|
||||
subject: "<uncommitted changes>",
|
||||
body: "Changes made to the local base during the workflow",
|
||||
body: "Changes to the local base during the workflow",
|
||||
})
|
||||
const remotePatch = gitgraph.branch("create-pull-request/patch");
|
||||
remotePatch.merge({
|
||||
branch: localMaster,
|
||||
branch: localMain,
|
||||
commitOptions: {
|
||||
subject: "[create-pull-request] automated change",
|
||||
body: "Changes pushed to create the remote branch",
|
||||
},
|
||||
});
|
||||
master.commit("New commit on base");
|
||||
main.commit("New commit on base");
|
||||
|
||||
const localMaster2 = gitgraph.branch("<#2> master (local)");
|
||||
localMaster2.commit({
|
||||
const localMain2 = gitgraph.branch("<#2> main (local)");
|
||||
localMain2.commit({
|
||||
subject: "<uncommitted changes>",
|
||||
body: "Changes made to the updated local base during the workflow",
|
||||
body: "Changes to the updated local base during the workflow",
|
||||
})
|
||||
remotePatch.merge({
|
||||
branch: localMaster2,
|
||||
branch: localMain2,
|
||||
commitOptions: {
|
||||
subject: "[create-pull-request] automated change",
|
||||
body: "Changes force pushed to update the remote branch",
|
||||
},
|
||||
});
|
||||
|
||||
master.merge(remotePatch);
|
||||
main.merge(remotePatch);
|
||||
|
||||
</script>
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 108 KiB |
@ -7,7 +7,7 @@ This document covers terminology, how the action works, general usage guidelines
|
||||
- [How the action works](#how-the-action-works)
|
||||
- [Guidelines](#guidelines)
|
||||
- [Providing a consistent base](#providing-a-consistent-base)
|
||||
- [Pull request events](#pull-request-events)
|
||||
- [Events which checkout a commit](#events-which-checkout-a-commit)
|
||||
- [Restrictions on repository forks](#restrictions-on-repository-forks)
|
||||
- [Triggering further workflow runs](#triggering-further-workflow-runs)
|
||||
- [Security](#security)
|
||||
@ -16,8 +16,8 @@ This document covers terminology, how the action works, general usage guidelines
|
||||
- [Push using SSH (deploy keys)](#push-using-ssh-deploy-keys)
|
||||
- [Push pull request branches to a fork](#push-pull-request-branches-to-a-fork)
|
||||
- [Authenticating with GitHub App generated tokens](#authenticating-with-github-app-generated-tokens)
|
||||
- [GPG commit signature verification](#gpg-commit-signature-verification)
|
||||
- [Running in a container or on self-hosted runners](#running-in-a-container-or-on-self-hosted-runners)
|
||||
- [Creating pull requests on tag push](#creating-pull-requests-on-tag-push)
|
||||
|
||||
## Terminology
|
||||
|
||||
@ -30,9 +30,8 @@ A pull request references two branches:
|
||||
|
||||
## Events and checkout
|
||||
|
||||
For each [event type](https://docs.github.com/en/actions/reference/events-that-trigger-workflows) there is a default `GITHUB_SHA` that will be checked out by the GitHub Actions [checkout](https://github.com/actions/checkout) action.
|
||||
|
||||
The majority of events will default to checking out the "last commit on default branch," which in most cases will be the latest commit on `master`.
|
||||
This action expects repositories to be checked out with the official GitHub Actions [checkout](https://github.com/actions/checkout) action.
|
||||
For each [event type](https://docs.github.com/en/actions/reference/events-that-trigger-workflows) there is a default `GITHUB_SHA` that will be checked out.
|
||||
|
||||
The default can be overridden by specifying a `ref` on checkout.
|
||||
|
||||
@ -44,7 +43,7 @@ The default can be overridden by specifying a `ref` on checkout.
|
||||
|
||||
## How the action works
|
||||
|
||||
By default, the action expects to be executed on the pull request `base`—the branch you intend to modify with the proposed changes.
|
||||
Unless the `base` input is supplied, the action expects the target repository to be checked out on the pull request `base`—the branch you intend to modify with the proposed changes.
|
||||
|
||||
Workflow steps:
|
||||
|
||||
@ -60,11 +59,11 @@ The following git diagram shows how the action creates and updates a pull reques
|
||||
|
||||
### Providing a consistent base
|
||||
|
||||
For the action to work correctly it should be executed in a workflow that checks out a *consistent base* branch. This will be the base of the pull request unless overridden with the `base` input.
|
||||
For the action to work correctly it should be executed in a workflow that checks out a *consistent* base branch. This will be the base of the pull request unless overridden with the `base` input.
|
||||
|
||||
This means your workflow should be consistently checking out the branch that you intend to modify once the PR is merged.
|
||||
|
||||
In the following example, the [`push`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push) and [`create`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#create) events both trigger the same workflow. This will cause the checkout action to checkout commits from inconsistent branches. Do *not* do this. It will cause multiple pull requests to be created for each additional `base` the action is executed against.
|
||||
In the following example, the [`push`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push) and [`create`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#create) events both trigger the same workflow. This will cause the checkout action to checkout inconsistent branches and commits. Do *not* do this. It will cause multiple pull requests to be created for each additional `base` the action is executed against.
|
||||
|
||||
```yml
|
||||
on:
|
||||
@ -77,16 +76,29 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
```
|
||||
|
||||
Although rare, there may be use cases where it makes sense to execute the workflow on a branch that is not the base of the pull request. In these cases, the base branch can be specified with the `base` action input. The action will attempt to rebase changes made during the workflow on to the actual base.
|
||||
There may be use cases where it makes sense to execute the workflow on a branch that is not the base of the pull request. In these cases, the base branch can be specified with the `base` action input. The action will attempt to rebase changes made during the workflow on to the actual base.
|
||||
|
||||
### Pull request events
|
||||
### Events which checkout a commit
|
||||
|
||||
Workflows triggered by `pull_request` events will by default check out a [merge commit](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request). To prevent the merge commit being included in created pull requests it is necessary to checkout the `head_ref`.
|
||||
The [default checkout](#events-and-checkout) for the majority of events will leave the repository checked out on a branch.
|
||||
However, some events such as `release` and `pull_request` will leave the repository in a "detached HEAD" state.
|
||||
This is because they checkout a commit, not a branch.
|
||||
In these cases, you *must supply* the `base` input so the action can rebase changes made during the workflow for the pull request.
|
||||
|
||||
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
|
||||
- uses: actions/checkout@v2
|
||||
- uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
base: ${{ github.head_ref }}
|
||||
```
|
||||
|
||||
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
|
||||
- uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
base: main
|
||||
```
|
||||
|
||||
### Restrictions on repository forks
|
||||
@ -118,6 +130,8 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
```
|
||||
|
||||
For further reading regarding the security of pull requests, see this GitHub blog post titled [Keeping your GitHub Actions and workflows secure: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
|
||||
### Triggering further workflow runs
|
||||
|
||||
Pull requests created by the action using the default `GITHUB_TOKEN` cannot trigger other workflows. If you have `on: pull_request` or `on: push` workflows acting as checks on pull requests, they will not run.
|
||||
@ -131,9 +145,13 @@ Pull requests created by the action using the default `GITHUB_TOKEN` cannot trig
|
||||
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.
|
||||
|
||||
- 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/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token). 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 [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 [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
|
||||
@ -146,7 +164,7 @@ Alternatively, use the action directly and reference the commit hash for the ver
|
||||
- uses: thirdparty/foo-action@172ec762f2ac8e050062398456fccd30444f8f30
|
||||
```
|
||||
|
||||
This action uses [ncc](https://github.com/zeit/ncc) to compile the Node.js code and dependencies into a single JavaScript file under the [dist](https://github.com/peter-evans/create-pull-request/tree/master/dist) directory.
|
||||
This action uses [ncc](https://github.com/vercel/ncc) to compile the Node.js code and dependencies into a single JavaScript file under the [dist](https://github.com/peter-evans/create-pull-request/tree/main/dist) directory.
|
||||
|
||||
## Advanced usage
|
||||
|
||||
@ -230,6 +248,8 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
||||
- Uncheck `Active` under `Webhook`. You do not need to enter a `Webhook URL`.
|
||||
- Under `Repository permissions: Contents` select `Access: Read & write`.
|
||||
- Under `Repository permissions: Pull requests` select `Access: Read & write`.
|
||||
- Under `Organization permissions: Members` select `Access: Read-only`.
|
||||
- **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.
|
||||
|
||||
@ -257,6 +277,48 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
```
|
||||
|
||||
### GPG commit signature verification
|
||||
|
||||
The action can use GPG to sign commits with a GPG key that you generate yourself.
|
||||
|
||||
1. Follow GitHub's guide to [generate a new GPG key](https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key).
|
||||
|
||||
2. [Add the public key](https://docs.github.com/en/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account) to the user account associated with the [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that you will use with the action.
|
||||
|
||||
3. Copy the private key to your clipboard, replacing `email@example.com` with the email address of your GPG key.
|
||||
```
|
||||
# macOS
|
||||
gpg --armor --export-secret-key email@example.com | pbcopy
|
||||
```
|
||||
|
||||
4. Paste the private key into a repository secret where the workflow will run. e.g. `GPG_PRIVATE_KEY`
|
||||
|
||||
5. Create another repository secret for the key's passphrase, if applicable. e.g. `GPG_PASSPHRASE`
|
||||
|
||||
6. The following example workflow shows how to use [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) to import your GPG key and allow the action to sign commits.
|
||||
|
||||
Note that the `committer` email address *MUST* match the email address used to create your GPG key.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: crazy-max/ghaction-import-gpg@v3
|
||||
with:
|
||||
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
git-user-signingkey: true
|
||||
git-commit-gpgsign: true
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
committer: example <email@example.com>
|
||||
```
|
||||
|
||||
### Running in a container or on self-hosted runners
|
||||
|
||||
This action can be run inside a container, or on [self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners), by installing the necessary dependencies.
|
||||
@ -306,63 +368,3 @@ jobs:
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
```
|
||||
|
||||
### Creating pull requests on tag push
|
||||
|
||||
An `on: push` workflow will also trigger when tags are pushed.
|
||||
During these events, the `actions/checkout` action will check out the `ref/tags/<tag>` git ref by default.
|
||||
This means the repository will *not* be checked out on an active branch.
|
||||
|
||||
If you would like to run `create-pull-request` action on the tagged commit you can achieve this by creating a temporary branch as follows.
|
||||
|
||||
```yml
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create a temporary tag branch
|
||||
run: |
|
||||
git config --global user.name 'GitHub'
|
||||
git config --global user.email 'noreply@github.com'
|
||||
git checkout -b temp-${GITHUB_REF:10}
|
||||
git push --set-upstream origin temp-${GITHUB_REF:10}
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
base: master
|
||||
|
||||
- name: Delete tag branch
|
||||
run: |
|
||||
git push --delete origin temp-${GITHUB_REF:10}
|
||||
```
|
||||
|
||||
This is an alternative, simpler workflow to the one above. However, this is not guaranteed to checkout the tagged commit.
|
||||
There is a chance that in between the tag being pushed and checking out the `master` branch in the workflow, another commit is made to `master`. If that possibility is not a concern, this workflow will work fine.
|
||||
|
||||
```yml
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
```
|
||||
|
133
docs/examples.md
133
docs/examples.md
@ -3,11 +3,14 @@
|
||||
- [Use case: Create a pull request to update X on push](#use-case-create-a-pull-request-to-update-x-on-push)
|
||||
- [Update project authors](#update-project-authors)
|
||||
- [Keep a branch up-to-date with another](#keep-a-branch-up-to-date-with-another)
|
||||
- [Use case: Create a pull request to update X on release](#use-case-create-a-pull-request-to-update-x-on-release)
|
||||
- [Update changelog](#update-changelog)
|
||||
- [Use case: Create a pull request to update X periodically](#use-case-create-a-pull-request-to-update-x-periodically)
|
||||
- [Update NPM dependencies](#update-npm-dependencies)
|
||||
- [Update Gradle dependencies](#update-gradle-dependencies)
|
||||
- [Update Cargo dependencies](#update-cargo-dependencies)
|
||||
- [Update SwaggerUI for GitHub Pages](#update-swaggerui-for-github-pages)
|
||||
- [Keep a fork up-to-date with its upstream](#keep-a-fork-up-to-date-with-its-upstream)
|
||||
- [Spider and download a website](#spider-and-download-a-website)
|
||||
- [Use case: Create a pull request to update X by calling the GitHub API](#use-case-create-a-pull-request-to-update-x-by-calling-the-github-api)
|
||||
- [Call the GitHub API from an external service](#call-the-github-api-from-an-external-service)
|
||||
@ -17,6 +20,8 @@
|
||||
- [Misc workflow tips](#misc-workflow-tips)
|
||||
- [Filtering push events](#filtering-push-events)
|
||||
- [Dynamic configuration using variables](#dynamic-configuration-using-variables)
|
||||
- [Setting the pull request body from a file](#setting-the-pull-request-body-from-a-file)
|
||||
- [Using a markdown template](#using-a-markdown-template)
|
||||
- [Debugging GitHub Actions](#debugging-github-actions)
|
||||
|
||||
|
||||
@ -33,7 +38,7 @@ name: Update AUTHORS
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
updateAuthors:
|
||||
runs-on: ubuntu-latest
|
||||
@ -57,14 +62,14 @@ jobs:
|
||||
|
||||
This is a use case where a branch should be kept up to date with another by opening a pull request to update it. The pull request should then be updated with new changes until it is merged or closed.
|
||||
|
||||
In this example scenario, a branch called `production` should be updated via pull request to keep it in sync with `master`. Merging the pull request is effectively promoting those changes to production.
|
||||
In this example scenario, a branch called `production` should be updated via pull request to keep it in sync with `main`. Merging the pull request is effectively promoting those changes to production.
|
||||
|
||||
```yml
|
||||
name: Create production promotion pull request
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
productionPromotion:
|
||||
runs-on: ubuntu-latest
|
||||
@ -74,14 +79,53 @@ jobs:
|
||||
ref: production
|
||||
- name: Reset promotion branch
|
||||
run: |
|
||||
git fetch origin master:master
|
||||
git reset --hard master
|
||||
git fetch origin main:main
|
||||
git reset --hard main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
branch: production-promotion
|
||||
```
|
||||
|
||||
## Use case: Create a pull request to update X on release
|
||||
|
||||
This pattern will work well for updating any kind of static content based on the tagged commit of a release. Note that because `release` is one of the [events which checkout a commit](concepts-guidelines.md#events-which-checkout-a-commit) it is necessary to supply the `base` input to the action.
|
||||
|
||||
### Update changelog
|
||||
|
||||
Raises a pull request to update the `CHANGELOG.md` file based on the tagged commit of the release.
|
||||
Note that [git-chglog](https://github.com/git-chglog/git-chglog/) requires some configuration files to exist in the repository before this workflow will work.
|
||||
|
||||
This workflow assumes the tagged release was made on a default branch called `main`.
|
||||
|
||||
```yml
|
||||
name: Update Changelog
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
updateChangelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Update Changelog
|
||||
run: |
|
||||
curl -o git-chglog -L https://github.com/git-chglog/git-chglog/releases/download/0.9.1/git-chglog_linux_amd64
|
||||
chmod u+x git-chglog
|
||||
./git-chglog -o CHANGELOG.md
|
||||
rm git-chglog
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
commit-message: update changelog
|
||||
title: Update Changelog
|
||||
body: Update changelog to reflect release changes
|
||||
branch: update-changelog
|
||||
base: main
|
||||
```
|
||||
|
||||
## Use case: Create a pull request to update X periodically
|
||||
|
||||
This pattern will work well for updating any kind of static content from an external source. The workflow executes on a schedule and raises a pull request when there are changes.
|
||||
@ -130,9 +174,9 @@ The above workflow works best in combination with a build workflow triggered on
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@ -276,6 +320,41 @@ jobs:
|
||||
branch: swagger-ui-updates
|
||||
```
|
||||
|
||||
### Keep a fork up-to-date with its upstream
|
||||
|
||||
This example is designed to be run in a seperate repository from the fork repository itself.
|
||||
The aim of this is to prevent committing anything to the fork's default branch would cause it to differ from the upstream.
|
||||
|
||||
In the following example workflow, `owner/repo` is the upstream repository and `fork-owner/repo` is the fork. It assumes the default branch of the upstream repository is called `main`.
|
||||
|
||||
The [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) should have `repo` scope. Additionally, if the upstream makes changes to the `.github/workflows` directory, the action will be unable to push the changes to a branch and throw the error "_(refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission)_". To allow these changes to be pushed to the fork, add the `workflow` scope to the PAT. Of course, allowing this comes with the risk that the workflow changes from the upstream could run and do something unexpected. Disabling GitHub Actions in the fork is highly recommended to prevent this.
|
||||
|
||||
When you merge the pull request make sure to choose the [`Rebase and merge`](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-merges#rebase-and-merge-your-pull-request-commits) option. This will make the fork's commits match the commits on the upstream.
|
||||
|
||||
```yml
|
||||
name: Update fork
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
jobs:
|
||||
updateFork:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: fork-owner/repo
|
||||
- name: Reset the default branch with upstream changes
|
||||
run: |
|
||||
git remote add upstream https://github.com/owner/repo.git
|
||||
git fetch upstream main:upstream-main
|
||||
git reset --hard upstream-main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
branch: upstream-changes
|
||||
```
|
||||
|
||||
### Spider and download a website
|
||||
|
||||
This workflow spiders a website and downloads the content. Any changes to the website will be raised in a pull request.
|
||||
@ -462,21 +541,6 @@ The recommended method is to use [`set-output`](https://docs.github.com/en/actio
|
||||
body: ${{ steps.vars.outputs.pr_body }}
|
||||
```
|
||||
|
||||
Alternatively, [`set-env`](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable) can be used to create environment variables.
|
||||
|
||||
```yml
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
echo ::set-env name=PULL_REQUEST_TITLE::"[Test] Add report file $(date +%d-%m-%Y)"
|
||||
echo ::set-env name=PULL_REQUEST_BODY::"This PR was auto-generated on $(date +%d-%m-%Y) \
|
||||
by [create-pull-request](https://github.com/peter-evans/create-pull-request)."
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
title: ${{ env.PULL_REQUEST_TITLE }}
|
||||
body: ${{ env.PULL_REQUEST_BODY }}
|
||||
```
|
||||
|
||||
### Setting the pull request body from a file
|
||||
|
||||
This example shows how file content can be read into a variable and passed to the action.
|
||||
@ -497,6 +561,31 @@ The content must be [escaped to preserve newlines](https://github.community/t/se
|
||||
body: ${{ steps.get-pr-body.outputs.body }}
|
||||
```
|
||||
|
||||
### Using a markdown template
|
||||
|
||||
In this example, a markdown template file is added to the repository at `.github/pull-request-template.md` with the following content.
|
||||
```
|
||||
This is a test pull request template
|
||||
Render template variables such as {{ .foo }} and {{ .bar }}.
|
||||
```
|
||||
|
||||
The template is rendered using the [render-template](https://github.com/chuhlomin/render-template) action and the result is used to create the pull request.
|
||||
```yml
|
||||
- name: Render template
|
||||
id: template
|
||||
uses: chuhlomin/render-template@v1.2
|
||||
with:
|
||||
template: .github/pull-request-template.md
|
||||
vars: |
|
||||
foo: this
|
||||
bar: that
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
body: ${{ steps.template.outputs.result }}
|
||||
```
|
||||
|
||||
### Debugging GitHub Actions
|
||||
|
||||
#### Runner Diagnostic Logging
|
||||
|
@ -68,6 +68,6 @@
|
||||
|
||||
### New features
|
||||
|
||||
- Unpushed commits made during the workflow before the action runs will now be considered as changes to be raised in the pull request. See [Controlling commits](https://github.com/peter-evans/create-pull-request#controlling-commits) for details.
|
||||
- Unpushed commits made during the workflow before the action runs will now be considered as changes to be raised in the pull request. See [Create your own commits](https://github.com/peter-evans/create-pull-request#create-your-own-commits) for details.
|
||||
- New commits made to the pull request base will now be taken into account when pull requests are updated.
|
||||
- If an updated pull request no longer differs from its base it will automatically be closed and the pull request branch deleted.
|
||||
|
12839
package-lock.json
generated
12839
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
41
package.json
41
package.json
@ -29,26 +29,29 @@
|
||||
},
|
||||
"homepage": "https://github.com/peter-evans/create-pull-request",
|
||||
"dependencies": {
|
||||
"@actions/core": "1.2.4",
|
||||
"@actions/exec": "1.0.4",
|
||||
"@octokit/core": "3.1.2",
|
||||
"@octokit/plugin-paginate-rest": "2.3.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "4.1.2",
|
||||
"uuid": "8.3.0"
|
||||
"@actions/core": "^1.6.0",
|
||||
"@actions/exec": "^1.1.0",
|
||||
"@octokit/core": "^3.5.1",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "26.0.9",
|
||||
"@types/node": "14.0.27",
|
||||
"@typescript-eslint/parser": "3.9.0",
|
||||
"@zeit/ncc": "0.22.3",
|
||||
"eslint": "7.6.0",
|
||||
"eslint-plugin-github": "4.1.1",
|
||||
"eslint-plugin-jest": "23.20.0",
|
||||
"jest": "26.4.0",
|
||||
"jest-circus": "26.4.0",
|
||||
"js-yaml": "3.14.0",
|
||||
"prettier": "2.0.5",
|
||||
"ts-jest": "26.2.0",
|
||||
"typescript": "3.9.7"
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^16.11.11",
|
||||
"@typescript-eslint/parser": "^5.5.0",
|
||||
"@vercel/ncc": "^0.32.0",
|
||||
"eslint": "^8.3.0",
|
||||
"eslint-import-resolver-typescript": "^2.5.0",
|
||||
"eslint-plugin-github": "^4.3.5",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-jest": "^25.3.0",
|
||||
"jest": "^27.4.3",
|
||||
"jest-circus": "^27.4.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^2.5.0",
|
||||
"ts-jest": "^27.0.7",
|
||||
"typescript": "^4.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,28 @@ import {v4 as uuidv4} from 'uuid'
|
||||
const CHERRYPICK_EMPTY =
|
||||
'The previous cherry-pick is now empty, possibly due to conflict resolution.'
|
||||
|
||||
export enum WorkingBaseType {
|
||||
Branch = 'branch',
|
||||
Commit = 'commit'
|
||||
}
|
||||
|
||||
export async function getWorkingBaseAndType(
|
||||
git: GitCommandManager
|
||||
): Promise<[string, WorkingBaseType]> {
|
||||
const symbolicRefResult = await git.exec(
|
||||
['symbolic-ref', 'HEAD', '--short'],
|
||||
true
|
||||
)
|
||||
if (symbolicRefResult.exitCode == 0) {
|
||||
// A ref is checked out
|
||||
return [symbolicRefResult.stdout.trim(), WorkingBaseType.Branch]
|
||||
} else {
|
||||
// A commit is checked out (detached HEAD)
|
||||
const headSha = await git.revParse('HEAD')
|
||||
return [headSha, WorkingBaseType.Commit]
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryFetch(
|
||||
git: GitCommandManager,
|
||||
remote: string,
|
||||
@ -56,15 +78,6 @@ async function isEven(
|
||||
)
|
||||
}
|
||||
|
||||
async function hasDiff(
|
||||
git: GitCommandManager,
|
||||
branch1: string,
|
||||
branch2: string
|
||||
): Promise<boolean> {
|
||||
const result = await git.diff([`${branch1}..${branch2}`])
|
||||
return result.length > 0
|
||||
}
|
||||
|
||||
function splitLines(multilineString: string): string[] {
|
||||
return multilineString
|
||||
.split('\n')
|
||||
@ -78,10 +91,18 @@ export async function createOrUpdateBranch(
|
||||
base: string,
|
||||
branch: string,
|
||||
branchRemoteName: string,
|
||||
signoff: boolean
|
||||
signoff: boolean,
|
||||
addPaths: string[]
|
||||
): Promise<CreateOrUpdateBranchResult> {
|
||||
// Get the working base. This may or may not be the actual base.
|
||||
const workingBase = await git.symbolicRef('HEAD', ['--short'])
|
||||
// Get the working base.
|
||||
// When a ref, it may or may not be the actual base.
|
||||
// When a commit, we must rebase onto the actual base.
|
||||
const [workingBase, workingBaseType] = await getWorkingBaseAndType(git)
|
||||
core.info(`Working base is ${workingBaseType} '${workingBase}'`)
|
||||
if (workingBaseType == WorkingBaseType.Commit && !base) {
|
||||
throw new Error(`When in 'detached HEAD' state, 'base' must be supplied.`)
|
||||
}
|
||||
|
||||
// If the base is not specified it is assumed to be the working base.
|
||||
base = base ? base : workingBase
|
||||
const baseRemote = 'origin'
|
||||
@ -90,7 +111,8 @@ export async function createOrUpdateBranch(
|
||||
const result: CreateOrUpdateBranchResult = {
|
||||
action: 'none',
|
||||
base: base,
|
||||
hasDiffWithBase: false
|
||||
hasDiffWithBase: false,
|
||||
headSha: ''
|
||||
}
|
||||
|
||||
// Save the working base changes to a temporary branch
|
||||
@ -99,22 +121,41 @@ export async function createOrUpdateBranch(
|
||||
// Commit any uncommitted changes
|
||||
if (await git.isDirty(true)) {
|
||||
core.info('Uncommitted changes found. Adding a commit.')
|
||||
await git.exec(['add', '-A'])
|
||||
for (const path of addPaths) {
|
||||
await git.exec(['add', path], true)
|
||||
}
|
||||
const params = ['-m', commitMessage]
|
||||
if (signoff) {
|
||||
params.push('--signoff')
|
||||
}
|
||||
await git.commit(params)
|
||||
// Remove uncommitted tracked and untracked changes
|
||||
await git.exec(['reset', '--hard'])
|
||||
await git.exec(['clean', '-f'])
|
||||
}
|
||||
|
||||
// Perform fetch and reset the working base
|
||||
// Commits made during the workflow will be removed
|
||||
await git.fetch([`${workingBase}:${workingBase}`], baseRemote, ['--force'])
|
||||
if (workingBaseType == WorkingBaseType.Branch) {
|
||||
core.info(`Resetting working base branch '${workingBase}'`)
|
||||
if (branchRemoteName == 'fork') {
|
||||
// If pushing to a fork we must fetch with 'unshallow' to avoid the following error on git push
|
||||
// ! [remote rejected] HEAD -> tests/push-branch-to-fork (shallow update not allowed)
|
||||
await git.fetch([`${workingBase}:${workingBase}`], baseRemote, [
|
||||
'--force'
|
||||
])
|
||||
} else {
|
||||
// If the remote is 'origin' we can git reset
|
||||
await git.checkout(workingBase)
|
||||
await git.exec(['reset', '--hard', `${baseRemote}/${workingBase}`])
|
||||
}
|
||||
}
|
||||
|
||||
// If the working base is not the base, rebase the temp branch commits
|
||||
// This will also be true if the working base type is a commit
|
||||
if (workingBase != base) {
|
||||
core.info(
|
||||
`Rebasing commits made to branch '${workingBase}' on to base branch '${base}'`
|
||||
`Rebasing commits made to ${workingBaseType} '${workingBase}' on to base branch '${base}'`
|
||||
)
|
||||
// Checkout the actual base
|
||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
||||
@ -144,7 +185,7 @@ export async function createOrUpdateBranch(
|
||||
// The pull request branch does not exist
|
||||
core.info(`Pull request branch '${branch}' does not exist yet.`)
|
||||
// Create the pull request branch
|
||||
await git.checkout(branch, 'HEAD')
|
||||
await git.checkout(branch, tempBranch)
|
||||
// Check if the pull request branch is ahead of the base
|
||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||
if (result.hasDiffWithBase) {
|
||||
@ -163,9 +204,18 @@ export async function createOrUpdateBranch(
|
||||
// Checkout the pull request branch
|
||||
await git.checkout(branch)
|
||||
|
||||
if (await hasDiff(git, branch, tempBranch)) {
|
||||
// If the branch differs from the recreated temp version then the branch is reset
|
||||
// For changes on base this action is similar to a rebase of the pull request branch
|
||||
// Reset the branch if one of the following conditions is true.
|
||||
// - If the branch differs from the recreated temp branch.
|
||||
// - 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
|
||||
// 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
|
||||
// to have a diff with the base due to different commits for the same changes.
|
||||
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
||||
if (
|
||||
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||
!(await isAhead(git, base, tempBranch))
|
||||
) {
|
||||
core.info(`Resetting '${branch}'`)
|
||||
// Alternatively, git switch -C branch tempBranch
|
||||
await git.checkout(branch, tempBranch)
|
||||
@ -178,6 +228,7 @@ export async function createOrUpdateBranch(
|
||||
result.action = 'updated'
|
||||
core.info(`Updated branch '${branch}'`)
|
||||
} else {
|
||||
result.action = 'not-updated'
|
||||
core.info(
|
||||
`Branch '${branch}' is even with its remote and will not be updated`
|
||||
)
|
||||
@ -187,6 +238,9 @@ export async function createOrUpdateBranch(
|
||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||
}
|
||||
|
||||
// Get the pull request branch SHA
|
||||
result.headSha = await git.revParse('HEAD')
|
||||
|
||||
// Delete the temporary branch
|
||||
await git.exec(['branch', '--delete', '--force', tempBranch])
|
||||
|
||||
@ -197,4 +251,5 @@ interface CreateOrUpdateBranchResult {
|
||||
action: string
|
||||
base: string
|
||||
hasDiffWithBase: boolean
|
||||
headSha: string
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import * as core from '@actions/core'
|
||||
import {createOrUpdateBranch} from './create-or-update-branch'
|
||||
import {
|
||||
createOrUpdateBranch,
|
||||
getWorkingBaseAndType,
|
||||
WorkingBaseType
|
||||
} from './create-or-update-branch'
|
||||
import {GitHubHelper} from './github-helper'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {GitAuthHelper} from './git-auth-helper'
|
||||
@ -8,11 +12,13 @@ import * as utils from './utils'
|
||||
export interface Inputs {
|
||||
token: string
|
||||
path: string
|
||||
addPaths: string[]
|
||||
commitMessage: string
|
||||
committer: string
|
||||
author: string
|
||||
signoff: boolean
|
||||
branch: string
|
||||
deleteBranch: boolean
|
||||
branchSuffix: string
|
||||
base: string
|
||||
pushToFork: string
|
||||
@ -81,24 +87,16 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
// Determine if the checked out ref is a valid base for a pull request
|
||||
// The action needs the checked out HEAD ref to be a branch
|
||||
// This check will fail in the following cases:
|
||||
// - HEAD is detached
|
||||
// - HEAD is a merge commit (pull_request events)
|
||||
// - HEAD is a tag
|
||||
core.startGroup('Checking the checked out ref')
|
||||
const symbolicRefResult = await git.exec(
|
||||
['symbolic-ref', 'HEAD', '--short'],
|
||||
true
|
||||
)
|
||||
if (symbolicRefResult.exitCode != 0) {
|
||||
core.debug(`${symbolicRefResult.stderr}`)
|
||||
core.startGroup('Checking the base repository state')
|
||||
const [workingBase, workingBaseType] = await getWorkingBaseAndType(git)
|
||||
core.info(`Working base is ${workingBaseType} '${workingBase}'`)
|
||||
// When in detached HEAD state (checked out on a commit), we need to
|
||||
// know the 'base' branch in order to rebase changes.
|
||||
if (workingBaseType == WorkingBaseType.Commit && !inputs.base) {
|
||||
throw new Error(
|
||||
'The checked out ref is not a valid base for a pull request. Unable to continue.'
|
||||
`When the repository is checked out on a commit instead of a branch, the 'base' input must be supplied.`
|
||||
)
|
||||
}
|
||||
const workingBase = symbolicRefResult.stdout.trim()
|
||||
// If the base is not specified it is assumed to be the working base.
|
||||
const base = inputs.base ? inputs.base : workingBase
|
||||
// Throw an error if the base and branch are not different branches
|
||||
@ -109,6 +107,12 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
`The 'base' and 'branch' for a pull request must be different branches. Unable to continue.`
|
||||
)
|
||||
}
|
||||
// For self-hosted runners the repository state persists between runs.
|
||||
// This command prunes the stale remote ref when the pull request branch was
|
||||
// deleted after being merged or closed. Without this the push using
|
||||
// '--force-with-lease' fails due to "stale info."
|
||||
// https://github.com/peter-evans/create-pull-request/issues/633
|
||||
await git.exec(['remote', 'prune', branchRemoteName])
|
||||
core.endGroup()
|
||||
|
||||
// Apply the branch suffix if set
|
||||
@ -170,7 +174,8 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
inputs.base,
|
||||
inputs.branch,
|
||||
branchRemoteName,
|
||||
inputs.signoff
|
||||
inputs.signoff,
|
||||
inputs.addPaths
|
||||
)
|
||||
core.endGroup()
|
||||
|
||||
@ -185,32 +190,57 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
`HEAD:refs/heads/${inputs.branch}`
|
||||
])
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
|
||||
if (result.hasDiffWithBase) {
|
||||
// Create or update the pull request
|
||||
await githubHelper.createOrUpdatePullRequest(
|
||||
inputs,
|
||||
baseRemote.repository,
|
||||
branchRepository
|
||||
)
|
||||
} else {
|
||||
// If there is no longer a diff with the base delete the branch
|
||||
if (result.hasDiffWithBase) {
|
||||
// Create or update the pull request
|
||||
core.startGroup('Create or update the pull request')
|
||||
const pull = await githubHelper.createOrUpdatePullRequest(
|
||||
inputs,
|
||||
baseRemote.repository,
|
||||
branchRepository
|
||||
)
|
||||
core.endGroup()
|
||||
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-number', pull.number)
|
||||
core.setOutput('pull-request-url', pull.html_url)
|
||||
if (pull.created) {
|
||||
core.setOutput('pull-request-operation', 'created')
|
||||
} else if (result.action == 'updated') {
|
||||
core.setOutput('pull-request-operation', 'updated')
|
||||
}
|
||||
core.setOutput('pull-request-head-sha', result.headSha)
|
||||
// Deprecated
|
||||
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
|
||||
core.endGroup()
|
||||
} else {
|
||||
// There is no longer a diff with the base
|
||||
// Check we are in a state where a branch exists
|
||||
if (['updated', 'not-updated'].includes(result.action)) {
|
||||
core.info(
|
||||
`Branch '${inputs.branch}' no longer differs from base branch '${inputs.base}'`
|
||||
)
|
||||
core.info(`Closing pull request and deleting branch '${inputs.branch}'`)
|
||||
await git.push([
|
||||
'--delete',
|
||||
'--force',
|
||||
branchRemoteName,
|
||||
`refs/heads/${inputs.branch}`
|
||||
])
|
||||
if (inputs.deleteBranch) {
|
||||
core.info(`Deleting branch '${inputs.branch}'`)
|
||||
await git.push([
|
||||
'--delete',
|
||||
'--force',
|
||||
branchRemoteName,
|
||||
`refs/heads/${inputs.branch}`
|
||||
])
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-operation', 'closed')
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
core.setFailed(error.message)
|
||||
} finally {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
|
@ -33,7 +33,7 @@ export class GitAuthHelper {
|
||||
try {
|
||||
await this.setExtraheaderConfig(this.persistedExtraheaderConfigValue)
|
||||
core.info('Persisted git credentials restored')
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
core.warning(e)
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ export class GitCommandManager {
|
||||
} else {
|
||||
args.push(ref)
|
||||
}
|
||||
// https://github.com/git/git/commit/a047fafc7866cc4087201e284dc1f53e8f9a32d5
|
||||
args.push('--')
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
@ -96,15 +98,6 @@ export class GitCommandManager {
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async diff(options?: string[]): Promise<string> {
|
||||
const args = ['-c', 'core.pager=cat', 'diff']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
const output = await this.exec(args)
|
||||
return output.stdout.trim()
|
||||
}
|
||||
|
||||
async fetch(
|
||||
refSpec: string[],
|
||||
remoteName?: string,
|
||||
@ -153,18 +146,26 @@ export class GitCommandManager {
|
||||
return this.workingDirectory
|
||||
}
|
||||
|
||||
async hasDiff(options?: string[]): Promise<boolean> {
|
||||
const args = ['diff', '--quiet']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
const output = await this.exec(args, true)
|
||||
return output.exitCode === 1
|
||||
}
|
||||
|
||||
async isDirty(untracked: boolean): Promise<boolean> {
|
||||
const diffArgs = ['--abbrev=40', '--full-index', '--raw']
|
||||
// Check staged changes
|
||||
if (await this.diff([...diffArgs, '--staged'])) {
|
||||
// Check untracked changes
|
||||
if (untracked && (await this.status(['--porcelain', '-unormal']))) {
|
||||
return true
|
||||
}
|
||||
// Check working index changes
|
||||
if (await this.diff(diffArgs)) {
|
||||
if (await this.hasDiff()) {
|
||||
return true
|
||||
}
|
||||
// Check untracked changes
|
||||
if (untracked && (await this.status(['--porcelain', '-unormal']))) {
|
||||
// Check staged changes
|
||||
if (await this.hasDiff(['--staged'])) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -10,6 +10,12 @@ interface Repository {
|
||||
repo: string
|
||||
}
|
||||
|
||||
interface Pull {
|
||||
number: number
|
||||
html_url: string
|
||||
created: boolean
|
||||
}
|
||||
|
||||
export class GitHubHelper {
|
||||
private octokit: InstanceType<typeof Octokit>
|
||||
|
||||
@ -18,6 +24,7 @@ export class GitHubHelper {
|
||||
if (token) {
|
||||
options.auth = `${token}`
|
||||
}
|
||||
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com'
|
||||
this.octokit = new Octokit(options)
|
||||
}
|
||||
|
||||
@ -33,10 +40,11 @@ export class GitHubHelper {
|
||||
inputs: Inputs,
|
||||
baseRepository: string,
|
||||
headBranch: string
|
||||
): Promise<number> {
|
||||
): Promise<Pull> {
|
||||
// Try to create the pull request
|
||||
try {
|
||||
const {data: pull} = await this.octokit.pulls.create({
|
||||
core.info(`Attempting creation of pull request`)
|
||||
const {data: pull} = await this.octokit.rest.pulls.create({
|
||||
...this.parseRepository(baseRepository),
|
||||
title: inputs.title,
|
||||
head: headBranch,
|
||||
@ -47,38 +55,49 @@ export class GitHubHelper {
|
||||
core.info(
|
||||
`Created pull request #${pull.number} (${headBranch} => ${inputs.base})`
|
||||
)
|
||||
return pull.number
|
||||
} catch (e) {
|
||||
return {
|
||||
number: pull.number,
|
||||
html_url: pull.html_url,
|
||||
created: true
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (
|
||||
!e.message ||
|
||||
!e.message.includes(`A pull request already exists for ${headBranch}`)
|
||||
e.message &&
|
||||
e.message.includes(`A pull request already exists for ${headBranch}`)
|
||||
) {
|
||||
core.info(`A pull request already exists for ${headBranch}`)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// Update the pull request that exists for this branch and base
|
||||
const {data: pulls} = await this.octokit.pulls.list({
|
||||
core.info(`Fetching existing pull request`)
|
||||
const {data: pulls} = await this.octokit.rest.pulls.list({
|
||||
...this.parseRepository(baseRepository),
|
||||
state: 'open',
|
||||
head: headBranch,
|
||||
base: inputs.base
|
||||
})
|
||||
const {data: pull} = await this.octokit.pulls.update({
|
||||
core.info(`Attempting update of pull request`)
|
||||
const {data: pull} = await this.octokit.rest.pulls.update({
|
||||
...this.parseRepository(baseRepository),
|
||||
pull_number: pulls[0].number,
|
||||
title: inputs.title,
|
||||
body: inputs.body,
|
||||
draft: inputs.draft
|
||||
body: inputs.body
|
||||
})
|
||||
core.info(
|
||||
`Updated pull request #${pull.number} (${headBranch} => ${inputs.base})`
|
||||
)
|
||||
return pull.number
|
||||
return {
|
||||
number: pull.number,
|
||||
html_url: pull.html_url,
|
||||
created: false
|
||||
}
|
||||
}
|
||||
|
||||
async getRepositoryParent(headRepository: string): Promise<string> {
|
||||
const {data: headRepo} = await this.octokit.repos.get({
|
||||
const {data: headRepo} = await this.octokit.rest.repos.get({
|
||||
...this.parseRepository(headRepository)
|
||||
})
|
||||
if (!headRepo.parent) {
|
||||
@ -93,42 +112,38 @@ export class GitHubHelper {
|
||||
inputs: Inputs,
|
||||
baseRepository: string,
|
||||
headRepository: string
|
||||
): Promise<void> {
|
||||
): Promise<Pull> {
|
||||
const [headOwner] = headRepository.split('/')
|
||||
const headBranch = `${headOwner}:${inputs.branch}`
|
||||
|
||||
// Create or update the pull request
|
||||
const pullNumber = await this.createOrUpdate(
|
||||
inputs,
|
||||
baseRepository,
|
||||
headBranch
|
||||
)
|
||||
const pull = await this.createOrUpdate(inputs, baseRepository, headBranch)
|
||||
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-number', pullNumber)
|
||||
core.exportVariable('PULL_REQUEST_NUMBER', pullNumber)
|
||||
core.endGroup()
|
||||
|
||||
// Set milestone, labels and assignees
|
||||
const updateIssueParams = {}
|
||||
// Apply milestone
|
||||
if (inputs.milestone) {
|
||||
updateIssueParams['milestone'] = inputs.milestone
|
||||
core.info(`Applying milestone '${inputs.milestone}'`)
|
||||
}
|
||||
if (inputs.labels.length > 0) {
|
||||
updateIssueParams['labels'] = inputs.labels
|
||||
core.info(`Applying labels '${inputs.labels}'`)
|
||||
}
|
||||
if (inputs.assignees.length > 0) {
|
||||
updateIssueParams['assignees'] = inputs.assignees
|
||||
core.info(`Applying assignees '${inputs.assignees}'`)
|
||||
}
|
||||
if (Object.keys(updateIssueParams).length > 0) {
|
||||
await this.octokit.issues.update({
|
||||
await this.octokit.rest.issues.update({
|
||||
...this.parseRepository(baseRepository),
|
||||
issue_number: pullNumber,
|
||||
...updateIssueParams
|
||||
issue_number: pull.number,
|
||||
milestone: inputs.milestone
|
||||
})
|
||||
}
|
||||
// Apply labels
|
||||
if (inputs.labels.length > 0) {
|
||||
core.info(`Applying labels '${inputs.labels}'`)
|
||||
await this.octokit.rest.issues.addLabels({
|
||||
...this.parseRepository(baseRepository),
|
||||
issue_number: pull.number,
|
||||
labels: inputs.labels
|
||||
})
|
||||
}
|
||||
// Apply assignees
|
||||
if (inputs.assignees.length > 0) {
|
||||
core.info(`Applying assignees '${inputs.assignees}'`)
|
||||
await this.octokit.rest.issues.addAssignees({
|
||||
...this.parseRepository(baseRepository),
|
||||
issue_number: pull.number,
|
||||
assignees: inputs.assignees
|
||||
})
|
||||
}
|
||||
|
||||
@ -144,12 +159,12 @@ export class GitHubHelper {
|
||||
}
|
||||
if (Object.keys(requestReviewersParams).length > 0) {
|
||||
try {
|
||||
await this.octokit.pulls.requestReviewers({
|
||||
await this.octokit.rest.pulls.requestReviewers({
|
||||
...this.parseRepository(baseRepository),
|
||||
pull_number: pullNumber,
|
||||
pull_number: pull.number,
|
||||
...requestReviewersParams
|
||||
})
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
if (e.message && e.message.includes(ERROR_PR_REVIEW_FROM_AUTHOR)) {
|
||||
core.warning(ERROR_PR_REVIEW_FROM_AUTHOR)
|
||||
} else {
|
||||
@ -157,5 +172,7 @@ export class GitHubHelper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pull
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,13 @@ async function run(): Promise<void> {
|
||||
const inputs: Inputs = {
|
||||
token: core.getInput('token'),
|
||||
path: core.getInput('path'),
|
||||
addPaths: utils.getInputAsArray('add-paths'),
|
||||
commitMessage: core.getInput('commit-message'),
|
||||
committer: core.getInput('committer'),
|
||||
author: core.getInput('author'),
|
||||
signoff: core.getInput('signoff') === 'true',
|
||||
branch: core.getInput('branch'),
|
||||
deleteBranch: core.getInput('delete-branch') === 'true',
|
||||
branchSuffix: core.getInput('branch-suffix'),
|
||||
base: core.getInput('base'),
|
||||
pushToFork: core.getInput('push-to-fork'),
|
||||
@ -28,7 +30,7 @@ async function run(): Promise<void> {
|
||||
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||
|
||||
await createPullRequest(inputs)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,23 @@
|
||||
import {Octokit as Core} from '@octokit/core'
|
||||
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
||||
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
||||
import {HttpsProxyAgent} from 'https-proxy-agent'
|
||||
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
||||
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||
|
||||
export const Octokit = Core.plugin(paginateRest, restEndpointMethods)
|
||||
export const Octokit = Core.plugin(
|
||||
paginateRest,
|
||||
restEndpointMethods,
|
||||
autoProxyAgent
|
||||
)
|
||||
|
||||
// Octokit plugin to support the https_proxy environment variable
|
||||
function autoProxyAgent(octokit: Core) {
|
||||
const proxy = process.env.https_proxy || process.env.HTTPS_PROXY
|
||||
if (!proxy) return
|
||||
|
||||
const agent = new HttpsProxyAgent(proxy)
|
||||
octokit.hook.before('request', options => {
|
||||
options.request.agent = agent
|
||||
})
|
||||
}
|
||||
|
19
src/utils.ts
19
src/utils.ts
@ -39,8 +39,21 @@ interface RemoteDetail {
|
||||
export function getRemoteDetail(remoteUrl: string): RemoteDetail {
|
||||
// Parse the protocol and github repository from a URL
|
||||
// e.g. HTTPS, peter-evans/create-pull-request
|
||||
const httpsUrlPattern = /^https:\/\/.*@?github.com\/(.+\/.+)$/i
|
||||
const sshUrlPattern = /^git@github.com:(.+\/.+).git$/i
|
||||
const githubUrl = process.env['GITHUB_SERVER_URL'] || 'https://github.com'
|
||||
|
||||
const githubServerMatch = githubUrl.match(/^https?:\/\/(.+)$/i)
|
||||
if (!githubServerMatch) {
|
||||
throw new Error('Could not parse GitHub Server name')
|
||||
}
|
||||
|
||||
const httpsUrlPattern = new RegExp(
|
||||
'^https?://.*@?' + githubServerMatch[1] + '/(.+/.+)$',
|
||||
'i'
|
||||
)
|
||||
const sshUrlPattern = new RegExp(
|
||||
'^git@' + githubServerMatch[1] + ':(.+/.+).git$',
|
||||
'i'
|
||||
)
|
||||
|
||||
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
||||
if (httpsMatch) {
|
||||
@ -121,7 +134,7 @@ export function fileExistsSync(path: string): boolean {
|
||||
let stats: fs.Stats
|
||||
try {
|
||||
stats = fs.statSync(path)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return false
|
||||
}
|
||||
|
Reference in New Issue
Block a user