feat: v6 (#2717)
* feat: update author and committer input defaults * Update github-actions[bot] * Update author to new email format * feat: optional input for git ops token * feat: allow push-to-fork to push to sibling repos (#2414) Fixes #2412. * build: update dist * feat: update action runtime to node 20 (#2340) * feat: add truncate warning to pull request body * perf: unshallow only when necessary * fix: remove the remote for the fork on completion * feat: infer github server and api urls * test: integration test fixes * build: bump major version * docs: update to v6 --------- Co-authored-by: Teko <112829523+Teko012@users.noreply.github.com> Co-authored-by: Benjamin Gilbert <bgilbert@backtick.net>
This commit is contained in:
parent
bb809027fd
commit
b1ddad2c99
24 changed files with 620 additions and 491 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.x
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
|
@ -68,8 +68,8 @@ jobs:
|
|||
uses: ./
|
||||
with:
|
||||
commit-message: '[CI] test ${{ matrix.target }}'
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
title: '[CI] test ${{ matrix.target }}'
|
||||
body: |
|
||||
- CI test case for target '${{ matrix.target }}'
|
||||
|
|
4
.github/workflows/cpr-example-command.yml
vendored
4
.github/workflows/cpr-example-command.yml
vendored
|
@ -16,8 +16,8 @@ jobs:
|
|||
uses: ./
|
||||
with:
|
||||
commit-message: Update report
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
title: '[Example] Update report'
|
||||
body: |
|
||||
|
|
2
.github/workflows/update-major-version.yml
vendored
2
.github/workflows/update-major-version.yml
vendored
|
@ -11,8 +11,8 @@ on:
|
|||
type: choice
|
||||
description: The major version tag to update
|
||||
options:
|
||||
- v4
|
||||
- v5
|
||||
- v6
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
|
|
27
README.md
27
README.md
|
@ -21,7 +21,7 @@ Create Pull Request action will:
|
|||
|
||||
- [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md)
|
||||
- [Examples](docs/examples.md)
|
||||
- [Updating to v5](docs/updating.md)
|
||||
- [Updating to v6](docs/updating.md)
|
||||
- [Common issues](docs/common-issues.md)
|
||||
|
||||
## Usage
|
||||
|
@ -32,10 +32,10 @@ Create Pull Request action will:
|
|||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v5.x.x`
|
||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v6.x.x`
|
||||
|
||||
### Workflow permissions
|
||||
|
||||
|
@ -53,11 +53,12 @@ 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. | `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>` |
|
||||
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the GitHub Actions bot user. | `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>` |
|
||||
| `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` |
|
||||
|
@ -99,7 +100,7 @@ If you want branches to be deleted immediately on merge then you should use GitH
|
|||
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
env:
|
||||
https_proxy: http://<proxy_address>:<port>
|
||||
```
|
||||
|
@ -119,7 +120,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
- name: Check outputs
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
run: |
|
||||
|
@ -182,7 +183,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
add-paths: |
|
||||
*.java
|
||||
|
@ -209,7 +210,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
### Create a project card
|
||||
|
@ -219,7 +220,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
|
||||
- name: Create or Update Project Card
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
|
@ -254,12 +255,12 @@ jobs:
|
|||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update report
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: example-patches
|
||||
delete-branch: true
|
||||
|
|
|
@ -8,7 +8,7 @@ import {GitCommandManager} from '../lib/git-command-manager'
|
|||
import * as path from 'path'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
|
||||
const REPO_PATH = '/git/local/test-base'
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
const REMOTE_NAME = 'origin'
|
||||
|
||||
const TRACKED_FILE = 'a/tracked-file.txt'
|
||||
|
@ -22,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/test-fork.git'
|
||||
const FORK_REMOTE_URL = 'git://127.0.0.1/repos/test-fork.git'
|
||||
const FORK_REMOTE_NAME = 'fork'
|
||||
|
||||
const ADD_PATHS_DEFAULT = []
|
||||
|
|
|
@ -5,18 +5,18 @@ set -euo pipefail
|
|||
WORKINGDIR=$PWD
|
||||
|
||||
# Create and serve a remote repo
|
||||
mkdir -p /git/remote
|
||||
mkdir -p /git/remote/repos
|
||||
git config --global init.defaultBranch main
|
||||
git init --bare /git/remote/test-base.git
|
||||
git init --bare /git/remote/repos/test-base.git
|
||||
git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all /git/remote &>/dev/null &
|
||||
|
||||
# Give the daemon time to start
|
||||
sleep 2
|
||||
|
||||
# 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
|
||||
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
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Your Name"
|
||||
echo "#test-base" > README.md
|
||||
|
@ -30,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/test-base.git /git/remote/test-fork.git
|
||||
cd /git/remote/test-fork.git
|
||||
git clone --mirror git://127.0.0.1/repos/test-base.git /git/remote/repos/test-fork.git
|
||||
cd /git/remote/repos/test-fork.git
|
||||
git log -1 --pretty=oneline
|
||||
|
||||
# Restore the working directory
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
import {GitAuthHelper} from '../lib/git-auth-helper'
|
||||
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||
|
||||
const REPO_PATH = '/git/local/test-base'
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
|
||||
const extraheaderConfigKey = 'http.https://github.com/.extraheader'
|
||||
const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||
|
||||
describe('git-auth-helper tests', () => {
|
||||
describe('git-config-helper integration tests', () => {
|
||||
let git: GitCommandManager
|
||||
let gitAuthHelper: GitAuthHelper
|
||||
let gitConfigHelper: GitConfigHelper
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
gitAuthHelper = new GitAuthHelper(git)
|
||||
})
|
||||
|
||||
it('tests save and restore with no persisted auth', async () => {
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await gitConfigHelper.close()
|
||||
})
|
||||
|
||||
it('tests configure and removal of auth', async () => {
|
||||
await gitAuthHelper.configureToken('github-token')
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await gitConfigHelper.configureToken('github-token')
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
||||
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
||||
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
||||
)
|
||||
|
||||
await gitAuthHelper.removeAuth()
|
||||
await gitConfigHelper.close()
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
||||
})
|
||||
|
||||
|
@ -34,37 +34,53 @@ describe('git-auth-helper tests', () => {
|
|||
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
||||
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
||||
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
|
||||
const exists = await git.configExists(extraheaderConfigKey)
|
||||
expect(exists).toBeFalsy()
|
||||
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
await gitConfigHelper.close()
|
||||
|
||||
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
||||
expect(configValue).toEqual(extraheaderConfigValue)
|
||||
|
||||
await gitAuthHelper.removeAuth()
|
||||
const unset = await git.tryConfigUnset(
|
||||
extraheaderConfigKey,
|
||||
'^AUTHORIZATION:'
|
||||
)
|
||||
expect(unset).toBeTruthy()
|
||||
})
|
||||
|
||||
it('tests not adding/removing the safe.directory config when it already exists', async () => {
|
||||
await git.config('safe.directory', '/another-value', true, true)
|
||||
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', '/another-value', true)
|
||||
).toBeTruthy()
|
||||
|
||||
await gitConfigHelper.close()
|
||||
|
||||
const unset = await git.tryConfigUnset(
|
||||
'safe.directory',
|
||||
'/another-value',
|
||||
true
|
||||
)
|
||||
expect(unset).toBeTruthy()
|
||||
})
|
||||
|
||||
it('tests adding and removing the safe.directory config', async () => {
|
||||
await git.config('safe.directory', '/another-value', true, true)
|
||||
|
||||
await gitAuthHelper.removeSafeDirectory()
|
||||
await gitAuthHelper.addSafeDirectory()
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', REPO_PATH, true)
|
||||
).toBeTruthy()
|
||||
|
||||
await gitAuthHelper.addSafeDirectory()
|
||||
await gitAuthHelper.removeSafeDirectory()
|
||||
await gitConfigHelper.close()
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', REPO_PATH, true)
|
||||
).toBeFalsy()
|
||||
expect(
|
||||
await git.configExists('safe.directory', '/another-value', true)
|
||||
).toBeTruthy()
|
||||
})
|
||||
})
|
93
__test__/git-config-helper.unit.test.ts
Normal file
93
__test__/git-config-helper.unit.test.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||
|
||||
describe('git-config-helper unit tests', () => {
|
||||
test('parseGitRemote successfully parses HTTPS remote URLs', async () => {
|
||||
const remote1 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote1.hostname).toEqual('github.com')
|
||||
expect(remote1.protocol).toEqual('HTTPS')
|
||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote2 = GitConfigHelper.parseGitRemote(
|
||||
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote2.hostname).toEqual('github.com')
|
||||
expect(remote2.protocol).toEqual('HTTPS')
|
||||
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote3 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote3.hostname).toEqual('github.com')
|
||||
expect(remote3.protocol).toEqual('HTTPS')
|
||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote4 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/ungit'
|
||||
)
|
||||
expect(remote4.hostname).toEqual('github.com')
|
||||
expect(remote4.protocol).toEqual('HTTPS')
|
||||
expect(remote4.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote5 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/ungit.git'
|
||||
)
|
||||
expect(remote5.hostname).toEqual('github.com')
|
||||
expect(remote5.protocol).toEqual('HTTPS')
|
||||
expect(remote5.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote6 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.internal.company/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote6.hostname).toEqual('github.internal.company')
|
||||
expect(remote6.protocol).toEqual('HTTPS')
|
||||
expect(remote6.repository).toEqual('peter-evans/create-pull-request')
|
||||
})
|
||||
|
||||
test('parseGitRemote successfully parses SSH remote URLs', async () => {
|
||||
const remote1 = GitConfigHelper.parseGitRemote(
|
||||
'git@github.com:peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote1.hostname).toEqual('github.com')
|
||||
expect(remote1.protocol).toEqual('SSH')
|
||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote2 = GitConfigHelper.parseGitRemote(
|
||||
'git@github.com:peter-evans/ungit.git'
|
||||
)
|
||||
expect(remote2.hostname).toEqual('github.com')
|
||||
expect(remote2.protocol).toEqual('SSH')
|
||||
expect(remote2.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote3 = GitConfigHelper.parseGitRemote(
|
||||
'git@github.internal.company:peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote3.hostname).toEqual('github.internal.company')
|
||||
expect(remote3.protocol).toEqual('SSH')
|
||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||
})
|
||||
|
||||
test('parseGitRemote successfully parses GIT remote URLs', async () => {
|
||||
// Unauthenticated git protocol for integration tests only
|
||||
const remote1 = GitConfigHelper.parseGitRemote(
|
||||
'git://127.0.0.1/repos/test-base.git'
|
||||
)
|
||||
expect(remote1.hostname).toEqual('127.0.0.1')
|
||||
expect(remote1.protocol).toEqual('GIT')
|
||||
expect(remote1.repository).toEqual('repos/test-base')
|
||||
})
|
||||
|
||||
test('parseGitRemote fails to parse a remote URL', async () => {
|
||||
const remoteUrl = 'https://github.com/peter-evans'
|
||||
try {
|
||||
GitConfigHelper.parseGitRemote(remoteUrl)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
|
@ -8,7 +8,7 @@ if [[ "$(docker images -q $IMAGE 2> /dev/null)" == "" || $ARG1 == "build" ]]; th
|
|||
echo "Building Docker image $IMAGE ..."
|
||||
|
||||
cat > Dockerfile << EOF
|
||||
FROM node:16-alpine
|
||||
FROM node:20-alpine
|
||||
RUN apk --no-cache add git git-daemon
|
||||
RUN npm install jest jest-environment-jsdom --global
|
||||
WORKDIR /cpr
|
||||
|
|
|
@ -44,63 +44,6 @@ describe('utils tests', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('getRemoteDetail successfully parses remote URLs', async () => {
|
||||
const remote1 = utils.getRemoteDetail(
|
||||
'https://github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote1.protocol).toEqual('HTTPS')
|
||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote2 = utils.getRemoteDetail(
|
||||
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote2.protocol).toEqual('HTTPS')
|
||||
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote3 = utils.getRemoteDetail(
|
||||
'git@github.com:peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote3.protocol).toEqual('SSH')
|
||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote4 = utils.getRemoteDetail(
|
||||
'https://github.com/peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote4.protocol).toEqual('HTTPS')
|
||||
expect(remote4.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote5 = utils.getRemoteDetail(
|
||||
'https://github.com/peter-evans/ungit'
|
||||
)
|
||||
expect(remote5.protocol).toEqual('HTTPS')
|
||||
expect(remote5.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote6 = utils.getRemoteDetail(
|
||||
'https://github.com/peter-evans/ungit.git'
|
||||
)
|
||||
expect(remote6.protocol).toEqual('HTTPS')
|
||||
expect(remote6.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote7 = utils.getRemoteDetail(
|
||||
'git@github.com:peter-evans/ungit.git'
|
||||
)
|
||||
expect(remote7.protocol).toEqual('SSH')
|
||||
expect(remote7.repository).toEqual('peter-evans/ungit')
|
||||
})
|
||||
|
||||
test('getRemoteDetail fails to parse a remote URL', async () => {
|
||||
const remoteUrl = 'https://github.com/peter-evans'
|
||||
try {
|
||||
utils.getRemoteDetail(remoteUrl)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('getRemoteUrl successfully returns remote URLs', async () => {
|
||||
const url1 = utils.getRemoteUrl(
|
||||
'HTTPS',
|
||||
|
|
10
action.yml
10
action.yml
|
@ -4,6 +4,10 @@ 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.
|
||||
|
@ -20,12 +24,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 <noreply@github.com>'
|
||||
default: 'github-actions[bot] <41898282+github-actions[bot]@users.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 }}@users.noreply.github.com>'
|
||||
default: '${{ github.actor }} <${{ github.actor_id }}+${{ 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
|
||||
|
@ -80,7 +84,7 @@ outputs:
|
|||
pull-request-head-sha:
|
||||
description: 'The commit SHA of the pull request branch.'
|
||||
runs:
|
||||
using: 'node16'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'git-pull-request'
|
||||
|
|
499
dist/index.js
vendored
499
dist/index.js
vendored
|
@ -171,9 +171,7 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
|
|||
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)
|
||||
yield git.fetch([`${workingBase}:${workingBase}`], baseRemote, [
|
||||
'--force'
|
||||
]);
|
||||
yield git.fetch([`${workingBase}:${workingBase}`], baseRemote, ['--force'], true);
|
||||
}
|
||||
else {
|
||||
// If the remote is 'origin' we can git reset
|
||||
|
@ -318,43 +316,21 @@ const core = __importStar(__nccwpck_require__(2186));
|
|||
const create_or_update_branch_1 = __nccwpck_require__(8363);
|
||||
const github_helper_1 = __nccwpck_require__(446);
|
||||
const git_command_manager_1 = __nccwpck_require__(738);
|
||||
const git_auth_helper_1 = __nccwpck_require__(2565);
|
||||
const git_config_helper_1 = __nccwpck_require__(8384);
|
||||
const utils = __importStar(__nccwpck_require__(918));
|
||||
function createPullRequest(inputs) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let gitAuthHelper;
|
||||
let gitConfigHelper, git;
|
||||
try {
|
||||
if (!inputs.token) {
|
||||
throw new Error(`Input 'token' not supplied. Unable to continue.`);
|
||||
}
|
||||
if (inputs.bodyPath) {
|
||||
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
||||
throw new Error(`File '${inputs.bodyPath}' does not exist.`);
|
||||
}
|
||||
// Update the body input with the contents of the file
|
||||
inputs.body = utils.readFile(inputs.bodyPath);
|
||||
}
|
||||
// 65536 characters is the maximum allowed for the pull request body.
|
||||
if (inputs.body.length > 65536) {
|
||||
core.warning(`Pull request body is too long. Truncating to 65536 characters.`);
|
||||
inputs.body = inputs.body.substring(0, 65536);
|
||||
}
|
||||
// Get the repository path
|
||||
const repoPath = utils.getRepoPath(inputs.path);
|
||||
// Create a git command manager
|
||||
const git = yield git_command_manager_1.GitCommandManager.create(repoPath);
|
||||
// Save and unset the extraheader auth config if it exists
|
||||
core.startGroup('Prepare git configuration');
|
||||
gitAuthHelper = new git_auth_helper_1.GitAuthHelper(git);
|
||||
yield gitAuthHelper.addSafeDirectory();
|
||||
yield gitAuthHelper.savePersistedAuth();
|
||||
const repoPath = utils.getRepoPath(inputs.path);
|
||||
git = yield git_command_manager_1.GitCommandManager.create(repoPath);
|
||||
gitConfigHelper = yield git_config_helper_1.GitConfigHelper.create(git);
|
||||
core.endGroup();
|
||||
// Init the GitHub client
|
||||
const githubHelper = new github_helper_1.GitHubHelper(inputs.token);
|
||||
core.startGroup('Determining the base and head repositories');
|
||||
// Determine the base repository from git config
|
||||
const remoteUrl = yield git.tryGetRemoteUrl();
|
||||
const baseRemote = utils.getRemoteDetail(remoteUrl);
|
||||
const baseRemote = gitConfigHelper.getGitRemote();
|
||||
// Init the GitHub client
|
||||
const githubHelper = new github_helper_1.GitHubHelper(baseRemote.hostname, inputs.token);
|
||||
// Determine the head repository; the target for the pull request branch
|
||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin';
|
||||
const branchRepository = inputs.pushToFork
|
||||
|
@ -363,9 +339,14 @@ function createPullRequest(inputs) {
|
|||
if (inputs.pushToFork) {
|
||||
// Check if the supplied fork is really a fork of the base
|
||||
core.info(`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`);
|
||||
const parentRepository = yield githubHelper.getRepositoryParent(branchRepository);
|
||||
if (parentRepository != baseRemote.repository) {
|
||||
throw new Error(`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`);
|
||||
const baseParentRepository = yield githubHelper.getRepositoryParent(baseRemote.repository);
|
||||
const branchParentRepository = yield githubHelper.getRepositoryParent(branchRepository);
|
||||
if (branchParentRepository == null) {
|
||||
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.`);
|
||||
}
|
||||
// Add a remote for the fork
|
||||
const remoteUrl = utils.getRemoteUrl(baseRemote.protocol, baseRemote.hostname, branchRepository);
|
||||
|
@ -376,7 +357,7 @@ function createPullRequest(inputs) {
|
|||
// Configure auth
|
||||
if (baseRemote.protocol == 'HTTPS') {
|
||||
core.startGroup('Configuring credential for HTTPS authentication');
|
||||
yield gitAuthHelper.configureToken(inputs.token);
|
||||
yield gitConfigHelper.configureToken(inputs.gitToken);
|
||||
core.endGroup();
|
||||
}
|
||||
core.startGroup('Checking the base repository state');
|
||||
|
@ -503,11 +484,11 @@ function createPullRequest(inputs) {
|
|||
core.setFailed(utils.getErrorMessage(error));
|
||||
}
|
||||
finally {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
core.startGroup('Restore git configuration');
|
||||
yield gitAuthHelper.removeAuth();
|
||||
yield gitAuthHelper.restorePersistedAuth();
|
||||
yield gitAuthHelper.removeSafeDirectory();
|
||||
if (inputs.pushToFork) {
|
||||
yield git.exec(['remote', 'rm', 'fork']);
|
||||
}
|
||||
yield gitConfigHelper.close();
|
||||
core.endGroup();
|
||||
}
|
||||
});
|
||||
|
@ -515,163 +496,6 @@ function createPullRequest(inputs) {
|
|||
exports.createPullRequest = createPullRequest;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 2565:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.GitAuthHelper = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const url_1 = __nccwpck_require__(7310);
|
||||
const utils = __importStar(__nccwpck_require__(918));
|
||||
class GitAuthHelper {
|
||||
constructor(git) {
|
||||
this.gitConfigPath = '';
|
||||
this.safeDirectoryConfigKey = 'safe.directory';
|
||||
this.safeDirectoryAdded = false;
|
||||
this.extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***';
|
||||
this.extraheaderConfigValueRegex = '^AUTHORIZATION:';
|
||||
this.persistedExtraheaderConfigValue = '';
|
||||
this.git = git;
|
||||
this.workingDirectory = this.git.getWorkingDirectory();
|
||||
const serverUrl = this.getServerUrl();
|
||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`;
|
||||
}
|
||||
addSafeDirectory() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const exists = yield this.git.configExists(this.safeDirectoryConfigKey, this.workingDirectory, true);
|
||||
if (!exists) {
|
||||
yield this.git.config(this.safeDirectoryConfigKey, this.workingDirectory, true, true);
|
||||
this.safeDirectoryAdded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
removeSafeDirectory() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.safeDirectoryAdded) {
|
||||
yield this.git.tryConfigUnset(this.safeDirectoryConfigKey, this.workingDirectory, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
savePersistedAuth() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Save and unset persisted extraheader credential in git config if it exists
|
||||
this.persistedExtraheaderConfigValue = yield this.getAndUnset();
|
||||
});
|
||||
}
|
||||
restorePersistedAuth() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.persistedExtraheaderConfigValue) {
|
||||
try {
|
||||
yield this.setExtraheaderConfig(this.persistedExtraheaderConfigValue);
|
||||
core.info('Persisted git credentials restored');
|
||||
}
|
||||
catch (e) {
|
||||
core.warning(utils.getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
configureToken(token) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Encode and configure the basic credential for HTTPS access
|
||||
const basicCredential = Buffer.from(`x-access-token:${token}`, 'utf8').toString('base64');
|
||||
core.setSecret(basicCredential);
|
||||
const extraheaderConfigValue = `AUTHORIZATION: basic ${basicCredential}`;
|
||||
yield this.setExtraheaderConfig(extraheaderConfigValue);
|
||||
});
|
||||
}
|
||||
removeAuth() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.getAndUnset();
|
||||
});
|
||||
}
|
||||
setExtraheaderConfig(extraheaderConfigValue) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Configure a placeholder value. This approach avoids the credential being captured
|
||||
// by process creation audit events, which are commonly logged. For more information,
|
||||
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||
// See https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts#L267-L274
|
||||
yield this.git.config(this.extraheaderConfigKey, this.extraheaderConfigPlaceholderValue);
|
||||
// Replace the placeholder
|
||||
yield this.gitConfigStringReplace(this.extraheaderConfigPlaceholderValue, extraheaderConfigValue);
|
||||
});
|
||||
}
|
||||
getAndUnset() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let configValue = '';
|
||||
// Save and unset persisted extraheader credential in git config if it exists
|
||||
if (yield this.git.configExists(this.extraheaderConfigKey, this.extraheaderConfigValueRegex)) {
|
||||
configValue = yield this.git.getConfigValue(this.extraheaderConfigKey, this.extraheaderConfigValueRegex);
|
||||
if (yield this.git.tryConfigUnset(this.extraheaderConfigKey, this.extraheaderConfigValueRegex)) {
|
||||
core.info(`Unset config key '${this.extraheaderConfigKey}'`);
|
||||
}
|
||||
else {
|
||||
core.warning(`Failed to unset config key '${this.extraheaderConfigKey}'`);
|
||||
}
|
||||
}
|
||||
return configValue;
|
||||
});
|
||||
}
|
||||
gitConfigStringReplace(find, replace) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.gitConfigPath.length === 0) {
|
||||
const gitDir = yield this.git.getGitDirectory();
|
||||
this.gitConfigPath = path.join(this.workingDirectory, gitDir, 'config');
|
||||
}
|
||||
let content = (yield fs.promises.readFile(this.gitConfigPath)).toString();
|
||||
const index = content.indexOf(find);
|
||||
if (index < 0 || index != content.lastIndexOf(find)) {
|
||||
throw new Error(`Unable to replace '${find}' in ${this.gitConfigPath}`);
|
||||
}
|
||||
content = content.replace(find, replace);
|
||||
yield fs.promises.writeFile(this.gitConfigPath, content);
|
||||
});
|
||||
}
|
||||
getServerUrl() {
|
||||
return new url_1.URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
|
||||
}
|
||||
}
|
||||
exports.GitAuthHelper = GitAuthHelper;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 738:
|
||||
|
@ -793,14 +617,15 @@ class GitCommandManager {
|
|||
return output.exitCode === 0;
|
||||
});
|
||||
}
|
||||
fetch(refSpec, remoteName, options) {
|
||||
fetch(refSpec, remoteName, options, unshallow = false) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const args = ['-c', 'protocol.version=2', 'fetch'];
|
||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||
args.push('--no-tags');
|
||||
}
|
||||
args.push('--progress', '--no-recurse-submodules');
|
||||
if (utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))) {
|
||||
if (unshallow &&
|
||||
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))) {
|
||||
args.push('--unshallow');
|
||||
}
|
||||
if (options) {
|
||||
|
@ -1002,6 +827,218 @@ class GitOutput {
|
|||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8384:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.GitConfigHelper = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const url_1 = __nccwpck_require__(7310);
|
||||
const utils = __importStar(__nccwpck_require__(918));
|
||||
class GitConfigHelper {
|
||||
constructor(git) {
|
||||
this.gitConfigPath = '';
|
||||
this.safeDirectoryConfigKey = 'safe.directory';
|
||||
this.safeDirectoryAdded = false;
|
||||
this.remoteUrl = '';
|
||||
this.extraheaderConfigKey = '';
|
||||
this.extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***';
|
||||
this.extraheaderConfigValueRegex = '^AUTHORIZATION:';
|
||||
this.persistedExtraheaderConfigValue = '';
|
||||
this.git = git;
|
||||
this.workingDirectory = this.git.getWorkingDirectory();
|
||||
}
|
||||
static create(git) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const gitConfigHelper = new GitConfigHelper(git);
|
||||
yield gitConfigHelper.addSafeDirectory();
|
||||
yield gitConfigHelper.fetchRemoteDetail();
|
||||
yield gitConfigHelper.savePersistedAuth();
|
||||
return gitConfigHelper;
|
||||
});
|
||||
}
|
||||
close() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
yield this.removeAuth();
|
||||
yield this.restorePersistedAuth();
|
||||
yield this.removeSafeDirectory();
|
||||
});
|
||||
}
|
||||
addSafeDirectory() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const exists = yield this.git.configExists(this.safeDirectoryConfigKey, this.workingDirectory, true);
|
||||
if (!exists) {
|
||||
yield this.git.config(this.safeDirectoryConfigKey, this.workingDirectory, true, true);
|
||||
this.safeDirectoryAdded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
removeSafeDirectory() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.safeDirectoryAdded) {
|
||||
yield this.git.tryConfigUnset(this.safeDirectoryConfigKey, this.workingDirectory, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
fetchRemoteDetail() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.remoteUrl = yield this.git.tryGetRemoteUrl();
|
||||
});
|
||||
}
|
||||
getGitRemote() {
|
||||
return GitConfigHelper.parseGitRemote(this.remoteUrl);
|
||||
}
|
||||
static parseGitRemote(remoteUrl) {
|
||||
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`);
|
||||
}
|
||||
savePersistedAuth() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const serverUrl = new url_1.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 = yield this.getAndUnset();
|
||||
});
|
||||
}
|
||||
restorePersistedAuth() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.persistedExtraheaderConfigValue) {
|
||||
try {
|
||||
yield this.setExtraheaderConfig(this.persistedExtraheaderConfigValue);
|
||||
core.info('Persisted git credentials restored');
|
||||
}
|
||||
catch (e) {
|
||||
core.warning(utils.getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
configureToken(token) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Encode and configure the basic credential for HTTPS access
|
||||
const basicCredential = Buffer.from(`x-access-token:${token}`, 'utf8').toString('base64');
|
||||
core.setSecret(basicCredential);
|
||||
const extraheaderConfigValue = `AUTHORIZATION: basic ${basicCredential}`;
|
||||
yield this.setExtraheaderConfig(extraheaderConfigValue);
|
||||
});
|
||||
}
|
||||
removeAuth() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.getAndUnset();
|
||||
});
|
||||
}
|
||||
setExtraheaderConfig(extraheaderConfigValue) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Configure a placeholder value. This approach avoids the credential being captured
|
||||
// by process creation audit events, which are commonly logged. For more information,
|
||||
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||
// See https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts#L267-L274
|
||||
yield this.git.config(this.extraheaderConfigKey, this.extraheaderConfigPlaceholderValue);
|
||||
// Replace the placeholder
|
||||
yield this.gitConfigStringReplace(this.extraheaderConfigPlaceholderValue, extraheaderConfigValue);
|
||||
});
|
||||
}
|
||||
getAndUnset() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let configValue = '';
|
||||
// Save and unset persisted extraheader credential in git config if it exists
|
||||
if (yield this.git.configExists(this.extraheaderConfigKey, this.extraheaderConfigValueRegex)) {
|
||||
configValue = yield this.git.getConfigValue(this.extraheaderConfigKey, this.extraheaderConfigValueRegex);
|
||||
if (yield this.git.tryConfigUnset(this.extraheaderConfigKey, this.extraheaderConfigValueRegex)) {
|
||||
core.info(`Unset config key '${this.extraheaderConfigKey}'`);
|
||||
}
|
||||
else {
|
||||
core.warning(`Failed to unset config key '${this.extraheaderConfigKey}'`);
|
||||
}
|
||||
}
|
||||
return configValue;
|
||||
});
|
||||
}
|
||||
gitConfigStringReplace(find, replace) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.gitConfigPath.length === 0) {
|
||||
const gitDir = yield this.git.getGitDirectory();
|
||||
this.gitConfigPath = path.join(this.workingDirectory, gitDir, 'config');
|
||||
}
|
||||
let content = (yield fs.promises.readFile(this.gitConfigPath)).toString();
|
||||
const index = content.indexOf(find);
|
||||
if (index < 0 || index != content.lastIndexOf(find)) {
|
||||
throw new Error(`Unable to replace '${find}' in ${this.gitConfigPath}`);
|
||||
}
|
||||
content = content.replace(find, replace);
|
||||
yield fs.promises.writeFile(this.gitConfigPath, content);
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.GitConfigHelper = GitConfigHelper;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 446:
|
||||
|
@ -1048,12 +1085,17 @@ const octokit_client_1 = __nccwpck_require__(5040);
|
|||
const utils = __importStar(__nccwpck_require__(918));
|
||||
const ERROR_PR_REVIEW_TOKEN_SCOPE = 'Validation Failed: "Could not resolve to a node with the global id of';
|
||||
class GitHubHelper {
|
||||
constructor(token) {
|
||||
constructor(githubServerHostname, token) {
|
||||
const options = {};
|
||||
if (token) {
|
||||
options.auth = `${token}`;
|
||||
}
|
||||
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com';
|
||||
if (githubServerHostname !== 'github.com') {
|
||||
options.baseUrl = `https://${githubServerHostname}/api/v3`;
|
||||
}
|
||||
else {
|
||||
options.baseUrl = 'https://api.github.com';
|
||||
}
|
||||
this.octokit = new octokit_client_1.Octokit(options);
|
||||
}
|
||||
parseRepository(repository) {
|
||||
|
@ -1104,7 +1146,7 @@ class GitHubHelper {
|
|||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { data: headRepo } = yield this.octokit.rest.repos.get(Object.assign({}, this.parseRepository(headRepository)));
|
||||
if (!headRepo.parent) {
|
||||
throw new Error(`Repository '${headRepository}' is not a fork. Unable to continue.`);
|
||||
return null;
|
||||
}
|
||||
return headRepo.parent.full_name;
|
||||
});
|
||||
|
@ -1206,6 +1248,7 @@ function run() {
|
|||
try {
|
||||
const inputs = {
|
||||
token: core.getInput('token'),
|
||||
gitToken: core.getInput('git-token'),
|
||||
path: core.getInput('path'),
|
||||
addPaths: utils.getInputAsArray('add-paths'),
|
||||
commitMessage: core.getInput('commit-message'),
|
||||
|
@ -1228,6 +1271,24 @@ function run() {
|
|||
draft: core.getBooleanInput('draft')
|
||||
};
|
||||
core.debug(`Inputs: ${(0, util_1.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);
|
||||
}
|
||||
yield (0, create_pull_request_1.createPullRequest)(inputs);
|
||||
}
|
||||
catch (error) {
|
||||
|
@ -1295,7 +1356,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getErrorMessage = exports.readFile = exports.fileExistsSync = exports.parseDisplayNameEmail = exports.randomString = exports.secondsSinceEpoch = exports.getRemoteUrl = exports.getRemoteDetail = exports.getRepoPath = exports.stripOrgPrefixFromTeams = exports.getStringAsArray = exports.getInputAsArray = void 0;
|
||||
exports.getErrorMessage = exports.readFile = exports.fileExistsSync = exports.parseDisplayNameEmail = exports.randomString = exports.secondsSinceEpoch = exports.getRemoteUrl = exports.getRepoPath = exports.stripOrgPrefixFromTeams = exports.getStringAsArray = exports.getInputAsArray = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
|
@ -1334,36 +1395,6 @@ function getRepoPath(relativePath) {
|
|||
return repoPath;
|
||||
}
|
||||
exports.getRepoPath = getRepoPath;
|
||||
function getRemoteDetail(remoteUrl) {
|
||||
// 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`);
|
||||
}
|
||||
exports.getRemoteDetail = getRemoteDetail;
|
||||
function getRemoteUrl(protocol, hostname, repository) {
|
||||
return protocol == 'HTTPS'
|
||||
? `https://${hostname}/${repository}`
|
||||
|
|
|
@ -88,7 +88,7 @@ In these cases, you *must supply* the `base` input so the action can rebase chan
|
|||
Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request) events will by default check out a merge commit. Set the `base` input as follows to base the new pull request on the current pull request's branch.
|
||||
|
||||
```yml
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
base: ${{ github.head_ref }}
|
||||
```
|
||||
|
@ -96,7 +96,7 @@ Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/refer
|
|||
Workflows triggered by [`release`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#release) events will by default check out a tag. For most use cases, you will need to set the `base` input to the branch name of the tagged commit.
|
||||
|
||||
```yml
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
base: main
|
||||
```
|
||||
|
@ -180,7 +180,7 @@ Checking out a branch from a different repository from where the workflow is exe
|
|||
|
||||
# Make changes to pull request here
|
||||
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
```
|
||||
|
@ -207,7 +207,7 @@ How to use SSH (deploy keys) with create-pull-request action:
|
|||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
### Push pull request branches to a fork
|
||||
|
@ -230,7 +230,7 @@ Note that if you choose to use this method (not give the machine account `write`
|
|||
|
||||
# Make changes to pull request here
|
||||
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.MACHINE_USER_PAT }}
|
||||
push-to-fork: machine-user/fork-of-repository
|
||||
|
@ -275,7 +275,7 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
|||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
```
|
||||
|
@ -316,7 +316,7 @@ The action can use GPG to sign commits with a GPG key that you generate yourself
|
|||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
committer: example <email@example.com>
|
||||
|
@ -346,7 +346,7 @@ jobs:
|
|||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
**Ubuntu container example:**
|
||||
|
@ -369,5 +369,5 @@ jobs:
|
|||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
- [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)
|
||||
|
@ -50,7 +49,7 @@ jobs:
|
|||
run: |
|
||||
git log --format='%aN <%aE>%n%cN <%cE>' | sort -u > AUTHORS
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: update authors
|
||||
title: Update AUTHORS
|
||||
|
@ -82,7 +81,7 @@ jobs:
|
|||
git fetch origin main:main
|
||||
git reset --hard main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
branch: production-promotion
|
||||
```
|
||||
|
@ -117,7 +116,7 @@ jobs:
|
|||
./git-chglog -o CHANGELOG.md
|
||||
rm git-chglog
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: update changelog
|
||||
title: Update Changelog
|
||||
|
@ -154,7 +153,7 @@ jobs:
|
|||
npx -p npm-check-updates ncu -u
|
||||
npm install
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
|
@ -215,7 +214,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
|
@ -250,7 +249,7 @@ jobs:
|
|||
cargo update
|
||||
cargo upgrade --to-lockfile
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
|
@ -308,7 +307,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
|
@ -352,7 +351,7 @@ jobs:
|
|||
git fetch upstream main:upstream-main
|
||||
git reset --hard upstream-main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
branch: upstream-changes
|
||||
|
@ -385,7 +384,7 @@ jobs:
|
|||
--domains quotes.toscrape.com \
|
||||
http://quotes.toscrape.com/
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: update local website copy
|
||||
title: Automated Updates to Local Website Copy
|
||||
|
@ -482,7 +481,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: autopep8 action fixes
|
||||
title: Fixes by autopep8 action
|
||||
|
@ -526,18 +525,6 @@ jobs:
|
|||
...
|
||||
```
|
||||
|
||||
### Bypassing git hooks
|
||||
|
||||
If you have git hooks that prevent the action from working correctly you can remove them before running the action.
|
||||
|
||||
```yml
|
||||
# Remove git hooks
|
||||
- run: rm -rf .git/hooks
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
```
|
||||
|
||||
### Dynamic configuration using variables
|
||||
|
||||
The following examples show how configuration for the action can be dynamically defined in a previous workflow step.
|
||||
|
@ -553,7 +540,7 @@ Note that the step where output variables are defined must have an id.
|
|||
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT
|
||||
echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
title: ${{ steps.vars.outputs.pr_title }}
|
||||
body: ${{ steps.vars.outputs.pr_body }}
|
||||
|
@ -579,7 +566,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@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
body: ${{ steps.template.outputs.result }}
|
||||
```
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
## Updating from `v5` to `v6`
|
||||
|
||||
### Behaviour changes
|
||||
|
||||
- The default values for `author` and `committer` have changed. See "What's new" below for details. If you are overriding the default values you will not be affected by this change.
|
||||
- On completion, the action now removes the temporary git remote configuration it adds when using `push-to-fork`. This should not affect you unless you were using the temporary configuration for some other purpose after the action completes.
|
||||
|
||||
### What's new
|
||||
|
||||
- Updated runtime to Node.js 20
|
||||
- The action now requires a minimum version of [v2.308.0](https://github.com/actions/runner/releases/tag/v2.308.0) for the Actions runner. Update self-hosted runners to v2.308.0 or later to ensure compatibility.
|
||||
- The default value for `author` has been changed to `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>`. The change adds the `${{ github.actor_id }}+` prefix to the email address to align with GitHub's standard format for the author email address.
|
||||
- The default value for `committer` has been changed to `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>`. This is to align with the default GitHub Actions bot user account.
|
||||
- Adds input `git-token`, the [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that the action will use for git operations. This input defaults to the value of `token`. Use this input if you would like the action to use a different token for git operations than the one used for the GitHub API.
|
||||
- `push-to-fork` now supports pushing to sibling repositories in the same network.
|
||||
- Previously, when using `push-to-fork`, the action did not remove temporary git remote configuration it adds during execution. This has been fixed and the configuration is now removed when the action completes.
|
||||
- If the pull request body is truncated due to exceeding the maximum length, the action will now suffix the body with the message "...*[Pull request body truncated]*" to indicate that the body has been truncated.
|
||||
- The action now uses `--unshallow` only when necessary, rather than as a default argument of `git fetch`. This should improve performance, particularly for large git repositories with extensive commit history.
|
||||
- The action can now be executed on one GitHub server and create pull requests on a *different* GitHub server. Server products include GitHub hosted (github.com), GitHub Enterprise Server (GHES), and GitHub Enterprise Cloud (GHEC). For example, the action can be executed on GitHub hosted and create pull requests on a GHES or GHEC instance.
|
||||
|
||||
## Updating from `v4` to `v5`
|
||||
|
||||
### Behaviour changes
|
||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "create-pull-request",
|
||||
"version": "5.0.0",
|
||||
"version": "6.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "create-pull-request",
|
||||
"version": "5.0.0",
|
||||
"version": "6.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "create-pull-request",
|
||||
"version": "5.0.0",
|
||||
"version": "6.0.0",
|
||||
"private": true,
|
||||
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
||||
"main": "lib/main.js",
|
||||
|
|
|
@ -180,9 +180,12 @@ export async function createOrUpdateBranch(
|
|||
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'
|
||||
])
|
||||
await git.fetch(
|
||||
[`${workingBase}:${workingBase}`],
|
||||
baseRemote,
|
||||
['--force'],
|
||||
true
|
||||
)
|
||||
} else {
|
||||
// If the remote is 'origin' we can git reset
|
||||
await git.checkout(workingBase)
|
||||
|
|
|
@ -6,11 +6,12 @@ import {
|
|||
} from './create-or-update-branch'
|
||||
import {GitHubHelper} from './github-helper'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {GitAuthHelper} from './git-auth-helper'
|
||||
import {GitConfigHelper} from './git-config-helper'
|
||||
import * as utils from './utils'
|
||||
|
||||
export interface Inputs {
|
||||
token: string
|
||||
gitToken: string
|
||||
path: string
|
||||
addPaths: string[]
|
||||
commitMessage: string
|
||||
|
@ -34,45 +35,18 @@ export interface Inputs {
|
|||
}
|
||||
|
||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
let gitAuthHelper
|
||||
let gitConfigHelper, git
|
||||
try {
|
||||
if (!inputs.token) {
|
||||
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
||||
}
|
||||
if (inputs.bodyPath) {
|
||||
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
||||
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
|
||||
}
|
||||
// Update the body input with the contents of the file
|
||||
inputs.body = utils.readFile(inputs.bodyPath)
|
||||
}
|
||||
// 65536 characters is the maximum allowed for the pull request body.
|
||||
if (inputs.body.length > 65536) {
|
||||
core.warning(
|
||||
`Pull request body is too long. Truncating to 65536 characters.`
|
||||
)
|
||||
inputs.body = inputs.body.substring(0, 65536)
|
||||
}
|
||||
|
||||
// Get the repository path
|
||||
const repoPath = utils.getRepoPath(inputs.path)
|
||||
// Create a git command manager
|
||||
const git = await GitCommandManager.create(repoPath)
|
||||
|
||||
// Save and unset the extraheader auth config if it exists
|
||||
core.startGroup('Prepare git configuration')
|
||||
gitAuthHelper = new GitAuthHelper(git)
|
||||
await gitAuthHelper.addSafeDirectory()
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
const repoPath = utils.getRepoPath(inputs.path)
|
||||
git = await GitCommandManager.create(repoPath)
|
||||
gitConfigHelper = await GitConfigHelper.create(git)
|
||||
core.endGroup()
|
||||
|
||||
// Init the GitHub client
|
||||
const githubHelper = new GitHubHelper(inputs.token)
|
||||
|
||||
core.startGroup('Determining the base and head repositories')
|
||||
// Determine the base repository from git config
|
||||
const remoteUrl = await git.tryGetRemoteUrl()
|
||||
const baseRemote = utils.getRemoteDetail(remoteUrl)
|
||||
const baseRemote = gitConfigHelper.getGitRemote()
|
||||
// Init the GitHub client
|
||||
const githubHelper = new GitHubHelper(baseRemote.hostname, inputs.token)
|
||||
// Determine the head repository; the target for the pull request branch
|
||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
||||
const branchRepository = inputs.pushToFork
|
||||
|
@ -83,11 +57,22 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||
core.info(
|
||||
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
||||
)
|
||||
const parentRepository =
|
||||
const baseParentRepository = await githubHelper.getRepositoryParent(
|
||||
baseRemote.repository
|
||||
)
|
||||
const branchParentRepository =
|
||||
await githubHelper.getRepositoryParent(branchRepository)
|
||||
if (parentRepository != baseRemote.repository) {
|
||||
if (branchParentRepository == null) {
|
||||
throw new Error(
|
||||
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`
|
||||
`Repository '${branchRepository}' is not a fork. Unable to continue.`
|
||||
)
|
||||
}
|
||||
if (
|
||||
branchParentRepository != baseRemote.repository &&
|
||||
baseParentRepository != branchParentRepository
|
||||
) {
|
||||
throw new Error(
|
||||
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}', nor are they siblings. Unable to continue.`
|
||||
)
|
||||
}
|
||||
// Add a remote for the fork
|
||||
|
@ -106,7 +91,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||
// Configure auth
|
||||
if (baseRemote.protocol == 'HTTPS') {
|
||||
core.startGroup('Configuring credential for HTTPS authentication')
|
||||
await gitAuthHelper.configureToken(inputs.token)
|
||||
await gitConfigHelper.configureToken(inputs.gitToken)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
|
@ -266,11 +251,11 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||
} catch (error) {
|
||||
core.setFailed(utils.getErrorMessage(error))
|
||||
} finally {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
core.startGroup('Restore git configuration')
|
||||
await gitAuthHelper.removeAuth()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
await gitAuthHelper.removeSafeDirectory()
|
||||
if (inputs.pushToFork) {
|
||||
await git.exec(['remote', 'rm', 'fork'])
|
||||
}
|
||||
await gitConfigHelper.close()
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,8 @@ export class GitCommandManager {
|
|||
async fetch(
|
||||
refSpec: string[],
|
||||
remoteName?: string,
|
||||
options?: string[]
|
||||
options?: string[],
|
||||
unshallow = false
|
||||
): Promise<void> {
|
||||
const args = ['-c', 'protocol.version=2', 'fetch']
|
||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||
|
@ -113,7 +114,9 @@ export class GitCommandManager {
|
|||
}
|
||||
|
||||
args.push('--progress', '--no-recurse-submodules')
|
||||
|
||||
if (
|
||||
unshallow &&
|
||||
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))
|
||||
) {
|
||||
args.push('--unshallow')
|
||||
|
|
|
@ -5,22 +5,42 @@ import * as path from 'path'
|
|||
import {URL} from 'url'
|
||||
import * as utils from './utils'
|
||||
|
||||
export class GitAuthHelper {
|
||||
interface GitRemote {
|
||||
hostname: string
|
||||
protocol: string
|
||||
repository: string
|
||||
}
|
||||
|
||||
export class GitConfigHelper {
|
||||
private git: GitCommandManager
|
||||
private gitConfigPath = ''
|
||||
private workingDirectory: string
|
||||
private safeDirectoryConfigKey = 'safe.directory'
|
||||
private safeDirectoryAdded = false
|
||||
private extraheaderConfigKey: string
|
||||
private remoteUrl = ''
|
||||
private extraheaderConfigKey = ''
|
||||
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
||||
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
||||
private persistedExtraheaderConfigValue = ''
|
||||
|
||||
constructor(git: GitCommandManager) {
|
||||
private constructor(git: GitCommandManager) {
|
||||
this.git = git
|
||||
this.workingDirectory = this.git.getWorkingDirectory()
|
||||
const serverUrl = this.getServerUrl()
|
||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
||||
}
|
||||
|
||||
static async create(git: GitCommandManager): Promise<GitConfigHelper> {
|
||||
const gitConfigHelper = new GitConfigHelper(git)
|
||||
await gitConfigHelper.addSafeDirectory()
|
||||
await gitConfigHelper.fetchRemoteDetail()
|
||||
await gitConfigHelper.savePersistedAuth()
|
||||
return gitConfigHelper
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
await this.removeAuth()
|
||||
await this.restorePersistedAuth()
|
||||
await this.removeSafeDirectory()
|
||||
}
|
||||
|
||||
async addSafeDirectory(): Promise<void> {
|
||||
|
@ -50,7 +70,57 @@ export class GitAuthHelper {
|
|||
}
|
||||
}
|
||||
|
||||
async fetchRemoteDetail(): Promise<void> {
|
||||
this.remoteUrl = await this.git.tryGetRemoteUrl()
|
||||
}
|
||||
|
||||
getGitRemote(): GitRemote {
|
||||
return GitConfigHelper.parseGitRemote(this.remoteUrl)
|
||||
}
|
||||
|
||||
static parseGitRemote(remoteUrl: string): GitRemote {
|
||||
const httpsUrlPattern = new RegExp(
|
||||
'^(https?)://(?:.+@)?(.+?)/(.+/.+?)(\\.git)?$',
|
||||
'i'
|
||||
)
|
||||
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
||||
if (httpsMatch) {
|
||||
return {
|
||||
hostname: httpsMatch[2],
|
||||
protocol: 'HTTPS',
|
||||
repository: httpsMatch[3]
|
||||
}
|
||||
}
|
||||
|
||||
const sshUrlPattern = new RegExp('^git@(.+?):(.+/.+)\\.git$', 'i')
|
||||
const sshMatch = remoteUrl.match(sshUrlPattern)
|
||||
if (sshMatch) {
|
||||
return {
|
||||
hostname: sshMatch[1],
|
||||
protocol: 'SSH',
|
||||
repository: sshMatch[2]
|
||||
}
|
||||
}
|
||||
|
||||
// Unauthenticated git protocol for integration tests only
|
||||
const gitUrlPattern = new RegExp('^git://(.+?)/(.+/.+)\\.git$', 'i')
|
||||
const gitMatch = remoteUrl.match(gitUrlPattern)
|
||||
if (gitMatch) {
|
||||
return {
|
||||
hostname: gitMatch[1],
|
||||
protocol: 'GIT',
|
||||
repository: gitMatch[2]
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
}
|
||||
|
||||
async savePersistedAuth(): Promise<void> {
|
||||
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()
|
||||
}
|
||||
|
@ -144,8 +214,4 @@ export class GitAuthHelper {
|
|||
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')
|
||||
}
|
||||
}
|
|
@ -20,12 +20,16 @@ interface Pull {
|
|||
export class GitHubHelper {
|
||||
private octokit: InstanceType<typeof Octokit>
|
||||
|
||||
constructor(token: string) {
|
||||
constructor(githubServerHostname: string, token: string) {
|
||||
const options: OctokitOptions = {}
|
||||
if (token) {
|
||||
options.auth = `${token}`
|
||||
}
|
||||
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com'
|
||||
if (githubServerHostname !== 'github.com') {
|
||||
options.baseUrl = `https://${githubServerHostname}/api/v3`
|
||||
} else {
|
||||
options.baseUrl = 'https://api.github.com'
|
||||
}
|
||||
this.octokit = new Octokit(options)
|
||||
}
|
||||
|
||||
|
@ -101,14 +105,12 @@ export class GitHubHelper {
|
|||
}
|
||||
}
|
||||
|
||||
async getRepositoryParent(headRepository: string): Promise<string> {
|
||||
async getRepositoryParent(headRepository: string): Promise<string | null> {
|
||||
const {data: headRepo} = await this.octokit.rest.repos.get({
|
||||
...this.parseRepository(headRepository)
|
||||
})
|
||||
if (!headRepo.parent) {
|
||||
throw new Error(
|
||||
`Repository '${headRepository}' is not a fork. Unable to continue.`
|
||||
)
|
||||
return null
|
||||
}
|
||||
return headRepo.parent.full_name
|
||||
}
|
||||
|
|
22
src/main.ts
22
src/main.ts
|
@ -7,6 +7,7 @@ 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'),
|
||||
|
@ -30,6 +31,27 @@ async function run(): Promise<void> {
|
|||
}
|
||||
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))
|
||||
|
|
47
src/utils.ts
47
src/utils.ts
|
@ -41,53 +41,6 @@ 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,
|
||||
|
|
Loading…
Reference in a new issue