Compare commits
88 Commits
v6-rc
...
signed-com
Author | SHA1 | Date | |
---|---|---|---|
714e9be377 | |||
35dbaca7d5 | |||
8101ce8ff4 | |||
a636b8113c | |||
0db6c3cf7b | |||
36e042a736 | |||
6284ea5854 | |||
03266d3789 | |||
404696dda5 | |||
0e209053e0 | |||
548d90536f | |||
3e7e19f0eb | |||
7c0b09154e | |||
9a6173b25c | |||
13c3ab4d5e | |||
081c241f6e | |||
5bb83f1307 | |||
70815fee7e | |||
93bc7fd9cd | |||
a7dcd19d46 | |||
ed59299f13 | |||
cb4a03daab | |||
ca2ba77cd9 | |||
d884c1e53f | |||
85c93e4595 | |||
cd5c7e4b8b | |||
67f9e1be23 | |||
c5a7806660 | |||
4383ba9ef0 | |||
36f7648874 | |||
5f7c1586fd | |||
db1713da3a | |||
ca98a71ccc | |||
ce008085c8 | |||
7318c0b7b6 | |||
e30bbbb3c9 | |||
bad19b8e0b | |||
098cf60ce4 | |||
1ec1358801 | |||
b5ed4c38bc | |||
b67febc3a3 | |||
7c71392b65 | |||
bdffaf9259 | |||
0a0317b2d8 | |||
d9e8da8566 | |||
59e72ed4d2 | |||
15410bdb79 | |||
ce1b5d49b6 | |||
479b20f068 | |||
8c75f4ab5f | |||
5874ea5902 | |||
6d6857d369 | |||
9153d834b6 | |||
c55203cfde | |||
6ce4eca6b6 | |||
36ef0ed92f | |||
8500972a13 | |||
bda5ade93c | |||
70a41aba78 | |||
57a101480a | |||
b3a2c5d525 | |||
02c7da59e8 | |||
bac6da8071 | |||
a4f52f8033 | |||
853c071bcf | |||
d2c126edc7 | |||
43d39c6836 | |||
5a9d206da2 | |||
e0743ed96c | |||
e1529cb8ab | |||
aad52e87e7 | |||
a64ebdd734 | |||
51b40aff5f | |||
49006b2a60 | |||
b1ddad2c99 | |||
bb809027fd | |||
e0037d470c | |||
94b1f99e3a | |||
69c27eaf4a | |||
7ea722a0f6 | |||
5ee839affd | |||
60fc256c67 | |||
0c67723361 | |||
4e288e851b | |||
278e5302dd | |||
5e5e72ede9 | |||
5ac05dbaf3 | |||
583b402db4 |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 16.x
|
node-version: 20.x
|
||||||
cache: npm
|
cache: npm
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
@ -68,8 +68,8 @@ jobs:
|
|||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
commit-message: '[CI] test ${{ matrix.target }}'
|
commit-message: '[CI] test ${{ matrix.target }}'
|
||||||
committer: GitHub <noreply@github.com>
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||||
title: '[CI] test ${{ matrix.target }}'
|
title: '[CI] test ${{ matrix.target }}'
|
||||||
body: |
|
body: |
|
||||||
- CI test case for target '${{ matrix.target }}'
|
- CI test case for target '${{ matrix.target }}'
|
||||||
@ -92,7 +92,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Find Comment
|
- name: Find Comment
|
||||||
uses: peter-evans/find-comment@v2
|
uses: peter-evans/find-comment@v3
|
||||||
id: fc
|
id: fc
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.number }}
|
issue-number: ${{ github.event.number }}
|
||||||
@ -101,7 +101,7 @@ jobs:
|
|||||||
|
|
||||||
- if: steps.fc.outputs.comment-id == ''
|
- if: steps.fc.outputs.comment-id == ''
|
||||||
name: Create comment
|
name: Create comment
|
||||||
uses: peter-evans/create-or-update-comment@v3
|
uses: peter-evans/create-or-update-comment@v4
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.number }}
|
issue-number: ${{ github.event.number }}
|
||||||
body: |
|
body: |
|
||||||
@ -121,7 +121,7 @@ jobs:
|
|||||||
name: dist
|
name: dist
|
||||||
path: dist
|
path: dist
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||||
commit-message: 'build: update distribution'
|
commit-message: 'build: update distribution'
|
||||||
|
6
.github/workflows/cpr-example-command.yml
vendored
6
.github/workflows/cpr-example-command.yml
vendored
@ -16,8 +16,8 @@ jobs:
|
|||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
commit-message: Update report
|
commit-message: Update report
|
||||||
committer: GitHub <noreply@github.com>
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||||
signoff: false
|
signoff: false
|
||||||
title: '[Example] Update report'
|
title: '[Example] Update report'
|
||||||
body: |
|
body: |
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||||
|
|
||||||
- name: Add reaction
|
- name: Add reaction
|
||||||
uses: peter-evans/create-or-update-comment@v3
|
uses: peter-evans/create-or-update-comment@v4
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
|
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
|
||||||
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
|
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
|
||||||
|
2
.github/workflows/slash-command-dispatch.yml
vendored
2
.github/workflows/slash-command-dispatch.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Slash Command Dispatch
|
- name: Slash Command Dispatch
|
||||||
uses: peter-evans/slash-command-dispatch@v3
|
uses: peter-evans/slash-command-dispatch@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||||
config: >
|
config: >
|
||||||
|
2
.github/workflows/update-major-version.yml
vendored
2
.github/workflows/update-major-version.yml
vendored
@ -11,8 +11,8 @@ on:
|
|||||||
type: choice
|
type: choice
|
||||||
description: The major version tag to update
|
description: The major version tag to update
|
||||||
options:
|
options:
|
||||||
- v4
|
|
||||||
- v5
|
- v5
|
||||||
|
- v6
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tag:
|
tag:
|
||||||
|
35
README.md
35
README.md
@ -21,7 +21,7 @@ Create Pull Request action will:
|
|||||||
|
|
||||||
- [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md)
|
- [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md)
|
||||||
- [Examples](docs/examples.md)
|
- [Examples](docs/examples.md)
|
||||||
- [Updating to v5](docs/updating.md)
|
- [Updating to v6](docs/updating.md)
|
||||||
- [Common issues](docs/common-issues.md)
|
- [Common issues](docs/common-issues.md)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -32,10 +32,10 @@ Create Pull Request action will:
|
|||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v5.x.x`
|
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v6.x.x`
|
||||||
|
|
||||||
### Workflow permissions
|
### Workflow permissions
|
||||||
|
|
||||||
@ -53,11 +53,12 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
|||||||
| Name | Description | Default |
|
| Name | Description | Default |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `token` | `GITHUB_TOKEN` (permissions `contents: write` and `pull-requests: write`) or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
|
| `token` | `GITHUB_TOKEN` (permissions `contents: write` and `pull-requests: write`) or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
|
||||||
|
| `git-token` | The [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that the action will use for git operations. | Defaults to the value of `token` |
|
||||||
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
|
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
|
||||||
| `add-paths` | A comma or newline-separated list of file paths to commit. Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax. If no paths are specified, all new and modified files are added. See [Add specific paths](#add-specific-paths). | |
|
| `add-paths` | A comma or newline-separated list of file paths to commit. Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax. If no paths are specified, all new and modified files are added. See [Add specific paths](#add-specific-paths). | |
|
||||||
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[create-pull-request] automated change` |
|
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[create-pull-request] automated change` |
|
||||||
| `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>` |
|
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the GitHub Actions bot user on github.com. | `github-actions[bot] <41898282+github-actions[bot]@users.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>` |
|
| `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_id }}+${{ 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` |
|
| `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` |
|
| `branch` | The pull request branch name. | `create-pull-request/patch` |
|
||||||
| `delete-branch` | Delete the `branch` if it doesn't have an active pull request associated with it. See [delete-branch](#delete-branch). | `false` |
|
| `delete-branch` | Delete the `branch` if it doesn't have an active pull request associated with it. See [delete-branch](#delete-branch). | `false` |
|
||||||
@ -73,6 +74,7 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
|||||||
| `team-reviewers` | A comma or newline-separated list of GitHub teams to request a review from. Note that a `repo` scoped [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token), or equivalent [GitHub App permissions](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens), are required. | |
|
| `team-reviewers` | A comma or newline-separated list of GitHub teams to request a review from. Note that a `repo` scoped [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token), or equivalent [GitHub App permissions](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens), are required. | |
|
||||||
| `milestone` | The number of the milestone to associate this pull request with. | |
|
| `milestone` | The number of the milestone to associate this pull request with. | |
|
||||||
| `draft` | Create a [draft pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). It is not possible to change draft status after creation except through the web interface. | `false` |
|
| `draft` | Create a [draft pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). It is not possible to change draft status after creation except through the web interface. | `false` |
|
||||||
|
| `sign-commit` | Sign the commit as bot [refer: [Signature verification for bots](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#signature-verification-for-bots)]. This can be useful if your repo or org has enforced commit-signing. | `false` |
|
||||||
|
|
||||||
#### commit-message
|
#### commit-message
|
||||||
|
|
||||||
@ -90,7 +92,11 @@ In addition to a message, the `commit-message` input can also be used to populat
|
|||||||
#### delete-branch
|
#### delete-branch
|
||||||
|
|
||||||
The `delete-branch` feature doesn't delete branches immediately on merge. (It can't do that because it would require the merge to somehow trigger the action.)
|
The `delete-branch` feature doesn't delete branches immediately on merge. (It can't do that because it would require the merge to somehow trigger the action.)
|
||||||
The intention of the feature is that when the action next runs it will delete the `branch` if it doesn't have an active pull request associated with it.
|
The intention of the feature is that when the action next runs it will delete the `branch` if there is no diff.
|
||||||
|
|
||||||
|
Enabling this feature leads to the following behaviour:
|
||||||
|
1. If a pull request was merged and the branch is left undeleted, when the action next runs it will delete the branch if there is no further diff.
|
||||||
|
2. If a pull request is open, but there is now no longer a diff and the PR is unnecessary, the action will delete the branch causing the PR to close.
|
||||||
|
|
||||||
If you want branches to be deleted immediately on merge then you should use GitHub's `Automatically delete head branches` feature in your repository settings.
|
If you want branches to be deleted immediately on merge then you should use GitHub's `Automatically delete head branches` feature in your repository settings.
|
||||||
|
|
||||||
@ -99,7 +105,7 @@ If you want branches to be deleted immediately on merge then you should use GitH
|
|||||||
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
env:
|
env:
|
||||||
https_proxy: http://<proxy_address>:<port>
|
https_proxy: http://<proxy_address>:<port>
|
||||||
```
|
```
|
||||||
@ -112,6 +118,7 @@ The following outputs can be used by subsequent workflow steps.
|
|||||||
- `pull-request-url` - The URL of the pull request.
|
- `pull-request-url` - The URL of the pull request.
|
||||||
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated` or `closed`.
|
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated` or `closed`.
|
||||||
- `pull-request-head-sha` - The commit SHA of the pull request branch.
|
- `pull-request-head-sha` - The commit SHA of the pull request branch.
|
||||||
|
- `pull-request-branch` - The branch name of the pull request.
|
||||||
|
|
||||||
Step outputs can be accessed as in the following example.
|
Step outputs can be accessed as in the following example.
|
||||||
Note that in order to read the step outputs the action step must have an id.
|
Note that in order to read the step outputs the action step must have an id.
|
||||||
@ -119,7 +126,7 @@ Note that in order to read the step outputs the action step must have an id.
|
|||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
- name: Check outputs
|
- name: Check outputs
|
||||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||||
run: |
|
run: |
|
||||||
@ -182,7 +189,7 @@ File changes that do not match one of the paths will be stashed and restored aft
|
|||||||
|
|
||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
add-paths: |
|
add-paths: |
|
||||||
*.java
|
*.java
|
||||||
@ -209,7 +216,7 @@ Note that the repository must be checked out on a branch with a remote, it won't
|
|||||||
- name: Uncommitted change
|
- name: Uncommitted change
|
||||||
run: date +%s > report.txt
|
run: date +%s > report.txt
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create a project card
|
### Create a project card
|
||||||
@ -219,7 +226,7 @@ To create a project card for the pull request, pass the `pull-request-number` st
|
|||||||
```yml
|
```yml
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
|
||||||
- name: Create or Update Project Card
|
- name: Create or Update Project Card
|
||||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||||
@ -254,12 +261,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update report
|
commit-message: Update report
|
||||||
committer: GitHub <noreply@github.com>
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||||
signoff: false
|
signoff: false
|
||||||
branch: example-patches
|
branch: example-patches
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
createOrUpdateBranch,
|
createOrUpdateBranch,
|
||||||
tryFetch,
|
tryFetch,
|
||||||
getWorkingBaseAndType
|
getWorkingBaseAndType,
|
||||||
|
buildBranchFileChanges
|
||||||
} from '../lib/create-or-update-branch'
|
} from '../lib/create-or-update-branch'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import {GitCommandManager} from '../lib/git-command-manager'
|
import {GitCommandManager} from '../lib/git-command-manager'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import {v4 as uuidv4} from 'uuid'
|
import {v4 as uuidv4} from 'uuid'
|
||||||
|
|
||||||
const REPO_PATH = '/git/local/test-base'
|
const REPO_PATH = '/git/local/repos/test-base'
|
||||||
const REMOTE_NAME = 'origin'
|
const REMOTE_NAME = 'origin'
|
||||||
|
|
||||||
const TRACKED_FILE = 'a/tracked-file.txt'
|
const TRACKED_FILE = 'a/tracked-file.txt'
|
||||||
@ -22,7 +23,7 @@ const INIT_COMMIT_MESSAGE = 'Add file to be a tracked file for tests'
|
|||||||
const BRANCH = 'tests/create-pull-request/patch'
|
const BRANCH = 'tests/create-pull-request/patch'
|
||||||
const BASE = DEFAULT_BRANCH
|
const BASE = DEFAULT_BRANCH
|
||||||
|
|
||||||
const FORK_REMOTE_URL = 'git://127.0.0.1/test-fork.git'
|
const FORK_REMOTE_URL = 'git://127.0.0.1/repos/test-fork.git'
|
||||||
const FORK_REMOTE_NAME = 'fork'
|
const FORK_REMOTE_NAME = 'fork'
|
||||||
|
|
||||||
const ADD_PATHS_DEFAULT = []
|
const ADD_PATHS_DEFAULT = []
|
||||||
@ -140,10 +141,22 @@ describe('create-or-update-branch tests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function beforeTest(): Promise<void> {
|
async function beforeTest(): Promise<void> {
|
||||||
|
await git.fetch(
|
||||||
|
[`${DEFAULT_BRANCH}:${DEFAULT_BRANCH}`],
|
||||||
|
REMOTE_NAME,
|
||||||
|
['--force', '--update-head-ok'],
|
||||||
|
true
|
||||||
|
)
|
||||||
await git.checkout(DEFAULT_BRANCH)
|
await git.checkout(DEFAULT_BRANCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function afterTest(deleteRemote = true): Promise<void> {
|
async function afterTest(deleteRemote = true): Promise<void> {
|
||||||
|
await git.fetch(
|
||||||
|
[`${DEFAULT_BRANCH}:${DEFAULT_BRANCH}`],
|
||||||
|
REMOTE_NAME,
|
||||||
|
['--force', '--update-head-ok'],
|
||||||
|
true
|
||||||
|
)
|
||||||
await git.checkout(DEFAULT_BRANCH)
|
await git.checkout(DEFAULT_BRANCH)
|
||||||
try {
|
try {
|
||||||
// Get the upstream branch if it exists
|
// Get the upstream branch if it exists
|
||||||
@ -198,8 +211,8 @@ describe('create-or-update-branch tests', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it('tests if a branch exists and can be fetched', async () => {
|
it('tests if a branch exists and can be fetched', async () => {
|
||||||
expect(await tryFetch(git, REMOTE_NAME, NOT_BASE_BRANCH)).toBeTruthy()
|
expect(await tryFetch(git, REMOTE_NAME, NOT_BASE_BRANCH, 1)).toBeTruthy()
|
||||||
expect(await tryFetch(git, REMOTE_NAME, NOT_EXIST_BRANCH)).toBeFalsy()
|
expect(await tryFetch(git, REMOTE_NAME, NOT_EXIST_BRANCH, 1)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests getWorkingBaseAndType on a checked out ref', async () => {
|
it('tests getWorkingBaseAndType on a checked out ref', async () => {
|
||||||
@ -217,6 +230,80 @@ describe('create-or-update-branch tests', () => {
|
|||||||
expect(workingBaseType).toEqual('commit')
|
expect(workingBaseType).toEqual('commit')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchFileChanges with no diff', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||||
|
expect(branchFileChanges.additions.length).toEqual(0)
|
||||||
|
expect(branchFileChanges.deletions.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchFileChanges with addition and modification', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
const changes = await createChanges()
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', 'Test changes'])
|
||||||
|
|
||||||
|
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||||
|
|
||||||
|
expect(branchFileChanges.additions).toEqual([
|
||||||
|
{
|
||||||
|
path: TRACKED_FILE,
|
||||||
|
contents: Buffer.from(changes.tracked, 'binary').toString('base64')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: UNTRACKED_FILE,
|
||||||
|
contents: Buffer.from(changes.untracked, 'binary').toString('base64')
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(branchFileChanges.deletions.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchFileChanges with addition and deletion', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
const changes = await createChanges()
|
||||||
|
const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt'
|
||||||
|
const filepath = path.join(REPO_PATH, TRACKED_FILE_NEW_PATH)
|
||||||
|
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
|
||||||
|
await fs.promises.rename(path.join(REPO_PATH, TRACKED_FILE), filepath)
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', 'Test changes'])
|
||||||
|
|
||||||
|
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||||
|
|
||||||
|
expect(branchFileChanges.additions).toEqual([
|
||||||
|
{
|
||||||
|
path: UNTRACKED_FILE,
|
||||||
|
contents: Buffer.from(changes.untracked, 'binary').toString('base64')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: TRACKED_FILE_NEW_PATH,
|
||||||
|
contents: Buffer.from(changes.tracked, 'binary').toString('base64')
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(branchFileChanges.deletions).toEqual([{path: TRACKED_FILE}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests buildBranchFileChanges with binary files', async () => {
|
||||||
|
await git.checkout(BRANCH, BASE)
|
||||||
|
const filename = 'c/untracked-binary-file'
|
||||||
|
const filepath = path.join(REPO_PATH, filename)
|
||||||
|
const binaryData = Buffer.from([0x00, 0xff, 0x10, 0x20])
|
||||||
|
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
|
||||||
|
await fs.promises.writeFile(filepath, binaryData)
|
||||||
|
await git.exec(['add', '-A'])
|
||||||
|
await git.commit(['-m', 'Test changes'])
|
||||||
|
|
||||||
|
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||||
|
|
||||||
|
expect(branchFileChanges.additions).toEqual([
|
||||||
|
{
|
||||||
|
path: filename,
|
||||||
|
contents: binaryData.toString('base64')
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(branchFileChanges.deletions.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
it('tests no changes resulting in no new branch being created', async () => {
|
it('tests no changes resulting in no new branch being created', async () => {
|
||||||
const commitMessage = uuidv4()
|
const commitMessage = uuidv4()
|
||||||
const result = await createOrUpdateBranch(
|
const result = await createOrUpdateBranch(
|
||||||
@ -1454,8 +1541,7 @@ describe('create-or-update-branch tests', () => {
|
|||||||
expect(
|
expect(
|
||||||
await gitLogMatches([
|
await gitLogMatches([
|
||||||
_commitMessage,
|
_commitMessage,
|
||||||
...commits.commitMsgs,
|
commits.commitMsgs[0] // fetch depth of base is 1
|
||||||
INIT_COMMIT_MESSAGE
|
|
||||||
])
|
])
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
@ -1590,7 +1676,9 @@ describe('create-or-update-branch tests', () => {
|
|||||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||||
expect(
|
expect(
|
||||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
await gitLogMatches([
|
||||||
|
commits.commitMsgs[0] // fetch depth of base is 1
|
||||||
|
])
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1668,7 +1756,9 @@ describe('create-or-update-branch tests', () => {
|
|||||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||||
expect(
|
expect(
|
||||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
await gitLogMatches([
|
||||||
|
commits.commitMsgs[0] // fetch depth of base is 1
|
||||||
|
])
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1951,8 +2041,7 @@ describe('create-or-update-branch tests', () => {
|
|||||||
await gitLogMatches([
|
await gitLogMatches([
|
||||||
_commitMessage,
|
_commitMessage,
|
||||||
..._commits.commitMsgs,
|
..._commits.commitMsgs,
|
||||||
...commitsOnBase.commitMsgs,
|
commitsOnBase.commitMsgs[0] // fetch depth of base is 1
|
||||||
INIT_COMMIT_MESSAGE
|
|
||||||
])
|
])
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
@ -2147,8 +2236,7 @@ describe('create-or-update-branch tests', () => {
|
|||||||
expect(
|
expect(
|
||||||
await gitLogMatches([
|
await gitLogMatches([
|
||||||
_commitMessage,
|
_commitMessage,
|
||||||
...commitsOnBase.commitMsgs,
|
commitsOnBase.commitMsgs[0] // fetch depth of base is 1
|
||||||
INIT_COMMIT_MESSAGE
|
|
||||||
])
|
])
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
@ -5,18 +5,18 @@ set -euo pipefail
|
|||||||
WORKINGDIR=$PWD
|
WORKINGDIR=$PWD
|
||||||
|
|
||||||
# Create and serve a remote repo
|
# Create and serve a remote repo
|
||||||
mkdir -p /git/remote
|
mkdir -p /git/remote/repos
|
||||||
git config --global init.defaultBranch main
|
git config --global init.defaultBranch main
|
||||||
git init --bare /git/remote/test-base.git
|
git init --bare /git/remote/repos/test-base.git
|
||||||
git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all /git/remote &>/dev/null &
|
git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all /git/remote &>/dev/null &
|
||||||
|
|
||||||
# Give the daemon time to start
|
# Give the daemon time to start
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# Create a local clone and make an initial commit
|
# Create a local clone and make an initial commit
|
||||||
mkdir -p /git/local
|
mkdir -p /git/local/repos
|
||||||
git clone git://127.0.0.1/test-base.git /git/local/test-base
|
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
||||||
cd /git/local/test-base
|
cd /git/local/repos/test-base
|
||||||
git config --global user.email "you@example.com"
|
git config --global user.email "you@example.com"
|
||||||
git config --global user.name "Your Name"
|
git config --global user.name "Your Name"
|
||||||
echo "#test-base" > README.md
|
echo "#test-base" > README.md
|
||||||
@ -30,8 +30,8 @@ git config -l
|
|||||||
|
|
||||||
# Clone a server-side fork of the base repo
|
# Clone a server-side fork of the base repo
|
||||||
cd $WORKINGDIR
|
cd $WORKINGDIR
|
||||||
git clone --mirror git://127.0.0.1/test-base.git /git/remote/test-fork.git
|
git clone --mirror git://127.0.0.1/repos/test-base.git /git/remote/repos/test-fork.git
|
||||||
cd /git/remote/test-fork.git
|
cd /git/remote/repos/test-fork.git
|
||||||
git log -1 --pretty=oneline
|
git log -1 --pretty=oneline
|
||||||
|
|
||||||
# Restore the working directory
|
# Restore the working directory
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import {GitCommandManager} from '../lib/git-command-manager'
|
import {GitCommandManager} from '../lib/git-command-manager'
|
||||||
import {GitAuthHelper} from '../lib/git-auth-helper'
|
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||||
|
|
||||||
const REPO_PATH = '/git/local/test-base'
|
const REPO_PATH = '/git/local/repos/test-base'
|
||||||
|
|
||||||
const extraheaderConfigKey = 'http.https://github.com/.extraheader'
|
const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||||
|
|
||||||
describe('git-auth-helper tests', () => {
|
describe('git-config-helper integration tests', () => {
|
||||||
let git: GitCommandManager
|
let git: GitCommandManager
|
||||||
let gitAuthHelper: GitAuthHelper
|
let gitConfigHelper: GitConfigHelper
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
git = await GitCommandManager.create(REPO_PATH)
|
git = await GitCommandManager.create(REPO_PATH)
|
||||||
gitAuthHelper = new GitAuthHelper(git)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests save and restore with no persisted auth', async () => {
|
it('tests save and restore with no persisted auth', async () => {
|
||||||
await gitAuthHelper.savePersistedAuth()
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
await gitAuthHelper.restorePersistedAuth()
|
await gitConfigHelper.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests configure and removal of auth', async () => {
|
it('tests configure and removal of auth', async () => {
|
||||||
await gitAuthHelper.configureToken('github-token')
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
await gitConfigHelper.configureToken('github-token')
|
||||||
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
||||||
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
||||||
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
||||||
)
|
)
|
||||||
|
|
||||||
await gitAuthHelper.removeAuth()
|
await gitConfigHelper.close()
|
||||||
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -34,37 +34,53 @@ describe('git-auth-helper tests', () => {
|
|||||||
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
||||||
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
||||||
|
|
||||||
await gitAuthHelper.savePersistedAuth()
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
|
||||||
const exists = await git.configExists(extraheaderConfigKey)
|
const exists = await git.configExists(extraheaderConfigKey)
|
||||||
expect(exists).toBeFalsy()
|
expect(exists).toBeFalsy()
|
||||||
|
|
||||||
await gitAuthHelper.restorePersistedAuth()
|
await gitConfigHelper.close()
|
||||||
|
|
||||||
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
||||||
expect(configValue).toEqual(extraheaderConfigValue)
|
expect(configValue).toEqual(extraheaderConfigValue)
|
||||||
|
|
||||||
await gitAuthHelper.removeAuth()
|
const unset = await git.tryConfigUnset(
|
||||||
|
extraheaderConfigKey,
|
||||||
|
'^AUTHORIZATION:'
|
||||||
|
)
|
||||||
|
expect(unset).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests not adding/removing the safe.directory config when it already exists', async () => {
|
||||||
|
await git.config('safe.directory', '/another-value', true, true)
|
||||||
|
|
||||||
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await git.configExists('safe.directory', '/another-value', true)
|
||||||
|
).toBeTruthy()
|
||||||
|
|
||||||
|
await gitConfigHelper.close()
|
||||||
|
|
||||||
|
const unset = await git.tryConfigUnset(
|
||||||
|
'safe.directory',
|
||||||
|
'/another-value',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
expect(unset).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests adding and removing the safe.directory config', async () => {
|
it('tests adding and removing the safe.directory config', async () => {
|
||||||
await git.config('safe.directory', '/another-value', true, true)
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
|
||||||
await gitAuthHelper.removeSafeDirectory()
|
|
||||||
await gitAuthHelper.addSafeDirectory()
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await git.configExists('safe.directory', REPO_PATH, true)
|
await git.configExists('safe.directory', REPO_PATH, true)
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
|
|
||||||
await gitAuthHelper.addSafeDirectory()
|
await gitConfigHelper.close()
|
||||||
await gitAuthHelper.removeSafeDirectory()
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await git.configExists('safe.directory', REPO_PATH, true)
|
await git.configExists('safe.directory', REPO_PATH, true)
|
||||||
).toBeFalsy()
|
).toBeFalsy()
|
||||||
expect(
|
|
||||||
await git.configExists('safe.directory', '/another-value', true)
|
|
||||||
).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
93
__test__/git-config-helper.unit.test.ts
Normal file
93
__test__/git-config-helper.unit.test.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||||
|
|
||||||
|
describe('git-config-helper unit tests', () => {
|
||||||
|
test('parseGitRemote successfully parses HTTPS remote URLs', async () => {
|
||||||
|
const remote1 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/create-pull-request'
|
||||||
|
)
|
||||||
|
expect(remote1.hostname).toEqual('github.com')
|
||||||
|
expect(remote1.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote2 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
||||||
|
)
|
||||||
|
expect(remote2.hostname).toEqual('github.com')
|
||||||
|
expect(remote2.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote3 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/create-pull-request.git'
|
||||||
|
)
|
||||||
|
expect(remote3.hostname).toEqual('github.com')
|
||||||
|
expect(remote3.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote4 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/ungit'
|
||||||
|
)
|
||||||
|
expect(remote4.hostname).toEqual('github.com')
|
||||||
|
expect(remote4.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote4.repository).toEqual('peter-evans/ungit')
|
||||||
|
|
||||||
|
const remote5 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/ungit.git'
|
||||||
|
)
|
||||||
|
expect(remote5.hostname).toEqual('github.com')
|
||||||
|
expect(remote5.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote5.repository).toEqual('peter-evans/ungit')
|
||||||
|
|
||||||
|
const remote6 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.internal.company/peter-evans/create-pull-request'
|
||||||
|
)
|
||||||
|
expect(remote6.hostname).toEqual('github.internal.company')
|
||||||
|
expect(remote6.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote6.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parseGitRemote successfully parses SSH remote URLs', async () => {
|
||||||
|
const remote1 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git@github.com:peter-evans/create-pull-request.git'
|
||||||
|
)
|
||||||
|
expect(remote1.hostname).toEqual('github.com')
|
||||||
|
expect(remote1.protocol).toEqual('SSH')
|
||||||
|
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote2 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git@github.com:peter-evans/ungit.git'
|
||||||
|
)
|
||||||
|
expect(remote2.hostname).toEqual('github.com')
|
||||||
|
expect(remote2.protocol).toEqual('SSH')
|
||||||
|
expect(remote2.repository).toEqual('peter-evans/ungit')
|
||||||
|
|
||||||
|
const remote3 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git@github.internal.company:peter-evans/create-pull-request.git'
|
||||||
|
)
|
||||||
|
expect(remote3.hostname).toEqual('github.internal.company')
|
||||||
|
expect(remote3.protocol).toEqual('SSH')
|
||||||
|
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parseGitRemote successfully parses GIT remote URLs', async () => {
|
||||||
|
// Unauthenticated git protocol for integration tests only
|
||||||
|
const remote1 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git://127.0.0.1/repos/test-base.git'
|
||||||
|
)
|
||||||
|
expect(remote1.hostname).toEqual('127.0.0.1')
|
||||||
|
expect(remote1.protocol).toEqual('GIT')
|
||||||
|
expect(remote1.repository).toEqual('repos/test-base')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parseGitRemote fails to parse a remote URL', async () => {
|
||||||
|
const remoteUrl = 'https://github.com/peter-evans'
|
||||||
|
try {
|
||||||
|
GitConfigHelper.parseGitRemote(remoteUrl)
|
||||||
|
// Fail the test if an error wasn't thrown
|
||||||
|
expect(true).toEqual(false)
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).toEqual(
|
||||||
|
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
@ -8,7 +8,7 @@ if [[ "$(docker images -q $IMAGE 2> /dev/null)" == "" || $ARG1 == "build" ]]; th
|
|||||||
echo "Building Docker image $IMAGE ..."
|
echo "Building Docker image $IMAGE ..."
|
||||||
|
|
||||||
cat > Dockerfile << EOF
|
cat > Dockerfile << EOF
|
||||||
FROM node:16-alpine
|
FROM node:20-alpine
|
||||||
RUN apk --no-cache add git git-daemon
|
RUN apk --no-cache add git git-daemon
|
||||||
RUN npm install jest jest-environment-jsdom --global
|
RUN npm install jest jest-environment-jsdom --global
|
||||||
WORKDIR /cpr
|
WORKDIR /cpr
|
||||||
|
@ -44,63 +44,6 @@ describe('utils tests', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('getRemoteDetail successfully parses remote URLs', async () => {
|
|
||||||
const remote1 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/create-pull-request'
|
|
||||||
)
|
|
||||||
expect(remote1.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote2 = utils.getRemoteDetail(
|
|
||||||
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
|
||||||
)
|
|
||||||
expect(remote2.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote3 = utils.getRemoteDetail(
|
|
||||||
'git@github.com:peter-evans/create-pull-request.git'
|
|
||||||
)
|
|
||||||
expect(remote3.protocol).toEqual('SSH')
|
|
||||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote4 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/create-pull-request.git'
|
|
||||||
)
|
|
||||||
expect(remote4.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote4.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote5 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/ungit'
|
|
||||||
)
|
|
||||||
expect(remote5.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote5.repository).toEqual('peter-evans/ungit')
|
|
||||||
|
|
||||||
const remote6 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/ungit.git'
|
|
||||||
)
|
|
||||||
expect(remote6.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote6.repository).toEqual('peter-evans/ungit')
|
|
||||||
|
|
||||||
const remote7 = utils.getRemoteDetail(
|
|
||||||
'git@github.com:peter-evans/ungit.git'
|
|
||||||
)
|
|
||||||
expect(remote7.protocol).toEqual('SSH')
|
|
||||||
expect(remote7.repository).toEqual('peter-evans/ungit')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('getRemoteDetail fails to parse a remote URL', async () => {
|
|
||||||
const remoteUrl = 'https://github.com/peter-evans'
|
|
||||||
try {
|
|
||||||
utils.getRemoteDetail(remoteUrl)
|
|
||||||
// Fail the test if an error wasn't thrown
|
|
||||||
expect(true).toEqual(false)
|
|
||||||
} catch (e: any) {
|
|
||||||
expect(e.message).toEqual(
|
|
||||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test('getRemoteUrl successfully returns remote URLs', async () => {
|
test('getRemoteUrl successfully returns remote URLs', async () => {
|
||||||
const url1 = utils.getRemoteUrl(
|
const url1 = utils.getRemoteUrl(
|
||||||
'HTTPS',
|
'HTTPS',
|
||||||
|
15
action.yml
15
action.yml
@ -4,6 +4,10 @@ inputs:
|
|||||||
token:
|
token:
|
||||||
description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
|
description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
|
git-token:
|
||||||
|
description: >
|
||||||
|
The Personal Access Token (PAT) that the action will use for git operations.
|
||||||
|
Defaults to the value of `token`.
|
||||||
path:
|
path:
|
||||||
description: >
|
description: >
|
||||||
Relative path under $GITHUB_WORKSPACE to the repository.
|
Relative path under $GITHUB_WORKSPACE to the repository.
|
||||||
@ -20,12 +24,12 @@ inputs:
|
|||||||
description: >
|
description: >
|
||||||
The committer name and email address in the format `Display Name <email@address.com>`.
|
The committer name and email address in the format `Display Name <email@address.com>`.
|
||||||
Defaults to the GitHub Actions bot user.
|
Defaults to the GitHub Actions bot user.
|
||||||
default: 'GitHub <noreply@github.com>'
|
default: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
|
||||||
author:
|
author:
|
||||||
description: >
|
description: >
|
||||||
The author name and email address in the format `Display Name <email@address.com>`.
|
The author name and email address in the format `Display Name <email@address.com>`.
|
||||||
Defaults to the user who triggered the workflow run.
|
Defaults to the user who triggered the workflow run.
|
||||||
default: '${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>'
|
default: '${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>'
|
||||||
signoff:
|
signoff:
|
||||||
description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.'
|
description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.'
|
||||||
default: false
|
default: false
|
||||||
@ -70,6 +74,9 @@ inputs:
|
|||||||
draft:
|
draft:
|
||||||
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
|
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
|
||||||
default: false
|
default: false
|
||||||
|
sign-commit:
|
||||||
|
description: 'Sign the commit as github-actions bot (and as custom app if a different github-token is provided)'
|
||||||
|
default: true
|
||||||
outputs:
|
outputs:
|
||||||
pull-request-number:
|
pull-request-number:
|
||||||
description: 'The pull request number'
|
description: 'The pull request number'
|
||||||
@ -79,8 +86,10 @@ outputs:
|
|||||||
description: 'The pull request operation performed by the action, `created`, `updated` or `closed`.'
|
description: 'The pull request operation performed by the action, `created`, `updated` or `closed`.'
|
||||||
pull-request-head-sha:
|
pull-request-head-sha:
|
||||||
description: 'The commit SHA of the pull request branch.'
|
description: 'The commit SHA of the pull request branch.'
|
||||||
|
pull-request-branch:
|
||||||
|
description: 'The pull request branch name'
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node20'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
branding:
|
branding:
|
||||||
icon: 'git-pull-request'
|
icon: 'git-pull-request'
|
||||||
|
53879
dist/index.js
vendored
53879
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
@ -35,7 +35,12 @@ The reason is that I'm trying very hard to keep the interface for this action to
|
|||||||
Git hooks must be installed after a repository is checked out in order for them to work.
|
Git hooks must be installed after a repository is checked out in order for them to work.
|
||||||
So the straightforward solution is to just not install them during the workflow where this action is used.
|
So the straightforward solution is to just not install them during the workflow where this action is used.
|
||||||
|
|
||||||
- If hooks are automatically enabled by a framework, use an option provided by the framework to disable them. For example, for Husky users, they can be disabled with the `--ignore-scripts` flag.
|
- If hooks are automatically enabled by a framework, use an option provided by the framework to disable them. For example, for Husky users, they can be disabled with the `--ignore-scripts` flag, or by setting the `HUSKY` environment variable when the action runs.
|
||||||
|
```yml
|
||||||
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
env:
|
||||||
|
HUSKY: '0'
|
||||||
|
```
|
||||||
- If hooks are installed in a script, then add a condition checking if the `CI` environment variable exists.
|
- If hooks are installed in a script, then add a condition checking if the `CI` environment variable exists.
|
||||||
```sh
|
```sh
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
@ -88,7 +88,7 @@ In these cases, you *must supply* the `base` input so the action can rebase chan
|
|||||||
Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request) events will by default check out a merge commit. Set the `base` input as follows to base the new pull request on the current pull request's branch.
|
Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request) events will by default check out a merge commit. Set the `base` input as follows to base the new pull request on the current pull request's branch.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
- uses: peter-evans/create-pull-request@v5
|
- uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
base: ${{ github.head_ref }}
|
base: ${{ github.head_ref }}
|
||||||
```
|
```
|
||||||
@ -96,7 +96,7 @@ Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/refer
|
|||||||
Workflows triggered by [`release`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#release) events will by default check out a tag. For most use cases, you will need to set the `base` input to the branch name of the tagged commit.
|
Workflows triggered by [`release`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#release) events will by default check out a tag. For most use cases, you will need to set the `base` input to the branch name of the tagged commit.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
- uses: peter-evans/create-pull-request@v5
|
- uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
base: main
|
base: main
|
||||||
```
|
```
|
||||||
@ -180,7 +180,7 @@ Checking out a branch from a different repository from where the workflow is exe
|
|||||||
|
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- uses: peter-evans/create-pull-request@v5
|
- uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
```
|
```
|
||||||
@ -207,7 +207,7 @@ How to use SSH (deploy keys) with create-pull-request action:
|
|||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
```
|
```
|
||||||
|
|
||||||
### Push pull request branches to a fork
|
### Push pull request branches to a fork
|
||||||
@ -230,7 +230,7 @@ Note that if you choose to use this method (not give the machine account `write`
|
|||||||
|
|
||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- uses: peter-evans/create-pull-request@v5
|
- uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MACHINE_USER_PAT }}
|
token: ${{ secrets.MACHINE_USER_PAT }}
|
||||||
push-to-fork: machine-user/fork-of-repository
|
push-to-fork: machine-user/fork-of-repository
|
||||||
@ -275,7 +275,7 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
|||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
```
|
```
|
||||||
@ -316,7 +316,7 @@ The action can use GPG to sign commits with a GPG key that you generate yourself
|
|||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
committer: example <email@example.com>
|
committer: example <email@example.com>
|
||||||
@ -346,7 +346,7 @@ jobs:
|
|||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
```
|
```
|
||||||
|
|
||||||
**Ubuntu container example:**
|
**Ubuntu container example:**
|
||||||
@ -369,5 +369,5 @@ jobs:
|
|||||||
# Make changes to pull request here
|
# Make changes to pull request here
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
```
|
```
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
- [autopep8](#autopep8)
|
- [autopep8](#autopep8)
|
||||||
- [Misc workflow tips](#misc-workflow-tips)
|
- [Misc workflow tips](#misc-workflow-tips)
|
||||||
- [Filtering push events](#filtering-push-events)
|
- [Filtering push events](#filtering-push-events)
|
||||||
- [Bypassing git hooks](#bypassing-git-hooks)
|
|
||||||
- [Dynamic configuration using variables](#dynamic-configuration-using-variables)
|
- [Dynamic configuration using variables](#dynamic-configuration-using-variables)
|
||||||
- [Using a markdown template](#using-a-markdown-template)
|
- [Using a markdown template](#using-a-markdown-template)
|
||||||
- [Debugging GitHub Actions](#debugging-github-actions)
|
- [Debugging GitHub Actions](#debugging-github-actions)
|
||||||
@ -50,7 +49,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git log --format='%aN <%aE>%n%cN <%cE>' | sort -u > AUTHORS
|
git log --format='%aN <%aE>%n%cN <%cE>' | sort -u > AUTHORS
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
commit-message: update authors
|
commit-message: update authors
|
||||||
title: Update AUTHORS
|
title: Update AUTHORS
|
||||||
@ -82,7 +81,7 @@ jobs:
|
|||||||
git fetch origin main:main
|
git fetch origin main:main
|
||||||
git reset --hard main
|
git reset --hard main
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
branch: production-promotion
|
branch: production-promotion
|
||||||
```
|
```
|
||||||
@ -117,7 +116,7 @@ jobs:
|
|||||||
./git-chglog -o CHANGELOG.md
|
./git-chglog -o CHANGELOG.md
|
||||||
rm git-chglog
|
rm git-chglog
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
commit-message: update changelog
|
commit-message: update changelog
|
||||||
title: Update Changelog
|
title: Update Changelog
|
||||||
@ -154,7 +153,7 @@ jobs:
|
|||||||
npx -p npm-check-updates ncu -u
|
npx -p npm-check-updates ncu -u
|
||||||
npm install
|
npm install
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update dependencies
|
commit-message: Update dependencies
|
||||||
@ -215,7 +214,7 @@ jobs:
|
|||||||
- name: Perform dependency resolution and write new lockfiles
|
- name: Perform dependency resolution and write new lockfiles
|
||||||
run: ./gradlew dependencies --write-locks
|
run: ./gradlew dependencies --write-locks
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update dependencies
|
commit-message: Update dependencies
|
||||||
@ -250,7 +249,7 @@ jobs:
|
|||||||
cargo update
|
cargo update
|
||||||
cargo upgrade --to-lockfile
|
cargo upgrade --to-lockfile
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update dependencies
|
commit-message: Update dependencies
|
||||||
@ -308,7 +307,7 @@ jobs:
|
|||||||
# Update current release
|
# Update current release
|
||||||
echo ${{ steps.swagger-ui.outputs.release_tag }} > swagger-ui.version
|
echo ${{ steps.swagger-ui.outputs.release_tag }} > swagger-ui.version
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||||
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||||
@ -352,7 +351,7 @@ jobs:
|
|||||||
git fetch upstream main:upstream-main
|
git fetch upstream main:upstream-main
|
||||||
git reset --hard upstream-main
|
git reset --hard upstream-main
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
branch: upstream-changes
|
branch: upstream-changes
|
||||||
@ -385,7 +384,7 @@ jobs:
|
|||||||
--domains quotes.toscrape.com \
|
--domains quotes.toscrape.com \
|
||||||
http://quotes.toscrape.com/
|
http://quotes.toscrape.com/
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
commit-message: update local website copy
|
commit-message: update local website copy
|
||||||
title: Automated Updates to Local Website Copy
|
title: Automated Updates to Local Website Copy
|
||||||
@ -482,7 +481,7 @@ jobs:
|
|||||||
echo "branch-name=$branch-name" >> $GITHUB_OUTPUT
|
echo "branch-name=$branch-name" >> $GITHUB_OUTPUT
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: steps.autopep8.outputs.exit-code == 2
|
if: steps.autopep8.outputs.exit-code == 2
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
commit-message: autopep8 action fixes
|
commit-message: autopep8 action fixes
|
||||||
title: Fixes by autopep8 action
|
title: Fixes by autopep8 action
|
||||||
@ -526,18 +525,6 @@ jobs:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bypassing git hooks
|
|
||||||
|
|
||||||
If you have git hooks that prevent the action from working correctly you can remove them before running the action.
|
|
||||||
|
|
||||||
```yml
|
|
||||||
# Remove git hooks
|
|
||||||
- run: rm -rf .git/hooks
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dynamic configuration using variables
|
### Dynamic configuration using variables
|
||||||
|
|
||||||
The following examples show how configuration for the action can be dynamically defined in a previous workflow step.
|
The following examples show how configuration for the action can be dynamically defined in a previous workflow step.
|
||||||
@ -553,7 +540,7 @@ Note that the step where output variables are defined must have an id.
|
|||||||
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT
|
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT
|
||||||
echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
|
echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
title: ${{ steps.vars.outputs.pr_title }}
|
title: ${{ steps.vars.outputs.pr_title }}
|
||||||
body: ${{ steps.vars.outputs.pr_body }}
|
body: ${{ steps.vars.outputs.pr_body }}
|
||||||
@ -579,7 +566,7 @@ The template is rendered using the [render-template](https://github.com/chuhlomi
|
|||||||
bar: that
|
bar: that
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
body: ${{ steps.template.outputs.result }}
|
body: ${{ steps.template.outputs.result }}
|
||||||
```
|
```
|
||||||
|
@ -1,3 +1,23 @@
|
|||||||
|
## Updating from `v5` to `v6`
|
||||||
|
|
||||||
|
### Behaviour changes
|
||||||
|
|
||||||
|
- The default values for `author` and `committer` have changed. See "What's new" below for details. If you are overriding the default values you will not be affected by this change.
|
||||||
|
- On completion, the action now removes the temporary git remote configuration it adds when using `push-to-fork`. This should not affect you unless you were using the temporary configuration for some other purpose after the action completes.
|
||||||
|
|
||||||
|
### What's new
|
||||||
|
|
||||||
|
- Updated runtime to Node.js 20
|
||||||
|
- The action now requires a minimum version of [v2.308.0](https://github.com/actions/runner/releases/tag/v2.308.0) for the Actions runner. Update self-hosted runners to v2.308.0 or later to ensure compatibility.
|
||||||
|
- The default value for `author` has been changed to `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>`. The change adds the `${{ github.actor_id }}+` prefix to the email address to align with GitHub's standard format for the author email address.
|
||||||
|
- The default value for `committer` has been changed to `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>`. This is to align with the default GitHub Actions bot user account.
|
||||||
|
- Adds input `git-token`, the [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that the action will use for git operations. This input defaults to the value of `token`. Use this input if you would like the action to use a different token for git operations than the one used for the GitHub API.
|
||||||
|
- `push-to-fork` now supports pushing to sibling repositories in the same network.
|
||||||
|
- Previously, when using `push-to-fork`, the action did not remove temporary git remote configuration it adds during execution. This has been fixed and the configuration is now removed when the action completes.
|
||||||
|
- If the pull request body is truncated due to exceeding the maximum length, the action will now suffix the body with the message "...*[Pull request body truncated]*" to indicate that the body has been truncated.
|
||||||
|
- The action now uses `--unshallow` only when necessary, rather than as a default argument of `git fetch`. This should improve performance, particularly for large git repositories with extensive commit history.
|
||||||
|
- The action can now be executed on one GitHub server and create pull requests on a *different* GitHub server. Server products include GitHub hosted (github.com), GitHub Enterprise Server (GHES), and GitHub Enterprise Cloud (GHEC). For example, the action can be executed on GitHub hosted and create pull requests on a GHES or GHEC instance.
|
||||||
|
|
||||||
## Updating from `v4` to `v5`
|
## Updating from `v4` to `v5`
|
||||||
|
|
||||||
### Behaviour changes
|
### Behaviour changes
|
||||||
|
4065
package-lock.json
generated
4065
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "create-pull-request",
|
"name": "create-pull-request",
|
||||||
"version": "5.0.0",
|
"version": "6.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
||||||
"main": "lib/main.js",
|
"main": "lib/main.js",
|
||||||
@ -32,29 +32,32 @@
|
|||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"@octokit/core": "^4.2.4",
|
"@octokit/core": "^4.2.4",
|
||||||
|
"@octokit/graphql": "^8.1.1",
|
||||||
|
"@octokit/graphql-schema": "^15.25.0",
|
||||||
"@octokit/plugin-paginate-rest": "^5.0.1",
|
"@octokit/plugin-paginate-rest": "^5.0.1",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
||||||
"https-proxy-agent": "^5.0.1",
|
|
||||||
"proxy-from-env": "^1.1.0",
|
"proxy-from-env": "^1.1.0",
|
||||||
|
"undici": "^6.19.4",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^18.19.3",
|
"@types/node": "^18.19.42",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||||
|
"@typescript-eslint/parser": "^7.17.0",
|
||||||
"@vercel/ncc": "^0.38.1",
|
"@vercel/ncc": "^0.38.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-github": "^4.10.1",
|
"eslint-plugin-github": "^4.10.2",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jest": "^27.6.0",
|
"eslint-plugin-jest": "^27.9.0",
|
||||||
"eslint-plugin-prettier": "^5.1.2",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-circus": "^29.7.0",
|
"jest-circus": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.3.3",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.2.3",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {GitCommandManager} from './git-command-manager'
|
import {GitCommandManager} from './git-command-manager'
|
||||||
import {v4 as uuidv4} from 'uuid'
|
import {v4 as uuidv4} from 'uuid'
|
||||||
|
import * as utils from './utils'
|
||||||
|
|
||||||
const CHERRYPICK_EMPTY =
|
const CHERRYPICK_EMPTY =
|
||||||
'The previous cherry-pick is now empty, possibly due to conflict resolution.'
|
'The previous cherry-pick is now empty, possibly due to conflict resolution.'
|
||||||
const NOTHING_TO_COMMIT = 'nothing to commit, working tree clean'
|
const NOTHING_TO_COMMIT = 'nothing to commit, working tree clean'
|
||||||
|
|
||||||
|
const FETCH_DEPTH_MARGIN = 10
|
||||||
|
|
||||||
export enum WorkingBaseType {
|
export enum WorkingBaseType {
|
||||||
Branch = 'branch',
|
Branch = 'branch',
|
||||||
Commit = 'commit'
|
Commit = 'commit'
|
||||||
@ -31,11 +34,13 @@ export async function getWorkingBaseAndType(
|
|||||||
export async function tryFetch(
|
export async function tryFetch(
|
||||||
git: GitCommandManager,
|
git: GitCommandManager,
|
||||||
remote: string,
|
remote: string,
|
||||||
branch: string
|
branch: string,
|
||||||
|
depth: number
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await git.fetch([`${branch}:refs/remotes/${remote}/${branch}`], remote, [
|
await git.fetch([`${branch}:refs/remotes/${remote}/${branch}`], remote, [
|
||||||
'--force'
|
'--force',
|
||||||
|
`--depth=${depth}`
|
||||||
])
|
])
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
@ -43,6 +48,38 @@ export async function tryFetch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function buildBranchFileChanges(
|
||||||
|
git: GitCommandManager,
|
||||||
|
base: string,
|
||||||
|
branch: string
|
||||||
|
): Promise<BranchFileChanges> {
|
||||||
|
const branchFileChanges: BranchFileChanges = {
|
||||||
|
additions: [],
|
||||||
|
deletions: []
|
||||||
|
}
|
||||||
|
const changedFiles = await git.getChangedFiles([
|
||||||
|
'--diff-filter=AM',
|
||||||
|
`${base}..${branch}`
|
||||||
|
])
|
||||||
|
const deletedFiles = await git.getChangedFiles([
|
||||||
|
'--diff-filter=D',
|
||||||
|
`${base}..${branch}`
|
||||||
|
])
|
||||||
|
const repoPath = git.getWorkingDirectory()
|
||||||
|
for (const file of changedFiles) {
|
||||||
|
branchFileChanges.additions!.push({
|
||||||
|
path: file,
|
||||||
|
contents: utils.readFileBase64([repoPath, file])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (const file of deletedFiles) {
|
||||||
|
branchFileChanges.deletions!.push({
|
||||||
|
path: file
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return branchFileChanges
|
||||||
|
}
|
||||||
|
|
||||||
// Return the number of commits that branch2 is ahead of branch1
|
// Return the number of commits that branch2 is ahead of branch1
|
||||||
async function commitsAhead(
|
async function commitsAhead(
|
||||||
git: GitCommandManager,
|
git: GitCommandManager,
|
||||||
@ -106,11 +143,22 @@ function splitLines(multilineString: string): string[] {
|
|||||||
.filter(x => x !== '')
|
.filter(x => x !== '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BranchFileChanges {
|
||||||
|
additions: {
|
||||||
|
path: string
|
||||||
|
contents: string
|
||||||
|
}[]
|
||||||
|
deletions: {
|
||||||
|
path: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateOrUpdateBranchResult {
|
interface CreateOrUpdateBranchResult {
|
||||||
action: string
|
action: string
|
||||||
base: string
|
base: string
|
||||||
hasDiffWithBase: boolean
|
hasDiffWithBase: boolean
|
||||||
headSha: string
|
headSha: string
|
||||||
|
branchFileChanges?: BranchFileChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createOrUpdateBranch(
|
export async function createOrUpdateBranch(
|
||||||
@ -173,21 +221,12 @@ export async function createOrUpdateBranch(
|
|||||||
// Stash any uncommitted tracked and untracked changes
|
// Stash any uncommitted tracked and untracked changes
|
||||||
const stashed = await git.stashPush(['--include-untracked'])
|
const stashed = await git.stashPush(['--include-untracked'])
|
||||||
|
|
||||||
// Perform fetch and reset the working base
|
// Reset the working base
|
||||||
// Commits made during the workflow will be removed
|
// Commits made during the workflow will be removed
|
||||||
if (workingBaseType == WorkingBaseType.Branch) {
|
if (workingBaseType == WorkingBaseType.Branch) {
|
||||||
core.info(`Resetting working base branch '${workingBase}'`)
|
core.info(`Resetting working base branch '${workingBase}'`)
|
||||||
if (branchRemoteName == 'fork') {
|
await git.checkout(workingBase)
|
||||||
// If pushing to a fork we must fetch with 'unshallow' to avoid the following error on git push
|
await git.exec(['reset', '--hard', `${baseRemote}/${workingBase}`])
|
||||||
// ! [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
|
// If the working base is not the base, rebase the temp branch commits
|
||||||
@ -196,8 +235,13 @@ export async function createOrUpdateBranch(
|
|||||||
core.info(
|
core.info(
|
||||||
`Rebasing commits made to ${workingBaseType} '${workingBase}' on to base branch '${base}'`
|
`Rebasing commits made to ${workingBaseType} '${workingBase}' on to base branch '${base}'`
|
||||||
)
|
)
|
||||||
|
const fetchArgs = ['--force']
|
||||||
|
if (branchRemoteName != 'fork') {
|
||||||
|
// If pushing to a fork we cannot shallow fetch otherwise the 'shallow update not allowed' error occurs
|
||||||
|
fetchArgs.push('--depth=1')
|
||||||
|
}
|
||||||
// Checkout the actual base
|
// Checkout the actual base
|
||||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||||
await git.checkout(base)
|
await git.checkout(base)
|
||||||
// Cherrypick commits from the temporary branch starting from the working base
|
// Cherrypick commits from the temporary branch starting from the working base
|
||||||
const commits = await git.revList(
|
const commits = await git.revList(
|
||||||
@ -216,11 +260,18 @@ export async function createOrUpdateBranch(
|
|||||||
// Reset the temp branch to the working index
|
// Reset the temp branch to the working index
|
||||||
await git.checkout(tempBranch, 'HEAD')
|
await git.checkout(tempBranch, 'HEAD')
|
||||||
// Reset the base
|
// Reset the base
|
||||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the fetch depth for the pull request branch (best effort)
|
||||||
|
const tempBranchCommitsAhead = await commitsAhead(git, base, tempBranch)
|
||||||
|
const fetchDepth =
|
||||||
|
tempBranchCommitsAhead > 0
|
||||||
|
? tempBranchCommitsAhead + FETCH_DEPTH_MARGIN
|
||||||
|
: FETCH_DEPTH_MARGIN
|
||||||
|
|
||||||
// Try to fetch the pull request branch
|
// Try to fetch the pull request branch
|
||||||
if (!(await tryFetch(git, branchRemoteName, branch))) {
|
if (!(await tryFetch(git, branchRemoteName, branch, fetchDepth))) {
|
||||||
// The pull request branch does not exist
|
// The pull request branch does not exist
|
||||||
core.info(`Pull request branch '${branch}' does not exist yet.`)
|
core.info(`Pull request branch '${branch}' does not exist yet.`)
|
||||||
// Create the pull request branch
|
// Create the pull request branch
|
||||||
@ -254,7 +305,6 @@ export async function createOrUpdateBranch(
|
|||||||
// temp branch. This catches a case where the base branch has been force pushed to
|
// temp branch. This catches a case where the base branch has been force pushed to
|
||||||
// a new commit.
|
// a new commit.
|
||||||
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
||||||
const tempBranchCommitsAhead = await commitsAhead(git, base, tempBranch)
|
|
||||||
const branchCommitsAhead = await commitsAhead(git, base, branch)
|
const branchCommitsAhead = await commitsAhead(git, base, branch)
|
||||||
if (
|
if (
|
||||||
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||||
@ -283,6 +333,9 @@ export async function createOrUpdateBranch(
|
|||||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the branch file changes
|
||||||
|
result.branchFileChanges = await buildBranchFileChanges(git, base, branch)
|
||||||
|
|
||||||
// Get the pull request branch SHA
|
// Get the pull request branch SHA
|
||||||
result.headSha = await git.revParse('HEAD')
|
result.headSha = await git.revParse('HEAD')
|
||||||
|
|
||||||
|
@ -6,11 +6,12 @@ import {
|
|||||||
} from './create-or-update-branch'
|
} from './create-or-update-branch'
|
||||||
import {GitHubHelper} from './github-helper'
|
import {GitHubHelper} from './github-helper'
|
||||||
import {GitCommandManager} from './git-command-manager'
|
import {GitCommandManager} from './git-command-manager'
|
||||||
import {GitAuthHelper} from './git-auth-helper'
|
import {GitConfigHelper} from './git-config-helper'
|
||||||
import * as utils from './utils'
|
import * as utils from './utils'
|
||||||
|
|
||||||
export interface Inputs {
|
export interface Inputs {
|
||||||
token: string
|
token: string
|
||||||
|
gitToken: string
|
||||||
path: string
|
path: string
|
||||||
addPaths: string[]
|
addPaths: string[]
|
||||||
commitMessage: string
|
commitMessage: string
|
||||||
@ -31,48 +32,22 @@ export interface Inputs {
|
|||||||
teamReviewers: string[]
|
teamReviewers: string[]
|
||||||
milestone: number
|
milestone: number
|
||||||
draft: boolean
|
draft: boolean
|
||||||
|
signCommit: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
let gitAuthHelper
|
let gitConfigHelper, git
|
||||||
try {
|
try {
|
||||||
if (!inputs.token) {
|
|
||||||
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
|
||||||
}
|
|
||||||
if (inputs.bodyPath) {
|
|
||||||
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
|
||||||
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
|
|
||||||
}
|
|
||||||
// Update the body input with the contents of the file
|
|
||||||
inputs.body = utils.readFile(inputs.bodyPath)
|
|
||||||
}
|
|
||||||
// 65536 characters is the maximum allowed for the pull request body.
|
|
||||||
if (inputs.body.length > 65536) {
|
|
||||||
core.warning(
|
|
||||||
`Pull request body is too long. Truncating to 65536 characters.`
|
|
||||||
)
|
|
||||||
inputs.body = inputs.body.substring(0, 65536)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the repository path
|
|
||||||
const repoPath = utils.getRepoPath(inputs.path)
|
|
||||||
// Create a git command manager
|
|
||||||
const git = await GitCommandManager.create(repoPath)
|
|
||||||
|
|
||||||
// Save and unset the extraheader auth config if it exists
|
|
||||||
core.startGroup('Prepare git configuration')
|
core.startGroup('Prepare git configuration')
|
||||||
gitAuthHelper = new GitAuthHelper(git)
|
const repoPath = utils.getRepoPath(inputs.path)
|
||||||
await gitAuthHelper.addSafeDirectory()
|
git = await GitCommandManager.create(repoPath)
|
||||||
await gitAuthHelper.savePersistedAuth()
|
gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
// Init the GitHub client
|
|
||||||
const githubHelper = new GitHubHelper(inputs.token)
|
|
||||||
|
|
||||||
core.startGroup('Determining the base and head repositories')
|
core.startGroup('Determining the base and head repositories')
|
||||||
// Determine the base repository from git config
|
const baseRemote = gitConfigHelper.getGitRemote()
|
||||||
const remoteUrl = await git.tryGetRemoteUrl()
|
// Init the GitHub client
|
||||||
const baseRemote = utils.getRemoteDetail(remoteUrl)
|
const githubHelper = new GitHubHelper(baseRemote.hostname, inputs.token)
|
||||||
// Determine the head repository; the target for the pull request branch
|
// Determine the head repository; the target for the pull request branch
|
||||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
||||||
const branchRepository = inputs.pushToFork
|
const branchRepository = inputs.pushToFork
|
||||||
@ -83,11 +58,22 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||||||
core.info(
|
core.info(
|
||||||
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
||||||
)
|
)
|
||||||
const parentRepository =
|
const baseParentRepository = await githubHelper.getRepositoryParent(
|
||||||
|
baseRemote.repository
|
||||||
|
)
|
||||||
|
const branchParentRepository =
|
||||||
await githubHelper.getRepositoryParent(branchRepository)
|
await githubHelper.getRepositoryParent(branchRepository)
|
||||||
if (parentRepository != baseRemote.repository) {
|
if (branchParentRepository == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`
|
`Repository '${branchRepository}' is not a fork. Unable to continue.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
branchParentRepository != baseRemote.repository &&
|
||||||
|
baseParentRepository != branchParentRepository
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}', nor are they siblings. Unable to continue.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Add a remote for the fork
|
// Add a remote for the fork
|
||||||
@ -106,7 +92,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||||||
// Configure auth
|
// Configure auth
|
||||||
if (baseRemote.protocol == 'HTTPS') {
|
if (baseRemote.protocol == 'HTTPS') {
|
||||||
core.startGroup('Configuring credential for HTTPS authentication')
|
core.startGroup('Configuring credential for HTTPS authentication')
|
||||||
await gitAuthHelper.configureToken(inputs.token)
|
await gitConfigHelper.configureToken(inputs.gitToken)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +186,8 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||||||
inputs.signoff,
|
inputs.signoff,
|
||||||
inputs.addPaths
|
inputs.addPaths
|
||||||
)
|
)
|
||||||
|
// Set the base. It would have been '' if not specified as an input
|
||||||
|
inputs.base = result.base
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
if (['created', 'updated'].includes(result.action)) {
|
if (['created', 'updated'].includes(result.action)) {
|
||||||
@ -207,17 +195,24 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||||||
core.startGroup(
|
core.startGroup(
|
||||||
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
||||||
)
|
)
|
||||||
await git.push([
|
if (inputs.signCommit) {
|
||||||
'--force-with-lease',
|
await githubHelper.pushSignedCommit(
|
||||||
branchRemoteName,
|
branchRepository,
|
||||||
`${inputs.branch}:refs/heads/${inputs.branch}`
|
inputs.branch,
|
||||||
])
|
inputs.base,
|
||||||
|
inputs.commitMessage,
|
||||||
|
result.branchFileChanges
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await git.push([
|
||||||
|
'--force-with-lease',
|
||||||
|
branchRemoteName,
|
||||||
|
`${inputs.branch}:refs/heads/${inputs.branch}`
|
||||||
|
])
|
||||||
|
}
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the base. It would have been '' if not specified as an input
|
|
||||||
inputs.base = result.base
|
|
||||||
|
|
||||||
if (result.hasDiffWithBase) {
|
if (result.hasDiffWithBase) {
|
||||||
// Create or update the pull request
|
// Create or update the pull request
|
||||||
core.startGroup('Create or update the pull request')
|
core.startGroup('Create or update the pull request')
|
||||||
@ -238,6 +233,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||||||
core.setOutput('pull-request-operation', 'updated')
|
core.setOutput('pull-request-operation', 'updated')
|
||||||
}
|
}
|
||||||
core.setOutput('pull-request-head-sha', result.headSha)
|
core.setOutput('pull-request-head-sha', result.headSha)
|
||||||
|
core.setOutput('pull-request-branch', inputs.branch)
|
||||||
// Deprecated
|
// Deprecated
|
||||||
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
|
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
@ -266,11 +262,11 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(utils.getErrorMessage(error))
|
core.setFailed(utils.getErrorMessage(error))
|
||||||
} finally {
|
} finally {
|
||||||
// Remove auth and restore persisted auth config if it existed
|
|
||||||
core.startGroup('Restore git configuration')
|
core.startGroup('Restore git configuration')
|
||||||
await gitAuthHelper.removeAuth()
|
if (inputs.pushToFork) {
|
||||||
await gitAuthHelper.restorePersistedAuth()
|
await git.exec(['remote', 'rm', 'fork'])
|
||||||
await gitAuthHelper.removeSafeDirectory()
|
}
|
||||||
|
await gitConfigHelper.close()
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,8 @@ export class GitCommandManager {
|
|||||||
async fetch(
|
async fetch(
|
||||||
refSpec: string[],
|
refSpec: string[],
|
||||||
remoteName?: string,
|
remoteName?: string,
|
||||||
options?: string[]
|
options?: string[],
|
||||||
|
unshallow = false
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const args = ['-c', 'protocol.version=2', 'fetch']
|
const args = ['-c', 'protocol.version=2', 'fetch']
|
||||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||||
@ -113,7 +114,9 @@ export class GitCommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
args.push('--progress', '--no-recurse-submodules')
|
args.push('--progress', '--no-recurse-submodules')
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
unshallow &&
|
||||||
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))
|
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))
|
||||||
) {
|
) {
|
||||||
args.push('--unshallow')
|
args.push('--unshallow')
|
||||||
@ -163,6 +166,15 @@ export class GitCommandManager {
|
|||||||
return output.exitCode === 1
|
return output.exitCode === 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getChangedFiles(options?: string[]): Promise<string[]> {
|
||||||
|
const args = ['diff', '--name-only']
|
||||||
|
if (options) {
|
||||||
|
args.push(...options)
|
||||||
|
}
|
||||||
|
const output = await this.exec(args)
|
||||||
|
return output.stdout.split('\n').filter(filename => filename != '')
|
||||||
|
}
|
||||||
|
|
||||||
async isDirty(untracked: boolean, pathspec?: string[]): Promise<boolean> {
|
async isDirty(untracked: boolean, pathspec?: string[]): Promise<boolean> {
|
||||||
const pathspecArgs = pathspec ? ['--', ...pathspec] : []
|
const pathspecArgs = pathspec ? ['--', ...pathspec] : []
|
||||||
// Check untracked changes
|
// Check untracked changes
|
||||||
|
@ -5,22 +5,42 @@ import * as path from 'path'
|
|||||||
import {URL} from 'url'
|
import {URL} from 'url'
|
||||||
import * as utils from './utils'
|
import * as utils from './utils'
|
||||||
|
|
||||||
export class GitAuthHelper {
|
interface GitRemote {
|
||||||
|
hostname: string
|
||||||
|
protocol: string
|
||||||
|
repository: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitConfigHelper {
|
||||||
private git: GitCommandManager
|
private git: GitCommandManager
|
||||||
private gitConfigPath = ''
|
private gitConfigPath = ''
|
||||||
private workingDirectory: string
|
private workingDirectory: string
|
||||||
private safeDirectoryConfigKey = 'safe.directory'
|
private safeDirectoryConfigKey = 'safe.directory'
|
||||||
private safeDirectoryAdded = false
|
private safeDirectoryAdded = false
|
||||||
private extraheaderConfigKey: string
|
private remoteUrl = ''
|
||||||
|
private extraheaderConfigKey = ''
|
||||||
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
||||||
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
||||||
private persistedExtraheaderConfigValue = ''
|
private persistedExtraheaderConfigValue = ''
|
||||||
|
|
||||||
constructor(git: GitCommandManager) {
|
private constructor(git: GitCommandManager) {
|
||||||
this.git = git
|
this.git = git
|
||||||
this.workingDirectory = this.git.getWorkingDirectory()
|
this.workingDirectory = this.git.getWorkingDirectory()
|
||||||
const serverUrl = this.getServerUrl()
|
}
|
||||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
|
||||||
|
static async create(git: GitCommandManager): Promise<GitConfigHelper> {
|
||||||
|
const gitConfigHelper = new GitConfigHelper(git)
|
||||||
|
await gitConfigHelper.addSafeDirectory()
|
||||||
|
await gitConfigHelper.fetchRemoteDetail()
|
||||||
|
await gitConfigHelper.savePersistedAuth()
|
||||||
|
return gitConfigHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
// Remove auth and restore persisted auth config if it existed
|
||||||
|
await this.removeAuth()
|
||||||
|
await this.restorePersistedAuth()
|
||||||
|
await this.removeSafeDirectory()
|
||||||
}
|
}
|
||||||
|
|
||||||
async addSafeDirectory(): Promise<void> {
|
async addSafeDirectory(): Promise<void> {
|
||||||
@ -50,7 +70,57 @@ export class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchRemoteDetail(): Promise<void> {
|
||||||
|
this.remoteUrl = await this.git.tryGetRemoteUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
getGitRemote(): GitRemote {
|
||||||
|
return GitConfigHelper.parseGitRemote(this.remoteUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseGitRemote(remoteUrl: string): GitRemote {
|
||||||
|
const httpsUrlPattern = new RegExp(
|
||||||
|
'^(https?)://(?:.+@)?(.+?)/(.+/.+?)(\\.git)?$',
|
||||||
|
'i'
|
||||||
|
)
|
||||||
|
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
||||||
|
if (httpsMatch) {
|
||||||
|
return {
|
||||||
|
hostname: httpsMatch[2],
|
||||||
|
protocol: 'HTTPS',
|
||||||
|
repository: httpsMatch[3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sshUrlPattern = new RegExp('^git@(.+?):(.+/.+)\\.git$', 'i')
|
||||||
|
const sshMatch = remoteUrl.match(sshUrlPattern)
|
||||||
|
if (sshMatch) {
|
||||||
|
return {
|
||||||
|
hostname: sshMatch[1],
|
||||||
|
protocol: 'SSH',
|
||||||
|
repository: sshMatch[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unauthenticated git protocol for integration tests only
|
||||||
|
const gitUrlPattern = new RegExp('^git://(.+?)/(.+/.+)\\.git$', 'i')
|
||||||
|
const gitMatch = remoteUrl.match(gitUrlPattern)
|
||||||
|
if (gitMatch) {
|
||||||
|
return {
|
||||||
|
hostname: gitMatch[1],
|
||||||
|
protocol: 'GIT',
|
||||||
|
repository: gitMatch[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async savePersistedAuth(): Promise<void> {
|
async savePersistedAuth(): Promise<void> {
|
||||||
|
const serverUrl = new URL(`https://${this.getGitRemote().hostname}`)
|
||||||
|
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
||||||
// Save and unset persisted extraheader credential in git config if it exists
|
// Save and unset persisted extraheader credential in git config if it exists
|
||||||
this.persistedExtraheaderConfigValue = await this.getAndUnset()
|
this.persistedExtraheaderConfigValue = await this.getAndUnset()
|
||||||
}
|
}
|
||||||
@ -144,8 +214,4 @@ export class GitAuthHelper {
|
|||||||
content = content.replace(find, replace)
|
content = content.replace(find, replace)
|
||||||
await fs.promises.writeFile(this.gitConfigPath, content)
|
await fs.promises.writeFile(this.gitConfigPath, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getServerUrl(): URL {
|
|
||||||
return new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com')
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,6 +1,13 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {Inputs} from './create-pull-request'
|
import {Inputs} from './create-pull-request'
|
||||||
import {Octokit, OctokitOptions} from './octokit-client'
|
import {Octokit, OctokitOptions} from './octokit-client'
|
||||||
|
import type {
|
||||||
|
Repository as TempRepository,
|
||||||
|
Ref,
|
||||||
|
Commit,
|
||||||
|
FileChanges
|
||||||
|
} from '@octokit/graphql-schema'
|
||||||
|
import {BranchFileChanges} from './create-or-update-branch'
|
||||||
import * as utils from './utils'
|
import * as utils from './utils'
|
||||||
|
|
||||||
const ERROR_PR_REVIEW_TOKEN_SCOPE =
|
const ERROR_PR_REVIEW_TOKEN_SCOPE =
|
||||||
@ -20,12 +27,16 @@ interface Pull {
|
|||||||
export class GitHubHelper {
|
export class GitHubHelper {
|
||||||
private octokit: InstanceType<typeof Octokit>
|
private octokit: InstanceType<typeof Octokit>
|
||||||
|
|
||||||
constructor(token: string) {
|
constructor(githubServerHostname: string, token: string) {
|
||||||
const options: OctokitOptions = {}
|
const options: OctokitOptions = {}
|
||||||
if (token) {
|
if (token) {
|
||||||
options.auth = `${token}`
|
options.auth = `${token}`
|
||||||
}
|
}
|
||||||
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com'
|
if (githubServerHostname !== 'github.com') {
|
||||||
|
options.baseUrl = `https://${githubServerHostname}/api/v3`
|
||||||
|
} else {
|
||||||
|
options.baseUrl = 'https://api.github.com'
|
||||||
|
}
|
||||||
this.octokit = new Octokit(options)
|
this.octokit = new Octokit(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +55,6 @@ export class GitHubHelper {
|
|||||||
): Promise<Pull> {
|
): Promise<Pull> {
|
||||||
const [headOwner] = headRepository.split('/')
|
const [headOwner] = headRepository.split('/')
|
||||||
const headBranch = `${headOwner}:${inputs.branch}`
|
const headBranch = `${headOwner}:${inputs.branch}`
|
||||||
const headBranchFull = `${headRepository}:${inputs.branch}`
|
|
||||||
|
|
||||||
// Try to create the pull request
|
// Try to create the pull request
|
||||||
try {
|
try {
|
||||||
@ -81,7 +91,7 @@ export class GitHubHelper {
|
|||||||
const {data: pulls} = await this.octokit.rest.pulls.list({
|
const {data: pulls} = await this.octokit.rest.pulls.list({
|
||||||
...this.parseRepository(baseRepository),
|
...this.parseRepository(baseRepository),
|
||||||
state: 'open',
|
state: 'open',
|
||||||
head: headBranchFull,
|
head: headBranch,
|
||||||
base: inputs.base
|
base: inputs.base
|
||||||
})
|
})
|
||||||
core.info(`Attempting update of pull request`)
|
core.info(`Attempting update of pull request`)
|
||||||
@ -101,14 +111,12 @@ export class GitHubHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRepositoryParent(headRepository: string): Promise<string> {
|
async getRepositoryParent(headRepository: string): Promise<string | null> {
|
||||||
const {data: headRepo} = await this.octokit.rest.repos.get({
|
const {data: headRepo} = await this.octokit.rest.repos.get({
|
||||||
...this.parseRepository(headRepository)
|
...this.parseRepository(headRepository)
|
||||||
})
|
})
|
||||||
if (!headRepo.parent) {
|
if (!headRepo.parent) {
|
||||||
throw new Error(
|
return null
|
||||||
`Repository '${headRepository}' is not a fork. Unable to continue.`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return headRepo.parent.full_name
|
return headRepo.parent.full_name
|
||||||
}
|
}
|
||||||
@ -183,4 +191,204 @@ export class GitHubHelper {
|
|||||||
|
|
||||||
return pull
|
return pull
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async pushSignedCommit(
|
||||||
|
branchRepository: string,
|
||||||
|
branch: string,
|
||||||
|
base: string,
|
||||||
|
commitMessage: string,
|
||||||
|
branchFileChanges?: BranchFileChanges
|
||||||
|
): Promise<void> {
|
||||||
|
core.info(`Use API to push a signed commit`)
|
||||||
|
|
||||||
|
const [repoOwner, repoName] = branchRepository.split('/')
|
||||||
|
core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`)
|
||||||
|
const refQuery = `
|
||||||
|
query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) {
|
||||||
|
repository(owner: $repoOwner, name: $repoName){
|
||||||
|
id
|
||||||
|
ref(qualifiedName: $branchName){
|
||||||
|
id
|
||||||
|
name
|
||||||
|
prefix
|
||||||
|
target{
|
||||||
|
id
|
||||||
|
oid
|
||||||
|
commitUrl
|
||||||
|
commitResourcePath
|
||||||
|
abbreviatedOid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
let branchRef = await this.octokit.graphql<{repository: TempRepository}>(
|
||||||
|
refQuery,
|
||||||
|
{
|
||||||
|
repoOwner: repoOwner,
|
||||||
|
repoName: repoName,
|
||||||
|
branchName: branch
|
||||||
|
}
|
||||||
|
)
|
||||||
|
core.debug(
|
||||||
|
`Fetched information for branch '${branch}' - '${JSON.stringify(branchRef)}'`
|
||||||
|
)
|
||||||
|
|
||||||
|
const branchExists = branchRef.repository.ref != null
|
||||||
|
|
||||||
|
// if the branch does not exist, then first we need to create the branch from base
|
||||||
|
if (!branchExists) {
|
||||||
|
core.debug(`Branch does not exist - '${branch}'`)
|
||||||
|
branchRef = await this.octokit.graphql<{repository: TempRepository}>(
|
||||||
|
refQuery,
|
||||||
|
{
|
||||||
|
repoOwner: repoOwner,
|
||||||
|
repoName: repoName,
|
||||||
|
branchName: base
|
||||||
|
}
|
||||||
|
)
|
||||||
|
core.debug(
|
||||||
|
`Fetched information for base branch '${base}' - '${JSON.stringify(branchRef)}'`
|
||||||
|
)
|
||||||
|
|
||||||
|
core.info(
|
||||||
|
`Creating new branch '${branch}' from '${base}', with ref '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`
|
||||||
|
)
|
||||||
|
if (branchRef.repository.ref != null) {
|
||||||
|
core.debug(`Send request for creating new branch`)
|
||||||
|
const newBranchMutation = `
|
||||||
|
mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) {
|
||||||
|
createRef(input: {
|
||||||
|
name: $branchName,
|
||||||
|
oid: $oid,
|
||||||
|
repositoryId: $repoId
|
||||||
|
}) {
|
||||||
|
ref {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const newBranch = await this.octokit.graphql<{createRef: {ref: Ref}}>(
|
||||||
|
newBranchMutation,
|
||||||
|
{
|
||||||
|
repoId: branchRef.repository.id,
|
||||||
|
oid: branchRef.repository.ref.target!.oid,
|
||||||
|
branchName: 'refs/heads/' + branch
|
||||||
|
}
|
||||||
|
)
|
||||||
|
core.debug(
|
||||||
|
`Created new branch '${branch}': '${JSON.stringify(newBranch.createRef.ref)}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core.info(
|
||||||
|
`Hash ref of branch '${branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileChanges = <FileChanges>{
|
||||||
|
additions: branchFileChanges!.additions,
|
||||||
|
deletions: branchFileChanges!.deletions
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushCommitMutation = `
|
||||||
|
mutation PushCommit(
|
||||||
|
$repoNameWithOwner: String!,
|
||||||
|
$branchName: String!,
|
||||||
|
$headOid: GitObjectID!,
|
||||||
|
$commitMessage: String!,
|
||||||
|
$fileChanges: FileChanges
|
||||||
|
) {
|
||||||
|
createCommitOnBranch(input: {
|
||||||
|
branch: {
|
||||||
|
repositoryNameWithOwner: $repoNameWithOwner,
|
||||||
|
branchName: $branchName,
|
||||||
|
}
|
||||||
|
fileChanges: $fileChanges
|
||||||
|
message: {
|
||||||
|
headline: $commitMessage
|
||||||
|
}
|
||||||
|
expectedHeadOid: $headOid
|
||||||
|
}){
|
||||||
|
clientMutationId
|
||||||
|
ref{
|
||||||
|
id
|
||||||
|
name
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
commit{
|
||||||
|
id
|
||||||
|
abbreviatedOid
|
||||||
|
oid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const pushCommitVars = {
|
||||||
|
branchName: branch,
|
||||||
|
repoNameWithOwner: repoOwner + '/' + repoName,
|
||||||
|
headOid: branchRef.repository.ref!.target!.oid,
|
||||||
|
commitMessage: commitMessage,
|
||||||
|
fileChanges: fileChanges
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushCommitVarsWithoutContents = {
|
||||||
|
...pushCommitVars,
|
||||||
|
fileChanges: {
|
||||||
|
...pushCommitVars.fileChanges,
|
||||||
|
additions: pushCommitVars.fileChanges.additions?.map(addition => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const {contents, ...rest} = addition
|
||||||
|
return rest
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.debug(
|
||||||
|
`Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'`
|
||||||
|
)
|
||||||
|
|
||||||
|
const commit = await this.octokit.graphql<{
|
||||||
|
createCommitOnBranch: {ref: Ref; commit: Commit}
|
||||||
|
}>(pushCommitMutation, pushCommitVars)
|
||||||
|
|
||||||
|
core.debug(`Pushed commit - '${JSON.stringify(commit)}'`)
|
||||||
|
core.info(
|
||||||
|
`Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (branchExists) {
|
||||||
|
// The branch existed so update the branch ref to point to the new commit
|
||||||
|
// This is the same behavior as force pushing the branch
|
||||||
|
core.info(
|
||||||
|
`Updating branch '${branch}' to commit '${commit.createCommitOnBranch.commit.oid}'`
|
||||||
|
)
|
||||||
|
const updateBranchMutation = `
|
||||||
|
mutation UpdateBranch($branchId: ID!, $commitOid: GitObjectID!) {
|
||||||
|
updateRef(input: {
|
||||||
|
refId: $branchId,
|
||||||
|
oid: $commitOid,
|
||||||
|
force: true
|
||||||
|
}) {
|
||||||
|
ref {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const updatedBranch = await this.octokit.graphql<{updateRef: {ref: Ref}}>(
|
||||||
|
updateBranchMutation,
|
||||||
|
{
|
||||||
|
branchId: branchRef.repository.ref!.id,
|
||||||
|
commitOid: commit.createCommitOnBranch.commit.oid
|
||||||
|
}
|
||||||
|
)
|
||||||
|
core.debug(`Updated branch - '${JSON.stringify(updatedBranch)}'`)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
25
src/main.ts
25
src/main.ts
@ -7,6 +7,7 @@ async function run(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
const inputs: Inputs = {
|
const inputs: Inputs = {
|
||||||
token: core.getInput('token'),
|
token: core.getInput('token'),
|
||||||
|
gitToken: core.getInput('git-token'),
|
||||||
path: core.getInput('path'),
|
path: core.getInput('path'),
|
||||||
addPaths: utils.getInputAsArray('add-paths'),
|
addPaths: utils.getInputAsArray('add-paths'),
|
||||||
commitMessage: core.getInput('commit-message'),
|
commitMessage: core.getInput('commit-message'),
|
||||||
@ -26,10 +27,32 @@ async function run(): Promise<void> {
|
|||||||
reviewers: utils.getInputAsArray('reviewers'),
|
reviewers: utils.getInputAsArray('reviewers'),
|
||||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||||
milestone: Number(core.getInput('milestone')),
|
milestone: Number(core.getInput('milestone')),
|
||||||
draft: core.getBooleanInput('draft')
|
draft: core.getBooleanInput('draft'),
|
||||||
|
signCommit: core.getBooleanInput('sign-commit')
|
||||||
}
|
}
|
||||||
core.debug(`Inputs: ${inspect(inputs)}`)
|
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||||
|
|
||||||
|
if (!inputs.token) {
|
||||||
|
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
||||||
|
}
|
||||||
|
if (!inputs.gitToken) {
|
||||||
|
inputs.gitToken = inputs.token
|
||||||
|
}
|
||||||
|
if (inputs.bodyPath) {
|
||||||
|
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
||||||
|
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
|
||||||
|
}
|
||||||
|
// Update the body input with the contents of the file
|
||||||
|
inputs.body = utils.readFile(inputs.bodyPath)
|
||||||
|
}
|
||||||
|
// 65536 characters is the maximum allowed for the pull request body.
|
||||||
|
if (inputs.body.length > 65536) {
|
||||||
|
core.warning(
|
||||||
|
`Pull request body is too long. Truncating to 65536 characters.`
|
||||||
|
)
|
||||||
|
inputs.body = inputs.body.substring(0, 65536)
|
||||||
|
}
|
||||||
|
|
||||||
await createPullRequest(inputs)
|
await createPullRequest(inputs)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(utils.getErrorMessage(error))
|
core.setFailed(utils.getErrorMessage(error))
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {Octokit as Core} from '@octokit/core'
|
import {Octokit as Core} from '@octokit/core'
|
||||||
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
||||||
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
||||||
import {HttpsProxyAgent} from 'https-proxy-agent'
|
|
||||||
import {getProxyForUrl} from 'proxy-from-env'
|
import {getProxyForUrl} from 'proxy-from-env'
|
||||||
|
import {ProxyAgent, fetch as undiciFetch} from 'undici'
|
||||||
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
||||||
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||||
|
|
||||||
@ -12,12 +12,23 @@ export const Octokit = Core.plugin(
|
|||||||
autoProxyAgent
|
autoProxyAgent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const proxyFetch =
|
||||||
|
(proxyUrl: string): typeof undiciFetch =>
|
||||||
|
(url, opts) => {
|
||||||
|
return undiciFetch(url, {
|
||||||
|
...opts,
|
||||||
|
dispatcher: new ProxyAgent({
|
||||||
|
uri: proxyUrl
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
|
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
|
||||||
function autoProxyAgent(octokit: Core) {
|
function autoProxyAgent(octokit: Core) {
|
||||||
octokit.hook.before('request', options => {
|
octokit.hook.before('request', options => {
|
||||||
const proxy = getProxyForUrl(options.baseUrl)
|
const proxy = getProxyForUrl(options.baseUrl)
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
options.request.agent = new HttpsProxyAgent(proxy)
|
options.request.fetch = proxyFetch(proxy)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
51
src/utils.ts
51
src/utils.ts
@ -41,53 +41,6 @@ export function getRepoPath(relativePath?: string): string {
|
|||||||
return repoPath
|
return repoPath
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoteDetail {
|
|
||||||
hostname: string
|
|
||||||
protocol: string
|
|
||||||
repository: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRemoteDetail(remoteUrl: string): RemoteDetail {
|
|
||||||
// Parse the protocol and github repository from a URL
|
|
||||||
// e.g. HTTPS, peter-evans/create-pull-request
|
|
||||||
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 hostname = githubServerMatch[1]
|
|
||||||
|
|
||||||
const httpsUrlPattern = new RegExp(
|
|
||||||
'^https?://.*@?' + hostname + '/(.+/.+?)(\\.git)?$',
|
|
||||||
'i'
|
|
||||||
)
|
|
||||||
const sshUrlPattern = new RegExp('^git@' + hostname + ':(.+/.+)\\.git$', 'i')
|
|
||||||
|
|
||||||
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
|
||||||
if (httpsMatch) {
|
|
||||||
return {
|
|
||||||
hostname,
|
|
||||||
protocol: 'HTTPS',
|
|
||||||
repository: httpsMatch[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sshMatch = remoteUrl.match(sshUrlPattern)
|
|
||||||
if (sshMatch) {
|
|
||||||
return {
|
|
||||||
hostname,
|
|
||||||
protocol: 'SSH',
|
|
||||||
repository: sshMatch[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRemoteUrl(
|
export function getRemoteUrl(
|
||||||
protocol: string,
|
protocol: string,
|
||||||
hostname: string,
|
hostname: string,
|
||||||
@ -173,6 +126,10 @@ export function readFile(path: string): string {
|
|||||||
return fs.readFileSync(path, 'utf-8')
|
return fs.readFileSync(path, 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readFileBase64(pathParts: string[]): string {
|
||||||
|
return fs.readFileSync(path.resolve(...pathParts)).toString('base64')
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
function hasErrorCode(error: any): error is {code: string} {
|
function hasErrorCode(error: any): error is {code: string} {
|
||||||
return typeof (error && error.code) === 'string'
|
return typeof (error && error.code) === 'string'
|
||||||
|
Reference in New Issue
Block a user