Compare commits
1 Commits
signed-com
...
v5-main
Author | SHA1 | Date | |
---|---|---|---|
4e1beaa752 |
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@ -19,21 +19,21 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run format-check
|
||||
- run: npm run lint
|
||||
- run: npm run test
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: action.yml
|
||||
path: action.yml
|
||||
@ -46,16 +46,16 @@ jobs:
|
||||
matrix:
|
||||
target: [built, committed]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main
|
||||
- if: matrix.target == 'built' || github.event_name == 'pull_request'
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- if: matrix.target == 'built' || github.event_name == 'pull_request'
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: action.yml
|
||||
path: .
|
||||
@ -68,8 +68,8 @@ jobs:
|
||||
uses: ./
|
||||
with:
|
||||
commit-message: '[CI] test ${{ matrix.target }}'
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
title: '[CI] test ${{ matrix.target }}'
|
||||
body: |
|
||||
- CI test case for target '${{ matrix.target }}'
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v3
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
@ -101,7 +101,7 @@ jobs:
|
||||
|
||||
- if: steps.fc.outputs.comment-id == ''
|
||||
name: Create comment
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
body: |
|
||||
@ -115,13 +115,13 @@ jobs:
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
commit-message: 'build: update distribution'
|
||||
|
8
.github/workflows/cpr-example-command.yml
vendored
8
.github/workflows/cpr-example-command.yml
vendored
@ -6,7 +6,7 @@ jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Make changes to pull request
|
||||
run: date +%s > report.txt
|
||||
@ -16,8 +16,8 @@ jobs:
|
||||
uses: ./
|
||||
with:
|
||||
commit-message: Update report
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
title: '[Example] Update report'
|
||||
body: |
|
||||
@ -42,7 +42,7 @@ jobs:
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
|
||||
- name: Add reaction
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
|
||||
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
|
||||
|
4
.github/workflows/slash-command-dispatch.yml
vendored
4
.github/workflows/slash-command-dispatch.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slash Command Dispatch
|
||||
uses: peter-evans/slash-command-dispatch@v4
|
||||
uses: peter-evans/slash-command-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
config: >
|
||||
@ -19,7 +19,7 @@ jobs:
|
||||
"named_args": true
|
||||
},
|
||||
{
|
||||
"command": "testv5",
|
||||
"command": "testv4",
|
||||
"permission": "admin",
|
||||
"repository": "peter-evans/create-pull-request-tests",
|
||||
"named_args": true
|
||||
|
4
.github/workflows/update-major-version.yml
vendored
4
.github/workflows/update-major-version.yml
vendored
@ -11,14 +11,14 @@ on:
|
||||
type: choice
|
||||
description: The major version tag to update
|
||||
options:
|
||||
- v4
|
||||
- v5
|
||||
- v6
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
72
README.md
72
README.md
@ -21,21 +21,20 @@ Create Pull Request action will:
|
||||
|
||||
- [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md)
|
||||
- [Examples](docs/examples.md)
|
||||
- [Updating to v6](docs/updating.md)
|
||||
- [Common issues](docs/common-issues.md)
|
||||
- [Updating to v5](docs/updating.md)
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
```
|
||||
|
||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v6.x.x`
|
||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v5.x.x`
|
||||
|
||||
### Workflow permissions
|
||||
|
||||
@ -53,19 +52,17 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
||||
| 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` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
| `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_id }}+${{ github.actor }}@users.noreply.github.com>` |
|
||||
| `commit-message` | The message to use when committing changes. | `[create-pull-request] automated change` |
|
||||
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the GitHub Actions bot user. | `GitHub <noreply@github.com>` |
|
||||
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>` |
|
||||
| `signoff` | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line by the committer at the end of the commit log message. | `false` |
|
||||
| `branch` | The pull request branch name. | `create-pull-request/patch` |
|
||||
| `delete-branch` | Delete the `branch` if it doesn't have an active pull request associated with it. See [delete-branch](#delete-branch). | `false` |
|
||||
| `delete-branch` | Delete the `branch` when closing pull requests, and when undeleted after merging. | `false` |
|
||||
| `branch-suffix` | The branch suffix type when using the alternative branching strategy. Valid values are `random`, `timestamp` and `short-commit-hash`. See [Alternative strategy](#alternative-strategy---always-create-a-new-pull-request-branch) for details. | |
|
||||
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
|
||||
| `push-to-fork` | A fork of the checked-out parent repository to which the pull request branch will be pushed. e.g. `owner/repo-fork`. The pull request will be created to merge the fork's branch into the parent's base. See [push pull request branches to a fork](docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork) for details. | |
|
||||
| `sign-commits` | Sign commits as `github-actions[bot]` when using `GITHUB_TOKEN`, or your own bot when using [GitHub App tokens](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens). See [commit signing](docs/concepts-guidelines.md#commit-signature-verification-for-bots) for details. | `false` |
|
||||
| `title` | The title of the pull request. | `Changes by create-pull-request action` |
|
||||
| `body` | The body of the pull request. | `Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action` |
|
||||
| `body-path` | The path to a file containing the pull request body. Takes precedence over `body`. | |
|
||||
@ -75,38 +72,11 @@ 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. | |
|
||||
| `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` |
|
||||
| `maintainer-can-modify` | Indicates whether [maintainers can modify](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) the pull request. | `true` |
|
||||
|
||||
#### commit-message
|
||||
|
||||
In addition to a message, the `commit-message` input can also be used to populate the commit description. Leave a single blank line between the message and description.
|
||||
|
||||
```yml
|
||||
commit-message: |
|
||||
the first line is the commit message
|
||||
|
||||
the commit description starts
|
||||
after a blank line and can be
|
||||
multiple lines
|
||||
```
|
||||
|
||||
#### 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 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.
|
||||
|
||||
#### Proxy support
|
||||
|
||||
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
env:
|
||||
https_proxy: http://<proxy_address>:<port>
|
||||
```
|
||||
@ -117,10 +87,8 @@ The following outputs can be used by subsequent workflow steps.
|
||||
|
||||
- `pull-request-number` - The pull request number.
|
||||
- `pull-request-url` - The URL of the pull request.
|
||||
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated`, `closed` or `none`.
|
||||
- `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-branch` - The branch name of the pull request.
|
||||
- `pull-request-commits-verified` - Whether GitHub considers the signature of the branch's commits to be verified; `true` or `false`.
|
||||
|
||||
Step outputs can be accessed as in the following example.
|
||||
Note that in order to read the step outputs the action step must have an id.
|
||||
@ -128,7 +96,7 @@ Note that in order to read the step outputs the action step must have an id.
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
- name: Check outputs
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
run: |
|
||||
@ -147,7 +115,7 @@ How the action behaves:
|
||||
- If there are changes (i.e. a diff exists with the checked-out base branch), the changes will be pushed to a new `branch` and a pull request created.
|
||||
- If there are no changes (i.e. no diff exists with the checked-out base branch), no pull request will be created and the action exits silently.
|
||||
- If a pull request already exists it will be updated if necessary. Local changes in the Actions workspace, or changes on the base branch, can cause an update. If no update is required the action exits silently.
|
||||
- If a pull request exists and new changes on the base branch make the pull request unnecessary (i.e. there is no longer a diff between the pull request branch and the base), the pull request is automatically closed. Additionally, if [`delete-branch`](#delete-branch) is set to `true` the `branch` will be deleted.
|
||||
- If a pull request exists and new changes on the base branch make the pull request unnecessary (i.e. there is no longer a diff between the pull request branch and the base), the pull request is automatically closed. Additionally, if `delete-branch` is set to `true` the `branch` will be deleted.
|
||||
|
||||
For further details about how the action works and usage guidelines, see [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md).
|
||||
|
||||
@ -191,7 +159,7 @@ File changes that do not match one of the paths will be stashed and restored aft
|
||||
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
add-paths: |
|
||||
*.java
|
||||
@ -205,7 +173,7 @@ Note that the repository must be checked out on a branch with a remote, it won't
|
||||
|
||||
```yml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Create commits
|
||||
run: |
|
||||
git config user.name 'Peter Evans'
|
||||
@ -218,7 +186,7 @@ Note that the repository must be checked out on a branch with a remote, it won't
|
||||
- name: Uncommitted change
|
||||
run: date +%s > report.txt
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
```
|
||||
|
||||
### Create a project card
|
||||
@ -228,7 +196,7 @@ To create a project card for the pull request, pass the `pull-request-number` st
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
|
||||
- name: Create or Update Project Card
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
@ -256,19 +224,19 @@ jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Make changes to pull request
|
||||
run: date +%s > report.txt
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update report
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: example-patches
|
||||
delete-branch: true
|
||||
|
@ -1,15 +1,14 @@
|
||||
import {
|
||||
createOrUpdateBranch,
|
||||
tryFetch,
|
||||
getWorkingBaseAndType,
|
||||
buildBranchCommits
|
||||
getWorkingBaseAndType
|
||||
} from '../lib/create-or-update-branch'
|
||||
import * as fs from 'fs'
|
||||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
import * as path from 'path'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
const REPO_PATH = '/git/local/test-base'
|
||||
const REMOTE_NAME = 'origin'
|
||||
|
||||
const TRACKED_FILE = 'a/tracked-file.txt'
|
||||
@ -23,7 +22,7 @@ const INIT_COMMIT_MESSAGE = 'Add file to be a tracked file for tests'
|
||||
const BRANCH = 'tests/create-pull-request/patch'
|
||||
const BASE = DEFAULT_BRANCH
|
||||
|
||||
const FORK_REMOTE_URL = 'git://127.0.0.1/repos/test-fork.git'
|
||||
const FORK_REMOTE_URL = 'git://127.0.0.1/test-fork.git'
|
||||
const FORK_REMOTE_NAME = 'fork'
|
||||
|
||||
const ADD_PATHS_DEFAULT = []
|
||||
@ -141,22 +140,10 @@ describe('create-or-update-branch tests', () => {
|
||||
})
|
||||
|
||||
async function beforeTest(): Promise<void> {
|
||||
await git.fetch(
|
||||
[`${DEFAULT_BRANCH}:${DEFAULT_BRANCH}`],
|
||||
REMOTE_NAME,
|
||||
['--force', '--update-head-ok'],
|
||||
true
|
||||
)
|
||||
await git.checkout(DEFAULT_BRANCH)
|
||||
}
|
||||
|
||||
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)
|
||||
try {
|
||||
// Get the upstream branch if it exists
|
||||
@ -211,8 +198,8 @@ describe('create-or-update-branch tests', () => {
|
||||
}
|
||||
|
||||
it('tests if a branch exists and can be fetched', async () => {
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_BASE_BRANCH, 1)).toBeTruthy()
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_EXIST_BRANCH, 1)).toBeFalsy()
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_BASE_BRANCH)).toBeTruthy()
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_EXIST_BRANCH)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('tests getWorkingBaseAndType on a checked out ref', async () => {
|
||||
@ -230,77 +217,6 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(workingBaseType).toEqual('commit')
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with no diff', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
expect(branchCommits.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with addition and modification', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
await createChanges()
|
||||
const UNTRACKED_EXE_FILE = 'a/script.sh'
|
||||
const filepath = path.join(REPO_PATH, UNTRACKED_EXE_FILE)
|
||||
await fs.promises.writeFile(filepath, '#!/usr/bin/env bash', {mode: 0o755})
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(1)
|
||||
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||
expect(branchCommits[0].changes.length).toEqual(3)
|
||||
expect(branchCommits[0].changes).toEqual([
|
||||
{mode: '100755', path: UNTRACKED_EXE_FILE, status: 'A'},
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: 'A'}
|
||||
])
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with addition and deletion', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
await createChanges()
|
||||
const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt'
|
||||
const filepath = path.join(REPO_PATH, TRACKED_FILE_NEW_PATH)
|
||||
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
|
||||
await fs.promises.rename(path.join(REPO_PATH, TRACKED_FILE), filepath)
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(1)
|
||||
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||
expect(branchCommits[0].changes.length).toEqual(3)
|
||||
expect(branchCommits[0].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'D'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: 'A'},
|
||||
{mode: '100644', path: TRACKED_FILE_NEW_PATH, status: 'A'}
|
||||
])
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with multiple commits', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await createChanges()
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', `Test changes ${i}`])
|
||||
}
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(3)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
expect(branchCommits[i].subject).toEqual(`Test changes ${i}`)
|
||||
expect(branchCommits[i].changes.length).toEqual(2)
|
||||
const untrackedFileStatus = i == 0 ? 'A' : 'M'
|
||||
expect(branchCommits[i].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: untrackedFileStatus}
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
it('tests no changes resulting in no new branch being created', async () => {
|
||||
const commitMessage = uuidv4()
|
||||
const result = await createOrUpdateBranch(
|
||||
@ -1538,7 +1454,8 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(
|
||||
await gitLogMatches([
|
||||
_commitMessage,
|
||||
commits.commitMsgs[0] // fetch depth of base is 1
|
||||
...commits.commitMsgs,
|
||||
INIT_COMMIT_MESSAGE
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
@ -1673,9 +1590,7 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||
expect(
|
||||
await gitLogMatches([
|
||||
commits.commitMsgs[0] // fetch depth of base is 1
|
||||
])
|
||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
@ -1753,9 +1668,7 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||
expect(
|
||||
await gitLogMatches([
|
||||
commits.commitMsgs[0] // fetch depth of base is 1
|
||||
])
|
||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
@ -2038,7 +1951,8 @@ describe('create-or-update-branch tests', () => {
|
||||
await gitLogMatches([
|
||||
_commitMessage,
|
||||
..._commits.commitMsgs,
|
||||
commitsOnBase.commitMsgs[0] // fetch depth of base is 1
|
||||
...commitsOnBase.commitMsgs,
|
||||
INIT_COMMIT_MESSAGE
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
@ -2233,7 +2147,8 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(
|
||||
await gitLogMatches([
|
||||
_commitMessage,
|
||||
commitsOnBase.commitMsgs[0] // fetch depth of base is 1
|
||||
...commitsOnBase.commitMsgs,
|
||||
INIT_COMMIT_MESSAGE
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
@ -5,27 +5,23 @@ set -euo pipefail
|
||||
WORKINGDIR=$PWD
|
||||
|
||||
# Create and serve a remote repo
|
||||
mkdir -p /git/remote/repos
|
||||
mkdir -p /git/remote
|
||||
git config --global init.defaultBranch main
|
||||
git init --bare /git/remote/repos/test-base.git
|
||||
git init --bare /git/remote/test-base.git
|
||||
git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all /git/remote &>/dev/null &
|
||||
|
||||
# Give the daemon time to start
|
||||
sleep 2
|
||||
|
||||
# Create a local clone and make initial commits
|
||||
mkdir -p /git/local/repos
|
||||
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
||||
cd /git/local/repos/test-base
|
||||
# Create a local clone and make an initial commit
|
||||
mkdir -p /git/local
|
||||
git clone git://127.0.0.1/test-base.git /git/local/test-base
|
||||
cd /git/local/test-base
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Your Name"
|
||||
echo "#test-base" > README.md
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
echo "#test-base :sparkles:" > README.md
|
||||
git add .
|
||||
git commit -m "add sparkles" -m "Change description:
|
||||
- updates README.md to add sparkles to the title"
|
||||
git push -u
|
||||
git log -1 --pretty=oneline
|
||||
git config --global --unset user.email
|
||||
@ -34,8 +30,8 @@ git config -l
|
||||
|
||||
# Clone a server-side fork of the base repo
|
||||
cd $WORKINGDIR
|
||||
git clone --mirror git://127.0.0.1/repos/test-base.git /git/remote/repos/test-fork.git
|
||||
cd /git/remote/repos/test-fork.git
|
||||
git clone --mirror git://127.0.0.1/test-base.git /git/remote/test-fork.git
|
||||
cd /git/remote/test-fork.git
|
||||
git log -1 --pretty=oneline
|
||||
|
||||
# Restore the working directory
|
||||
|
@ -1,31 +1,32 @@
|
||||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||
import {GitAuthHelper} from '../lib/git-auth-helper'
|
||||
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
const REPO_PATH = '/git/local/test-base'
|
||||
|
||||
const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||
const extraheaderConfigKey = 'http.https://github.com/.extraheader'
|
||||
|
||||
describe('git-config-helper integration tests', () => {
|
||||
describe('git-auth-helper tests', () => {
|
||||
let git: GitCommandManager
|
||||
let gitAuthHelper: GitAuthHelper
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
gitAuthHelper = new GitAuthHelper(git)
|
||||
})
|
||||
|
||||
it('tests save and restore with no persisted auth', async () => {
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await gitConfigHelper.close()
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
})
|
||||
|
||||
it('tests configure and removal of auth', async () => {
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await gitConfigHelper.configureToken('github-token')
|
||||
await gitAuthHelper.configureToken('github-token')
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
||||
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
||||
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
||||
)
|
||||
|
||||
await gitConfigHelper.close()
|
||||
await gitAuthHelper.removeAuth()
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
||||
})
|
||||
|
||||
@ -33,53 +34,37 @@ describe('git-config-helper integration tests', () => {
|
||||
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
||||
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
||||
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
|
||||
const exists = await git.configExists(extraheaderConfigKey)
|
||||
expect(exists).toBeFalsy()
|
||||
|
||||
await gitConfigHelper.close()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
|
||||
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
||||
expect(configValue).toEqual(extraheaderConfigValue)
|
||||
|
||||
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()
|
||||
await gitAuthHelper.removeAuth()
|
||||
})
|
||||
|
||||
it('tests adding and removing the safe.directory config', async () => {
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await git.config('safe.directory', '/another-value', true, true)
|
||||
|
||||
await gitAuthHelper.removeSafeDirectory()
|
||||
await gitAuthHelper.addSafeDirectory()
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', REPO_PATH, true)
|
||||
).toBeTruthy()
|
||||
|
||||
await gitConfigHelper.close()
|
||||
await gitAuthHelper.addSafeDirectory()
|
||||
await gitAuthHelper.removeSafeDirectory()
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', REPO_PATH, true)
|
||||
).toBeFalsy()
|
||||
expect(
|
||||
await git.configExists('safe.directory', '/another-value', true)
|
||||
).toBeTruthy()
|
||||
})
|
||||
})
|
@ -1,26 +0,0 @@
|
||||
import {GitCommandManager, Commit} from '../lib/git-command-manager'
|
||||
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
|
||||
describe('git-command-manager integration tests', () => {
|
||||
let git: GitCommandManager
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
await git.checkout('main')
|
||||
})
|
||||
|
||||
it('tests getCommit', async () => {
|
||||
const parent = await git.getCommit('HEAD^')
|
||||
const commit = await git.getCommit('HEAD')
|
||||
expect(parent.subject).toEqual('initial commit')
|
||||
expect(parent.changes).toEqual([
|
||||
{mode: '100644', status: 'A', path: 'README.md'}
|
||||
])
|
||||
expect(commit.subject).toEqual('add sparkles')
|
||||
expect(commit.parents[0]).toEqual(parent.sha)
|
||||
expect(commit.changes).toEqual([
|
||||
{mode: '100644', status: 'M', path: 'README.md'}
|
||||
])
|
||||
})
|
||||
})
|
@ -1,93 +0,0 @@
|
||||
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 ..."
|
||||
|
||||
cat > Dockerfile << EOF
|
||||
FROM node:20-alpine
|
||||
FROM node:16-alpine
|
||||
RUN apk --no-cache add git git-daemon
|
||||
RUN npm install jest jest-environment-jsdom --global
|
||||
WORKDIR /cpr
|
||||
|
@ -44,6 +44,63 @@ 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 () => {
|
||||
const url1 = utils.getRemoteUrl(
|
||||
'HTTPS',
|
||||
|
21
action.yml
21
action.yml
@ -4,10 +4,6 @@ inputs:
|
||||
token:
|
||||
description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
|
||||
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:
|
||||
description: >
|
||||
Relative path under $GITHUB_WORKSPACE to the repository.
|
||||
@ -24,12 +20,12 @@ inputs:
|
||||
description: >
|
||||
The committer name and email address in the format `Display Name <email@address.com>`.
|
||||
Defaults to the GitHub Actions bot user.
|
||||
default: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
|
||||
default: 'GitHub <noreply@github.com>'
|
||||
author:
|
||||
description: >
|
||||
The author name and email address in the format `Display Name <email@address.com>`.
|
||||
Defaults to the user who triggered the workflow run.
|
||||
default: '${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>'
|
||||
default: '${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>'
|
||||
signoff:
|
||||
description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.'
|
||||
default: false
|
||||
@ -38,7 +34,8 @@ inputs:
|
||||
default: 'create-pull-request/patch'
|
||||
delete-branch:
|
||||
description: >
|
||||
Delete the `branch` if it doesn't have an active pull request associated with it.
|
||||
Delete the `branch` when closing pull requests, and when undeleted after merging.
|
||||
Recommend `true`.
|
||||
default: false
|
||||
branch-suffix:
|
||||
description: 'The branch suffix type when using the alternative branching strategy.'
|
||||
@ -51,9 +48,6 @@ inputs:
|
||||
A fork of the checked out parent repository to which the pull request branch will be pushed.
|
||||
e.g. `owner/repo-fork`.
|
||||
The pull request will be created to merge the fork's branch into the parent's base.
|
||||
sign-commits:
|
||||
description: 'Sign commits as `github-actions[bot]` when using `GITHUB_TOKEN`, or your own bot when using GitHub App tokens.'
|
||||
default: false
|
||||
title:
|
||||
description: 'The title of the pull request.'
|
||||
default: 'Changes by create-pull-request action'
|
||||
@ -77,9 +71,6 @@ inputs:
|
||||
draft:
|
||||
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
|
||||
default: false
|
||||
maintainer-can-modify:
|
||||
description: 'Indicates whether maintainers can modify the pull request.'
|
||||
default: true
|
||||
outputs:
|
||||
pull-request-number:
|
||||
description: 'The pull request number'
|
||||
@ -89,10 +80,8 @@ outputs:
|
||||
description: 'The pull request operation performed by the action, `created`, `updated` or `closed`.'
|
||||
pull-request-head-sha:
|
||||
description: 'The commit SHA of the pull request branch.'
|
||||
pull-request-branch:
|
||||
description: 'The pull request branch name'
|
||||
runs:
|
||||
using: 'node20'
|
||||
using: 'node16'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'git-pull-request'
|
||||
|
64844
dist/index.js
vendored
64844
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
@ -1,53 +0,0 @@
|
||||
# Common issues
|
||||
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Create using an existing branch as the PR branch](#create-using-an-existing-branch-as-the-pr-branch)
|
||||
- [Frequently requested features](#use-case-create-a-pull-request-to-update-x-on-release)
|
||||
- [Disable force updates to existing PR branches](#disable-force-updates-to-existing-pr-branches)
|
||||
- [Add a no-verify option to bypass git hooks](#add-a-no-verify-option-to-bypass-git-hooks)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Create using an existing branch as the PR branch
|
||||
|
||||
A common point of confusion is to try and use an existing branch containing changes to raise in a PR as the `branch` input. This will not work because the action is primarily designed to be used in workflows where the PR branch does not exist yet. The action creates and manages the PR branch itself.
|
||||
|
||||
If you have an existing branch that you just want to create a PR for, then I recommend using the official [GitHub CLI](https://cli.github.com/manual/gh_pr_create) in a workflow step.
|
||||
|
||||
Alternatively, if you are trying to keep a branch up to date with another branch, then you can follow [this example](https://github.com/peter-evans/create-pull-request/blob/main/docs/examples.md#keep-a-branch-up-to-date-with-another).
|
||||
|
||||
## Frequently requested features
|
||||
|
||||
### Disable force updates to existing PR branches
|
||||
|
||||
This behaviour is fundamental to how the action works and is a conscious design decision. The "rule" that I based this design on is that when a workflow executes the action to create or update a PR, the result of those two possible actions should never be different. The easiest way to maintain that consistency is to rebase the PR branch and force push it.
|
||||
|
||||
If you want to avoid this behaviour there are some things that might work depending on your use case:
|
||||
- Check if the pull request branch exists in a separate step before the action runs and act accordingly.
|
||||
- Use the [alternative strategy](https://github.com/peter-evans/create-pull-request#alternative-strategy---always-create-a-new-pull-request-branch) of always creating a new PR that won't be updated by the action.
|
||||
- [Create your own commits](https://github.com/peter-evans/create-pull-request#create-your-own-commits) each time the action is created/updated.
|
||||
|
||||
### Add a no-verify option to bypass git hooks
|
||||
|
||||
Presently, there is no plan to add this feature to the action.
|
||||
The reason is that I'm trying very hard to keep the interface for this action to a minimum to prevent it becoming bloated and complicated.
|
||||
|
||||
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.
|
||||
|
||||
- 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.
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
[ -n "$CI" ] && exit 0
|
||||
```
|
||||
- If preventing the hooks installing is problematic, just delete them in a workflow step before the action runs.
|
||||
```yml
|
||||
- run: rm .git/hooks -rf
|
||||
```
|
@ -16,9 +16,7 @@ This document covers terminology, how the action works, general usage guidelines
|
||||
- [Push using SSH (deploy keys)](#push-using-ssh-deploy-keys)
|
||||
- [Push pull request branches to a fork](#push-pull-request-branches-to-a-fork)
|
||||
- [Authenticating with GitHub App generated tokens](#authenticating-with-github-app-generated-tokens)
|
||||
- [Commit signing](#commit-signing)
|
||||
- [Commit signature verification for bots](#commit-signature-verification-for-bots)
|
||||
- [GPG commit signature verification](#gpg-commit-signature-verification)
|
||||
- [GPG commit signature verification](#gpg-commit-signature-verification)
|
||||
- [Running in a container or on self-hosted runners](#running-in-a-container-or-on-self-hosted-runners)
|
||||
|
||||
## Terminology
|
||||
@ -38,7 +36,7 @@ For each [event type](https://docs.github.com/en/actions/reference/events-that-t
|
||||
The default can be overridden by specifying a `ref` on checkout.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: develop
|
||||
```
|
||||
@ -75,7 +73,7 @@ jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
```
|
||||
|
||||
There may be use cases where it makes sense to execute the workflow on a branch that is not the base of the pull request. In these cases, the base branch can be specified with the `base` action input. The action will attempt to rebase changes made during the workflow on to the actual base.
|
||||
@ -90,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.
|
||||
|
||||
```yml
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
base: ${{ github.head_ref }}
|
||||
```
|
||||
@ -98,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.
|
||||
|
||||
```yml
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
base: main
|
||||
```
|
||||
@ -175,14 +173,14 @@ This action uses [ncc](https://github.com/vercel/ncc) to compile the Node.js cod
|
||||
Checking out a branch from a different repository from where the workflow is executing will make *that repository* the target for the created pull request. In this case, a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) is required.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
repository: owner/repo
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
```
|
||||
@ -202,14 +200,14 @@ How to use SSH (deploy keys) with create-pull-request action:
|
||||
|
||||
```yml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
```
|
||||
|
||||
### Push pull request branches to a fork
|
||||
@ -228,11 +226,11 @@ Note that if you choose to use this method (not give the machine account `write`
|
||||
6. As shown in the following example workflow, set the `push-to-fork` input to the full repository name of the fork.
|
||||
|
||||
```yaml
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.MACHINE_USER_PAT }}
|
||||
push-to-fork: machine-user/fork-of-repository
|
||||
@ -262,74 +260,27 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
||||
|
||||
4. Set secrets on your repository containing the GitHub App ID, and the private key you created in step 2. e.g. `APP_ID`, `APP_PRIVATE_KEY`.
|
||||
|
||||
5. The following example workflow shows how to use [actions/create-github-app-token](https://github.com/actions/create-github-app-token) to generate a token for use with this action.
|
||||
5. The following example workflow shows how to use [tibdex/github-app-token](https://github.com/tibdex/github-app-token) to generate a token for use with this action.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/create-github-app-token@v1
|
||||
- uses: tibdex/github-app-token@v1
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
```
|
||||
|
||||
### Commit signing
|
||||
|
||||
[Commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) is a feature where GitHub will mark signed commits as "verified" to give confidence that changes are from a trusted source. Some organizations require commit signing, and enforce it with branch protection rules.
|
||||
|
||||
The action supports two methods to sign commits, [commit signature verification for bots](#commit-signature-verification-for-bots), and [GPG commit signature verification](#gpg-commit-signature-verification).
|
||||
|
||||
#### Commit signature verification for bots
|
||||
|
||||
The action can sign commits as `github-actions[bot]` when using the repository's default `GITHUB_TOKEN`, or your own bot when using [GitHub App tokens](#authenticating-with-github-app-generated-tokens).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> - When setting `sign-commits: true` the action will ignore the `committer` and `author` inputs.
|
||||
> - If you attempt to use a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) the action will create the pull request, but commits will not be signed. Commit signing is only supported with bot generated tokens.
|
||||
|
||||
In this example the `token` input is not supplied, so the action will use the repository's default `GITHUB_TOKEN`. This will sign commits as `github-actions[bot]`.
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
sign-commits: true
|
||||
```
|
||||
|
||||
In this example, the `token` input is generated using a GitHub App. This will sign commits as `<application-name>[bot]`.
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
sign-commits: true
|
||||
```
|
||||
|
||||
#### GPG commit signature verification
|
||||
### GPG commit signature verification
|
||||
|
||||
The action can use GPG to sign commits with a GPG key that you generate yourself.
|
||||
|
||||
@ -353,19 +304,19 @@ The action can use GPG to sign commits with a GPG key that you generate yourself
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: crazy-max/ghaction-import-gpg@v5
|
||||
- uses: crazy-max/ghaction-import-gpg@v3
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
git_user_signingkey: true
|
||||
git_commit_gpgsign: true
|
||||
git-user-signingkey: true
|
||||
git-commit-gpgsign: true
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
committer: example <email@example.com>
|
||||
@ -390,12 +341,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: apk --no-cache add git
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
```
|
||||
|
||||
**Ubuntu container example:**
|
||||
@ -413,10 +364,10 @@ jobs:
|
||||
add-apt-repository -y ppa:git-core/ppa
|
||||
apt-get install -y git
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
```
|
||||
|
@ -19,6 +19,7 @@
|
||||
- [autopep8](#autopep8)
|
||||
- [Misc workflow tips](#misc-workflow-tips)
|
||||
- [Filtering push events](#filtering-push-events)
|
||||
- [Bypassing git hooks](#bypassing-git-hooks)
|
||||
- [Dynamic configuration using variables](#dynamic-configuration-using-variables)
|
||||
- [Using a markdown template](#using-a-markdown-template)
|
||||
- [Debugging GitHub Actions](#debugging-github-actions)
|
||||
@ -42,14 +43,14 @@ jobs:
|
||||
updateAuthors:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Update AUTHORS
|
||||
run: |
|
||||
git log --format='%aN <%aE>%n%cN <%cE>' | sort -u > AUTHORS
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: update authors
|
||||
title: Update AUTHORS
|
||||
@ -73,7 +74,7 @@ jobs:
|
||||
productionPromotion:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: production
|
||||
- name: Reset promotion branch
|
||||
@ -81,7 +82,7 @@ jobs:
|
||||
git fetch origin main:main
|
||||
git reset --hard main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
branch: production-promotion
|
||||
```
|
||||
@ -106,7 +107,7 @@ jobs:
|
||||
updateChangelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Update Changelog
|
||||
@ -116,7 +117,7 @@ jobs:
|
||||
./git-chglog -o CHANGELOG.md
|
||||
rm git-chglog
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: update changelog
|
||||
title: Update Changelog
|
||||
@ -144,7 +145,7 @@ jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16.x'
|
||||
@ -153,7 +154,7 @@ jobs:
|
||||
npx -p npm-check-updates ncu -u
|
||||
npm install
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
@ -180,7 +181,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
@ -204,7 +205,7 @@ jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
@ -214,7 +215,7 @@ jobs:
|
||||
- name: Perform dependency resolution and write new lockfiles
|
||||
run: ./gradlew dependencies --write-locks
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
@ -242,14 +243,14 @@ jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Update dependencies
|
||||
run: |
|
||||
cargo install cargo-edit
|
||||
cargo update
|
||||
cargo upgrade --to-lockfile
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
@ -277,7 +278,7 @@ jobs:
|
||||
updateSwagger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get Latest Swagger UI Release
|
||||
id: swagger-ui
|
||||
run: |
|
||||
@ -307,7 +308,7 @@ jobs:
|
||||
# Update current release
|
||||
echo ${{ steps.swagger-ui.outputs.release_tag }} > swagger-ui.version
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
@ -342,7 +343,7 @@ jobs:
|
||||
updateFork:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: fork-owner/repo
|
||||
- name: Reset the default branch with upstream changes
|
||||
@ -351,7 +352,7 @@ jobs:
|
||||
git fetch upstream main:upstream-main
|
||||
git reset --hard upstream-main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
branch: upstream-changes
|
||||
@ -370,7 +371,7 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download website
|
||||
run: |
|
||||
wget \
|
||||
@ -384,7 +385,7 @@ jobs:
|
||||
--domains quotes.toscrape.com \
|
||||
http://quotes.toscrape.com/
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: update local website copy
|
||||
title: Automated Updates to Local Website Copy
|
||||
@ -466,7 +467,7 @@ jobs:
|
||||
if: startsWith(github.head_ref, 'autopep8-patches') == false && github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: autopep8
|
||||
@ -481,7 +482,7 @@ jobs:
|
||||
echo "branch-name=$branch-name" >> $GITHUB_OUTPUT
|
||||
- name: Create Pull Request
|
||||
if: steps.autopep8.outputs.exit-code == 2
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: autopep8 action fixes
|
||||
title: Fixes by autopep8 action
|
||||
@ -515,16 +516,28 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/heads/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
...
|
||||
|
||||
someOtherJob:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
...
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
The following examples show how configuration for the action can be dynamically defined in a previous workflow step.
|
||||
@ -540,7 +553,7 @@ Note that the step where output variables are defined must have an id.
|
||||
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT
|
||||
echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
title: ${{ steps.vars.outputs.pr_title }}
|
||||
body: ${{ steps.vars.outputs.pr_body }}
|
||||
@ -566,7 +579,7 @@ The template is rendered using the [render-template](https://github.com/chuhlomi
|
||||
bar: that
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
body: ${{ steps.template.outputs.result }}
|
||||
```
|
||||
|
@ -1,23 +1,3 @@
|
||||
## 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`
|
||||
|
||||
### Behaviour changes
|
||||
|
4744
package-lock.json
generated
4744
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-pull-request",
|
||||
"version": "6.0.0",
|
||||
"version": "5.0.0",
|
||||
"private": true,
|
||||
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
||||
"main": "lib/main.js",
|
||||
@ -29,35 +29,31 @@
|
||||
},
|
||||
"homepage": "https://github.com/peter-evans/create-pull-request",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@octokit/core": "^6.1.2",
|
||||
"@octokit/plugin-paginate-rest": "^11.3.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^13.2.4",
|
||||
"@octokit/plugin-throttling": "^9.3.1",
|
||||
"p-limit": "^6.1.0",
|
||||
"@octokit/core": "^4.2.1",
|
||||
"@octokit/plugin-paginate-rest": "^5.0.1",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
"undici": "^6.19.7",
|
||||
"uuid": "^9.0.1"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^18.19.44",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^18.16.18",
|
||||
"@typescript-eslint/parser": "^5.59.11",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"eslint-plugin-github": "^4.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"jest": "^29.5.0",
|
||||
"jest-circus": "^29.4.2",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.4",
|
||||
"typescript": "^5.5.4"
|
||||
"prettier": "^2.8.8",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
import * as core from '@actions/core'
|
||||
import {GitCommandManager, Commit} from './git-command-manager'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
|
||||
const CHERRYPICK_EMPTY =
|
||||
'The previous cherry-pick is now empty, possibly due to conflict resolution.'
|
||||
const NOTHING_TO_COMMIT = 'nothing to commit, working tree clean'
|
||||
|
||||
const FETCH_DEPTH_MARGIN = 10
|
||||
|
||||
export enum WorkingBaseType {
|
||||
Branch = 'branch',
|
||||
Commit = 'commit'
|
||||
@ -33,13 +31,11 @@ export async function getWorkingBaseAndType(
|
||||
export async function tryFetch(
|
||||
git: GitCommandManager,
|
||||
remote: string,
|
||||
branch: string,
|
||||
depth: number
|
||||
branch: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await git.fetch([`${branch}:refs/remotes/${remote}/${branch}`], remote, [
|
||||
'--force',
|
||||
`--depth=${depth}`
|
||||
'--force'
|
||||
])
|
||||
return true
|
||||
} catch {
|
||||
@ -47,24 +43,6 @@ export async function tryFetch(
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildBranchCommits(
|
||||
git: GitCommandManager,
|
||||
base: string,
|
||||
branch: string
|
||||
): Promise<Commit[]> {
|
||||
const output = await git.exec(['log', '--format=%H', `${base}..${branch}`])
|
||||
const shas = output.stdout
|
||||
.split('\n')
|
||||
.filter(x => x !== '')
|
||||
.reverse()
|
||||
const commits: Commit[] = []
|
||||
for (const sha of shas) {
|
||||
const commit = await git.getCommit(sha)
|
||||
commits.push(commit)
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
// Return the number of commits that branch2 is ahead of branch1
|
||||
async function commitsAhead(
|
||||
git: GitCommandManager,
|
||||
@ -132,9 +110,7 @@ interface CreateOrUpdateBranchResult {
|
||||
action: string
|
||||
base: string
|
||||
hasDiffWithBase: boolean
|
||||
baseSha: string
|
||||
headSha: string
|
||||
branchCommits: Commit[]
|
||||
}
|
||||
|
||||
export async function createOrUpdateBranch(
|
||||
@ -144,8 +120,7 @@ export async function createOrUpdateBranch(
|
||||
branch: string,
|
||||
branchRemoteName: string,
|
||||
signoff: boolean,
|
||||
addPaths: string[],
|
||||
signCommits: boolean = false
|
||||
addPaths: string[]
|
||||
): Promise<CreateOrUpdateBranchResult> {
|
||||
// Get the working base.
|
||||
// When a ref, it may or may not be the actual base.
|
||||
@ -165,9 +140,7 @@ export async function createOrUpdateBranch(
|
||||
action: 'none',
|
||||
base: base,
|
||||
hasDiffWithBase: false,
|
||||
baseSha: '',
|
||||
headSha: '',
|
||||
branchCommits: []
|
||||
headSha: ''
|
||||
}
|
||||
|
||||
// Save the working base changes to a temporary branch
|
||||
@ -200,12 +173,21 @@ export async function createOrUpdateBranch(
|
||||
// Stash any uncommitted tracked and untracked changes
|
||||
const stashed = await git.stashPush(['--include-untracked'])
|
||||
|
||||
// Reset the working base
|
||||
// Perform fetch and reset the working base
|
||||
// Commits made during the workflow will be removed
|
||||
if (workingBaseType == WorkingBaseType.Branch) {
|
||||
core.info(`Resetting working base branch '${workingBase}'`)
|
||||
await git.checkout(workingBase)
|
||||
await git.exec(['reset', '--hard', `${baseRemote}/${workingBase}`])
|
||||
if (branchRemoteName == 'fork') {
|
||||
// If pushing to a fork we must fetch with 'unshallow' to avoid the following error on git push
|
||||
// ! [remote rejected] HEAD -> tests/push-branch-to-fork (shallow update not allowed)
|
||||
await git.fetch([`${workingBase}:${workingBase}`], baseRemote, [
|
||||
'--force'
|
||||
])
|
||||
} else {
|
||||
// If the remote is 'origin' we can git reset
|
||||
await git.checkout(workingBase)
|
||||
await git.exec(['reset', '--hard', `${baseRemote}/${workingBase}`])
|
||||
}
|
||||
}
|
||||
|
||||
// If the working base is not the base, rebase the temp branch commits
|
||||
@ -214,13 +196,8 @@ export async function createOrUpdateBranch(
|
||||
core.info(
|
||||
`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
|
||||
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
||||
await git.checkout(base)
|
||||
// Cherrypick commits from the temporary branch starting from the working base
|
||||
const commits = await git.revList(
|
||||
@ -239,18 +216,11 @@ export async function createOrUpdateBranch(
|
||||
// Reset the temp branch to the working index
|
||||
await git.checkout(tempBranch, 'HEAD')
|
||||
// Reset the base
|
||||
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!(await tryFetch(git, branchRemoteName, branch, fetchDepth))) {
|
||||
if (!(await tryFetch(git, branchRemoteName, branch))) {
|
||||
// The pull request branch does not exist
|
||||
core.info(`Pull request branch '${branch}' does not exist yet.`)
|
||||
// Create the pull request branch
|
||||
@ -284,6 +254,7 @@ export async function createOrUpdateBranch(
|
||||
// temp branch. This catches a case where the base branch has been force pushed to
|
||||
// a new commit.
|
||||
// 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)
|
||||
if (
|
||||
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||
@ -312,15 +283,8 @@ export async function createOrUpdateBranch(
|
||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||
}
|
||||
|
||||
// Get the base and head SHAs
|
||||
result.baseSha = await git.revParse(base)
|
||||
result.headSha = await git.revParse(branch)
|
||||
|
||||
// NOTE: This could always be built and returned. Maybe remove when there is confidence in buildBranchCommits.
|
||||
if (signCommits) {
|
||||
// Build the branch commits
|
||||
result.branchCommits = await buildBranchCommits(git, base, branch)
|
||||
}
|
||||
// Get the pull request branch SHA
|
||||
result.headSha = await git.revParse('HEAD')
|
||||
|
||||
// Delete the temporary branch
|
||||
await git.exec(['branch', '--delete', '--force', tempBranch])
|
||||
|
@ -6,12 +6,11 @@ import {
|
||||
} from './create-or-update-branch'
|
||||
import {GitHubHelper} from './github-helper'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {GitConfigHelper} from './git-config-helper'
|
||||
import {GitAuthHelper} from './git-auth-helper'
|
||||
import * as utils from './utils'
|
||||
|
||||
export interface Inputs {
|
||||
token: string
|
||||
gitToken: string
|
||||
path: string
|
||||
addPaths: string[]
|
||||
commitMessage: string
|
||||
@ -23,7 +22,6 @@ export interface Inputs {
|
||||
branchSuffix: string
|
||||
base: string
|
||||
pushToFork: string
|
||||
signCommits: boolean
|
||||
title: string
|
||||
body: string
|
||||
bodyPath: string
|
||||
@ -33,23 +31,48 @@ export interface Inputs {
|
||||
teamReviewers: string[]
|
||||
milestone: number
|
||||
draft: boolean
|
||||
maintainerCanModify: boolean
|
||||
}
|
||||
|
||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
let gitConfigHelper, git
|
||||
let gitAuthHelper
|
||||
try {
|
||||
core.startGroup('Prepare git configuration')
|
||||
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)
|
||||
git = await GitCommandManager.create(repoPath)
|
||||
gitConfigHelper = await GitConfigHelper.create(git)
|
||||
// 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')
|
||||
gitAuthHelper = new GitAuthHelper(git)
|
||||
await gitAuthHelper.addSafeDirectory()
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
core.endGroup()
|
||||
|
||||
// Init the GitHub client
|
||||
const githubHelper = new GitHubHelper(inputs.token)
|
||||
|
||||
core.startGroup('Determining the base and head repositories')
|
||||
const baseRemote = gitConfigHelper.getGitRemote()
|
||||
// Init the GitHub clients
|
||||
const ghBranch = new GitHubHelper(baseRemote.hostname, inputs.gitToken)
|
||||
const ghPull = new GitHubHelper(baseRemote.hostname, inputs.token)
|
||||
// Determine the base repository from git config
|
||||
const remoteUrl = await git.tryGetRemoteUrl()
|
||||
const baseRemote = utils.getRemoteDetail(remoteUrl)
|
||||
// Determine the head repository; the target for the pull request branch
|
||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
||||
const branchRepository = inputs.pushToFork
|
||||
@ -60,22 +83,12 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
core.info(
|
||||
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
||||
)
|
||||
const baseParentRepository = await ghBranch.getRepositoryParent(
|
||||
baseRemote.repository
|
||||
const parentRepository = await githubHelper.getRepositoryParent(
|
||||
branchRepository
|
||||
)
|
||||
const branchParentRepository =
|
||||
await ghBranch.getRepositoryParent(branchRepository)
|
||||
if (branchParentRepository == null) {
|
||||
if (parentRepository != baseRemote.repository) {
|
||||
throw new Error(
|
||||
`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.`
|
||||
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`
|
||||
)
|
||||
}
|
||||
// Add a remote for the fork
|
||||
@ -94,7 +107,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
// Configure auth
|
||||
if (baseRemote.protocol == 'HTTPS') {
|
||||
core.startGroup('Configuring credential for HTTPS authentication')
|
||||
await gitConfigHelper.configureToken(inputs.gitToken)
|
||||
await gitAuthHelper.configureToken(inputs.token)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
@ -177,12 +190,6 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
)
|
||||
core.endGroup()
|
||||
|
||||
// Action outputs
|
||||
const outputs = new Map<string, string>()
|
||||
outputs.set('pull-request-branch', inputs.branch)
|
||||
outputs.set('pull-request-operation', 'none')
|
||||
outputs.set('pull-request-commits-verified', 'false')
|
||||
|
||||
// Create or update the pull request branch
|
||||
core.startGroup('Create or update the pull request branch')
|
||||
const result = await createOrUpdateBranch(
|
||||
@ -192,12 +199,8 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
inputs.branch,
|
||||
branchRemoteName,
|
||||
inputs.signoff,
|
||||
inputs.addPaths,
|
||||
inputs.signCommits
|
||||
inputs.addPaths
|
||||
)
|
||||
outputs.set('pull-request-head-sha', result.headSha)
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
core.endGroup()
|
||||
|
||||
if (['created', 'updated'].includes(result.action)) {
|
||||
@ -205,50 +208,37 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
core.startGroup(
|
||||
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
||||
)
|
||||
if (inputs.signCommits) {
|
||||
// Create signed commits via the GitHub API
|
||||
const stashed = await git.stashPush(['--include-untracked'])
|
||||
await git.checkout(inputs.branch)
|
||||
const pushSignedCommitsResult = await ghBranch.pushSignedCommits(
|
||||
result.branchCommits,
|
||||
result.baseSha,
|
||||
repoPath,
|
||||
branchRepository,
|
||||
inputs.branch
|
||||
)
|
||||
outputs.set('pull-request-head-sha', pushSignedCommitsResult.sha)
|
||||
outputs.set(
|
||||
'pull-request-commits-verified',
|
||||
pushSignedCommitsResult.verified.toString()
|
||||
)
|
||||
await git.checkout('-')
|
||||
if (stashed) {
|
||||
await git.stashPop()
|
||||
}
|
||||
} else {
|
||||
await git.push([
|
||||
'--force-with-lease',
|
||||
branchRemoteName,
|
||||
`${inputs.branch}:refs/heads/${inputs.branch}`
|
||||
])
|
||||
}
|
||||
await git.push([
|
||||
'--force-with-lease',
|
||||
branchRemoteName,
|
||||
`${inputs.branch}:refs/heads/${inputs.branch}`
|
||||
])
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
|
||||
if (result.hasDiffWithBase) {
|
||||
// Create or update the pull request
|
||||
core.startGroup('Create or update the pull request')
|
||||
const pull = await ghPull.createOrUpdatePullRequest(
|
||||
const pull = await githubHelper.createOrUpdatePullRequest(
|
||||
inputs,
|
||||
baseRemote.repository,
|
||||
branchRepository
|
||||
)
|
||||
outputs.set('pull-request-number', pull.number.toString())
|
||||
outputs.set('pull-request-url', pull.html_url)
|
||||
core.endGroup()
|
||||
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-number', pull.number)
|
||||
core.setOutput('pull-request-url', pull.html_url)
|
||||
if (pull.created) {
|
||||
outputs.set('pull-request-operation', 'created')
|
||||
core.setOutput('pull-request-operation', 'created')
|
||||
} else if (result.action == 'updated') {
|
||||
outputs.set('pull-request-operation', 'updated')
|
||||
core.setOutput('pull-request-operation', 'updated')
|
||||
}
|
||||
core.setOutput('pull-request-head-sha', result.headSha)
|
||||
// Deprecated
|
||||
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
|
||||
core.endGroup()
|
||||
@ -267,26 +257,21 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
branchRemoteName,
|
||||
`refs/heads/${inputs.branch}`
|
||||
])
|
||||
outputs.set('pull-request-operation', 'closed')
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-operation', 'closed')
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
for (const [key, value] of outputs) {
|
||||
core.info(`${key} = ${value}`)
|
||||
core.setOutput(key, value)
|
||||
}
|
||||
core.endGroup()
|
||||
} catch (error) {
|
||||
core.setFailed(utils.getErrorMessage(error))
|
||||
} finally {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
core.startGroup('Restore git configuration')
|
||||
if (inputs.pushToFork) {
|
||||
await git.exec(['remote', 'rm', 'fork'])
|
||||
}
|
||||
await gitConfigHelper.close()
|
||||
await gitAuthHelper.removeAuth()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
await gitAuthHelper.removeSafeDirectory()
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
|
@ -5,42 +5,22 @@ import * as path from 'path'
|
||||
import {URL} from 'url'
|
||||
import * as utils from './utils'
|
||||
|
||||
interface GitRemote {
|
||||
hostname: string
|
||||
protocol: string
|
||||
repository: string
|
||||
}
|
||||
|
||||
export class GitConfigHelper {
|
||||
export class GitAuthHelper {
|
||||
private git: GitCommandManager
|
||||
private gitConfigPath = ''
|
||||
private workingDirectory: string
|
||||
private safeDirectoryConfigKey = 'safe.directory'
|
||||
private safeDirectoryAdded = false
|
||||
private remoteUrl = ''
|
||||
private extraheaderConfigKey = ''
|
||||
private extraheaderConfigKey: string
|
||||
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
||||
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
||||
private persistedExtraheaderConfigValue = ''
|
||||
|
||||
private constructor(git: GitCommandManager) {
|
||||
constructor(git: GitCommandManager) {
|
||||
this.git = git
|
||||
this.workingDirectory = this.git.getWorkingDirectory()
|
||||
}
|
||||
|
||||
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()
|
||||
const serverUrl = this.getServerUrl()
|
||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
||||
}
|
||||
|
||||
async addSafeDirectory(): Promise<void> {
|
||||
@ -70,57 +50,7 @@ export class GitConfigHelper {
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
this.persistedExtraheaderConfigValue = await this.getAndUnset()
|
||||
}
|
||||
@ -214,4 +144,8 @@ export class GitConfigHelper {
|
||||
content = content.replace(find, replace)
|
||||
await fs.promises.writeFile(this.gitConfigPath, content)
|
||||
}
|
||||
|
||||
private getServerUrl(): URL {
|
||||
return new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com')
|
||||
}
|
||||
}
|
@ -5,19 +5,6 @@ import * as path from 'path'
|
||||
|
||||
const tagsRefSpec = '+refs/tags/*:refs/tags/*'
|
||||
|
||||
export type Commit = {
|
||||
sha: string
|
||||
tree: string
|
||||
parents: string[]
|
||||
subject: string
|
||||
body: string
|
||||
changes: {
|
||||
mode: string
|
||||
status: 'A' | 'M' | 'D'
|
||||
path: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export class GitCommandManager {
|
||||
private gitPath: string
|
||||
private workingDirectory: string
|
||||
@ -118,8 +105,7 @@ export class GitCommandManager {
|
||||
async fetch(
|
||||
refSpec: string[],
|
||||
remoteName?: string,
|
||||
options?: string[],
|
||||
unshallow = false
|
||||
options?: string[]
|
||||
): Promise<void> {
|
||||
const args = ['-c', 'protocol.version=2', 'fetch']
|
||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||
@ -127,9 +113,7 @@ export class GitCommandManager {
|
||||
}
|
||||
|
||||
args.push('--progress', '--no-recurse-submodules')
|
||||
|
||||
if (
|
||||
unshallow &&
|
||||
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))
|
||||
) {
|
||||
args.push('--unshallow')
|
||||
@ -151,43 +135,6 @@ export class GitCommandManager {
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
async getCommit(ref: string): Promise<Commit> {
|
||||
const endOfBody = '###EOB###'
|
||||
const output = await this.exec([
|
||||
'show',
|
||||
'--raw',
|
||||
'--cc',
|
||||
'--diff-filter=AMD',
|
||||
`--format=%H%n%T%n%P%n%s%n%b%n${endOfBody}`,
|
||||
ref
|
||||
])
|
||||
const lines = output.stdout.split('\n')
|
||||
const endOfBodyIndex = lines.lastIndexOf(endOfBody)
|
||||
const detailLines = lines.slice(0, endOfBodyIndex)
|
||||
|
||||
return <Commit>{
|
||||
sha: detailLines[0],
|
||||
tree: detailLines[1],
|
||||
parents: detailLines[2].split(' '),
|
||||
subject: detailLines[3],
|
||||
body: detailLines.slice(4, endOfBodyIndex).join('\n'),
|
||||
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
|
||||
const change = line.match(
|
||||
/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/
|
||||
)
|
||||
if (change) {
|
||||
return {
|
||||
mode: change[3] === 'D' ? change[1] : change[2],
|
||||
status: change[3],
|
||||
path: change[4]
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unexpected line format: ${line}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
||||
const output = await this.exec([
|
||||
'config',
|
||||
|
@ -1,16 +1,10 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Inputs} from './create-pull-request'
|
||||
import {Commit} from './git-command-manager'
|
||||
import {Octokit, OctokitOptions, throttleOptions} from './octokit-client'
|
||||
import pLimit from 'p-limit'
|
||||
import {Octokit, OctokitOptions} from './octokit-client'
|
||||
import * as utils from './utils'
|
||||
|
||||
const ERROR_PR_ALREADY_EXISTS = 'A pull request already exists for'
|
||||
const ERROR_PR_REVIEW_TOKEN_SCOPE =
|
||||
'Validation Failed: "Could not resolve to a node with the global id of'
|
||||
const ERROR_PR_FORK_COLLAB = `Fork collab can't be granted by someone without permission`
|
||||
|
||||
const blobCreationLimit = pLimit(8)
|
||||
|
||||
interface Repository {
|
||||
owner: string
|
||||
@ -23,32 +17,15 @@ interface Pull {
|
||||
created: boolean
|
||||
}
|
||||
|
||||
interface CommitResponse {
|
||||
sha: string
|
||||
verified: boolean
|
||||
}
|
||||
|
||||
type TreeObject = {
|
||||
path: string
|
||||
mode: '100644' | '100755' | '040000' | '160000' | '120000'
|
||||
sha: string | null
|
||||
type: 'blob'
|
||||
}
|
||||
|
||||
export class GitHubHelper {
|
||||
private octokit: InstanceType<typeof Octokit>
|
||||
|
||||
constructor(githubServerHostname: string, token: string) {
|
||||
constructor(token: string) {
|
||||
const options: OctokitOptions = {}
|
||||
if (token) {
|
||||
options.auth = `${token}`
|
||||
}
|
||||
if (githubServerHostname !== 'github.com') {
|
||||
options.baseUrl = `https://${githubServerHostname}/api/v3`
|
||||
} else {
|
||||
options.baseUrl = 'https://api.github.com'
|
||||
}
|
||||
options.throttle = throttleOptions
|
||||
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com'
|
||||
this.octokit = new Octokit(options)
|
||||
}
|
||||
|
||||
@ -78,8 +55,7 @@ export class GitHubHelper {
|
||||
head_repo: headRepository,
|
||||
base: inputs.base,
|
||||
body: inputs.body,
|
||||
draft: inputs.draft,
|
||||
maintainer_can_modify: inputs.maintainerCanModify
|
||||
draft: inputs.draft
|
||||
})
|
||||
core.info(
|
||||
`Created pull request #${pull.number} (${headBranch} => ${inputs.base})`
|
||||
@ -90,17 +66,10 @@ export class GitHubHelper {
|
||||
created: true
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMessage = utils.getErrorMessage(e)
|
||||
if (errorMessage.includes(ERROR_PR_ALREADY_EXISTS)) {
|
||||
if (
|
||||
utils.getErrorMessage(e).includes(`A pull request already exists for`)
|
||||
) {
|
||||
core.info(`A pull request already exists for ${headBranch}`)
|
||||
} else if (errorMessage.includes(ERROR_PR_FORK_COLLAB)) {
|
||||
core.warning(
|
||||
'An attempt was made to create a pull request using a token that does not have write access to the head branch.'
|
||||
)
|
||||
core.warning(
|
||||
`For this case, set input 'maintainer-can-modify' to 'false' to allow pull request creation.`
|
||||
)
|
||||
throw e
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
@ -131,12 +100,14 @@ export class GitHubHelper {
|
||||
}
|
||||
}
|
||||
|
||||
async getRepositoryParent(headRepository: string): Promise<string | null> {
|
||||
async getRepositoryParent(headRepository: string): Promise<string> {
|
||||
const {data: headRepo} = await this.octokit.rest.repos.get({
|
||||
...this.parseRepository(headRepository)
|
||||
})
|
||||
if (!headRepo.parent) {
|
||||
return null
|
||||
throw new Error(
|
||||
`Repository '${headRepository}' is not a fork. Unable to continue.`
|
||||
)
|
||||
}
|
||||
return headRepo.parent.full_name
|
||||
}
|
||||
@ -211,121 +182,4 @@ export class GitHubHelper {
|
||||
|
||||
return pull
|
||||
}
|
||||
|
||||
async pushSignedCommits(
|
||||
branchCommits: Commit[],
|
||||
baseSha: string,
|
||||
repoPath: string,
|
||||
branchRepository: string,
|
||||
branch: string
|
||||
): Promise<CommitResponse> {
|
||||
let headCommit: CommitResponse = {
|
||||
sha: baseSha,
|
||||
verified: false
|
||||
}
|
||||
for (const commit of branchCommits) {
|
||||
headCommit = await this.createCommit(
|
||||
commit,
|
||||
[headCommit.sha],
|
||||
repoPath,
|
||||
branchRepository
|
||||
)
|
||||
}
|
||||
await this.createOrUpdateRef(branchRepository, branch, headCommit.sha)
|
||||
return headCommit
|
||||
}
|
||||
|
||||
private async createCommit(
|
||||
commit: Commit,
|
||||
parents: string[],
|
||||
repoPath: string,
|
||||
branchRepository: string
|
||||
): Promise<CommitResponse> {
|
||||
const repository = this.parseRepository(branchRepository)
|
||||
let treeSha = commit.tree
|
||||
if (commit.changes.length > 0) {
|
||||
core.info(`Creating tree objects for local commit ${commit.sha}`)
|
||||
const treeObjects = await Promise.all(
|
||||
commit.changes.map(async ({path, mode, status}) => {
|
||||
let sha: string | null = null
|
||||
if (status === 'A' || status === 'M') {
|
||||
core.info(`Creating blob for file '${path}'`)
|
||||
const {data: blob} = await blobCreationLimit(() =>
|
||||
this.octokit.rest.git.createBlob({
|
||||
...repository,
|
||||
content: utils.readFileBase64([repoPath, path]),
|
||||
encoding: 'base64'
|
||||
})
|
||||
)
|
||||
sha = blob.sha
|
||||
}
|
||||
return <TreeObject>{
|
||||
path,
|
||||
mode,
|
||||
sha,
|
||||
type: 'blob'
|
||||
}
|
||||
})
|
||||
)
|
||||
core.info(`Creating tree for local commit ${commit.sha}`)
|
||||
const {data: tree} = await this.octokit.rest.git.createTree({
|
||||
...repository,
|
||||
base_tree: parents[0],
|
||||
tree: treeObjects
|
||||
})
|
||||
treeSha = tree.sha
|
||||
core.info(`Created tree ${treeSha} for local commit ${commit.sha}`)
|
||||
}
|
||||
|
||||
const {data: remoteCommit} = await this.octokit.rest.git.createCommit({
|
||||
...repository,
|
||||
parents: parents,
|
||||
tree: treeSha,
|
||||
message: `${commit.subject}\n\n${commit.body}`
|
||||
})
|
||||
core.info(
|
||||
`Created commit ${remoteCommit.sha} for local commit ${commit.sha}`
|
||||
)
|
||||
core.info(
|
||||
`Commit verified: ${remoteCommit.verification.verified}; reason: ${remoteCommit.verification.reason}`
|
||||
)
|
||||
return {
|
||||
sha: remoteCommit.sha,
|
||||
verified: remoteCommit.verification.verified
|
||||
}
|
||||
}
|
||||
|
||||
private async createOrUpdateRef(
|
||||
branchRepository: string,
|
||||
branch: string,
|
||||
newHead: string
|
||||
) {
|
||||
const repository = this.parseRepository(branchRepository)
|
||||
const branchExists = await this.octokit.rest.repos
|
||||
.getBranch({
|
||||
...repository,
|
||||
branch: branch
|
||||
})
|
||||
.then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
|
||||
if (branchExists) {
|
||||
core.info(`Branch ${branch} exists; Updating ref`)
|
||||
await this.octokit.rest.git.updateRef({
|
||||
...repository,
|
||||
sha: newHead,
|
||||
ref: `heads/${branch}`,
|
||||
force: true
|
||||
})
|
||||
} else {
|
||||
core.info(`Branch ${branch} does not exist; Creating ref`)
|
||||
await this.octokit.rest.git.createRef({
|
||||
...repository,
|
||||
sha: newHead,
|
||||
ref: `refs/heads/${branch}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
src/main.ts
26
src/main.ts
@ -7,7 +7,6 @@ async function run(): Promise<void> {
|
||||
try {
|
||||
const inputs: Inputs = {
|
||||
token: core.getInput('token'),
|
||||
gitToken: core.getInput('git-token'),
|
||||
path: core.getInput('path'),
|
||||
addPaths: utils.getInputAsArray('add-paths'),
|
||||
commitMessage: core.getInput('commit-message'),
|
||||
@ -19,7 +18,6 @@ async function run(): Promise<void> {
|
||||
branchSuffix: core.getInput('branch-suffix'),
|
||||
base: core.getInput('base'),
|
||||
pushToFork: core.getInput('push-to-fork'),
|
||||
signCommits: core.getBooleanInput('sign-commits'),
|
||||
title: core.getInput('title'),
|
||||
body: core.getInput('body'),
|
||||
bodyPath: core.getInput('body-path'),
|
||||
@ -28,32 +26,10 @@ async function run(): Promise<void> {
|
||||
reviewers: utils.getInputAsArray('reviewers'),
|
||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||
milestone: Number(core.getInput('milestone')),
|
||||
draft: core.getBooleanInput('draft'),
|
||||
maintainerCanModify: core.getBooleanInput('maintainer-can-modify')
|
||||
draft: core.getBooleanInput('draft')
|
||||
}
|
||||
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)
|
||||
} catch (error) {
|
||||
core.setFailed(utils.getErrorMessage(error))
|
||||
|
@ -1,55 +1,23 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Octokit as OctokitCore} from '@octokit/core'
|
||||
import {Octokit as Core} from '@octokit/core'
|
||||
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
||||
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
||||
import {throttling} from '@octokit/plugin-throttling'
|
||||
import {HttpsProxyAgent} from 'https-proxy-agent'
|
||||
import {getProxyForUrl} from 'proxy-from-env'
|
||||
import {ProxyAgent, fetch as undiciFetch} from 'undici'
|
||||
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||
|
||||
export const Octokit = OctokitCore.plugin(
|
||||
export const Octokit = Core.plugin(
|
||||
paginateRest,
|
||||
restEndpointMethods,
|
||||
throttling,
|
||||
autoProxyAgent
|
||||
)
|
||||
|
||||
export const throttleOptions = {
|
||||
onRateLimit: (retryAfter, options, _, retryCount) => {
|
||||
core.debug(`Hit rate limit for request ${options.method} ${options.url}`)
|
||||
// Retries twice for a total of three attempts
|
||||
if (retryCount < 2) {
|
||||
core.debug(`Retrying after ${retryAfter} seconds!`)
|
||||
return true
|
||||
}
|
||||
},
|
||||
onSecondaryRateLimit: (retryAfter, options) => {
|
||||
core.warning(
|
||||
`Hit secondary rate limit for request ${options.method} ${options.url}`
|
||||
)
|
||||
core.warning(`Requests may be retried after ${retryAfter} seconds.`)
|
||||
}
|
||||
}
|
||||
|
||||
const proxyFetch =
|
||||
(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
|
||||
function autoProxyAgent(octokit: OctokitCore) {
|
||||
function autoProxyAgent(octokit: Core) {
|
||||
octokit.hook.before('request', options => {
|
||||
const proxy = getProxyForUrl(options.baseUrl)
|
||||
if (proxy) {
|
||||
options.request.fetch = proxyFetch(proxy)
|
||||
options.request.agent = new HttpsProxyAgent(proxy)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
51
src/utils.ts
51
src/utils.ts
@ -41,6 +41,53 @@ export function getRepoPath(relativePath?: string): string {
|
||||
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(
|
||||
protocol: string,
|
||||
hostname: string,
|
||||
@ -126,10 +173,6 @@ export function readFile(path: string): string {
|
||||
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 */
|
||||
function hasErrorCode(error: any): error is {code: string} {
|
||||
return typeof (error && error.code) === 'string'
|
||||
|
Reference in New Issue
Block a user