mirror of
https://forgejo.stefka.eu/jiriks74/create-pull-request.git
synced 2025-01-18 16:01:06 +01:00
Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
098a6d3703 | ||
![]() |
1e6bff9d94 | ||
![]() |
02a8f71e34 | ||
![]() |
873341b21c | ||
![]() |
9606fe7fd0 | ||
![]() |
21d8ea09d5 | ||
![]() |
85246161a0 | ||
![]() |
bc1906950d | ||
![]() |
be547fcbbb | ||
![]() |
10a399b4b0 |
18 changed files with 566 additions and 444 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 16.x
|
node-version: 20.x
|
||||||
cache: npm
|
cache: npm
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
@ -68,8 +68,8 @@ jobs:
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
commit-message: '[CI] test ${{ matrix.target }}'
|
commit-message: '[CI] test ${{ matrix.target }}'
|
||||||
committer: GitHub <noreply@github.com>
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||||
title: '[CI] test ${{ matrix.target }}'
|
title: '[CI] test ${{ matrix.target }}'
|
||||||
body: |
|
body: |
|
||||||
- CI test case for target '${{ matrix.target }}'
|
- CI test case for target '${{ matrix.target }}'
|
||||||
|
|
4
.github/workflows/cpr-example-command.yml
vendored
4
.github/workflows/cpr-example-command.yml
vendored
|
@ -16,8 +16,8 @@ jobs:
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
commit-message: Update report
|
commit-message: Update report
|
||||||
committer: GitHub <noreply@github.com>
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||||
signoff: false
|
signoff: false
|
||||||
title: '[Example] Update report'
|
title: '[Example] Update report'
|
||||||
body: |
|
body: |
|
||||||
|
|
|
@ -53,11 +53,12 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
||||||
| Name | Description | Default |
|
| Name | Description | Default |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `token` | `GITHUB_TOKEN` (permissions `contents: write` and `pull-requests: write`) or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
|
| `token` | `GITHUB_TOKEN` (permissions `contents: write` and `pull-requests: write`) or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
|
||||||
|
| `git-token` | The [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that the action will use for git operations. | Defaults to the value of `token` |
|
||||||
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
|
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
|
||||||
| `add-paths` | A comma or newline-separated list of file paths to commit. Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax. If no paths are specified, all new and modified files are added. See [Add specific paths](#add-specific-paths). | |
|
| `add-paths` | A comma or newline-separated list of file paths to commit. Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax. If no paths are specified, all new and modified files are added. See [Add specific paths](#add-specific-paths). | |
|
||||||
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[create-pull-request] automated change` |
|
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[create-pull-request] automated change` |
|
||||||
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the GitHub Actions bot user. | `GitHub <noreply@github.com>` |
|
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the GitHub Actions bot user. | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` |
|
||||||
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>` |
|
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>` |
|
||||||
| `signoff` | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line by the committer at the end of the commit log message. | `false` |
|
| `signoff` | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line by the committer at the end of the commit log message. | `false` |
|
||||||
| `branch` | The pull request branch name. | `create-pull-request/patch` |
|
| `branch` | The pull request branch name. | `create-pull-request/patch` |
|
||||||
| `delete-branch` | Delete the `branch` if it doesn't have an active pull request associated with it. See [delete-branch](#delete-branch). | `false` |
|
| `delete-branch` | Delete the `branch` if it doesn't have an active pull request associated with it. See [delete-branch](#delete-branch). | `false` |
|
||||||
|
@ -258,8 +259,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
commit-message: Update report
|
commit-message: Update report
|
||||||
committer: GitHub <noreply@github.com>
|
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||||
signoff: false
|
signoff: false
|
||||||
branch: example-patches
|
branch: example-patches
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {GitCommandManager} from '../lib/git-command-manager'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import {v4 as uuidv4} from 'uuid'
|
import {v4 as uuidv4} from 'uuid'
|
||||||
|
|
||||||
const REPO_PATH = '/git/local/test-base'
|
const REPO_PATH = '/git/local/repos/test-base'
|
||||||
const REMOTE_NAME = 'origin'
|
const REMOTE_NAME = 'origin'
|
||||||
|
|
||||||
const TRACKED_FILE = 'a/tracked-file.txt'
|
const TRACKED_FILE = 'a/tracked-file.txt'
|
||||||
|
@ -22,7 +22,7 @@ const INIT_COMMIT_MESSAGE = 'Add file to be a tracked file for tests'
|
||||||
const BRANCH = 'tests/create-pull-request/patch'
|
const BRANCH = 'tests/create-pull-request/patch'
|
||||||
const BASE = DEFAULT_BRANCH
|
const BASE = DEFAULT_BRANCH
|
||||||
|
|
||||||
const FORK_REMOTE_URL = 'git://127.0.0.1/test-fork.git'
|
const FORK_REMOTE_URL = 'git://127.0.0.1/repos/test-fork.git'
|
||||||
const FORK_REMOTE_NAME = 'fork'
|
const FORK_REMOTE_NAME = 'fork'
|
||||||
|
|
||||||
const ADD_PATHS_DEFAULT = []
|
const ADD_PATHS_DEFAULT = []
|
||||||
|
|
|
@ -5,18 +5,18 @@ set -euo pipefail
|
||||||
WORKINGDIR=$PWD
|
WORKINGDIR=$PWD
|
||||||
|
|
||||||
# Create and serve a remote repo
|
# Create and serve a remote repo
|
||||||
mkdir -p /git/remote
|
mkdir -p /git/remote/repos
|
||||||
git config --global init.defaultBranch main
|
git config --global init.defaultBranch main
|
||||||
git init --bare /git/remote/test-base.git
|
git init --bare /git/remote/repos/test-base.git
|
||||||
git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all /git/remote &>/dev/null &
|
git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all /git/remote &>/dev/null &
|
||||||
|
|
||||||
# Give the daemon time to start
|
# Give the daemon time to start
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# Create a local clone and make an initial commit
|
# Create a local clone and make an initial commit
|
||||||
mkdir -p /git/local
|
mkdir -p /git/local/repos
|
||||||
git clone git://127.0.0.1/test-base.git /git/local/test-base
|
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
||||||
cd /git/local/test-base
|
cd /git/local/repos/test-base
|
||||||
git config --global user.email "you@example.com"
|
git config --global user.email "you@example.com"
|
||||||
git config --global user.name "Your Name"
|
git config --global user.name "Your Name"
|
||||||
echo "#test-base" > README.md
|
echo "#test-base" > README.md
|
||||||
|
@ -30,8 +30,8 @@ git config -l
|
||||||
|
|
||||||
# Clone a server-side fork of the base repo
|
# Clone a server-side fork of the base repo
|
||||||
cd $WORKINGDIR
|
cd $WORKINGDIR
|
||||||
git clone --mirror git://127.0.0.1/test-base.git /git/remote/test-fork.git
|
git clone --mirror git://127.0.0.1/repos/test-base.git /git/remote/repos/test-fork.git
|
||||||
cd /git/remote/test-fork.git
|
cd /git/remote/repos/test-fork.git
|
||||||
git log -1 --pretty=oneline
|
git log -1 --pretty=oneline
|
||||||
|
|
||||||
# Restore the working directory
|
# Restore the working directory
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
import {GitCommandManager} from '../lib/git-command-manager'
|
import {GitCommandManager} from '../lib/git-command-manager'
|
||||||
import {GitAuthHelper} from '../lib/git-auth-helper'
|
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||||
|
|
||||||
const REPO_PATH = '/git/local/test-base'
|
const REPO_PATH = '/git/local/repos/test-base'
|
||||||
|
|
||||||
const extraheaderConfigKey = 'http.https://github.com/.extraheader'
|
const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||||
|
|
||||||
describe('git-auth-helper tests', () => {
|
describe('git-config-helper integration tests', () => {
|
||||||
let git: GitCommandManager
|
let git: GitCommandManager
|
||||||
let gitAuthHelper: GitAuthHelper
|
let gitConfigHelper: GitConfigHelper
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
git = await GitCommandManager.create(REPO_PATH)
|
git = await GitCommandManager.create(REPO_PATH)
|
||||||
gitAuthHelper = new GitAuthHelper(git)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests save and restore with no persisted auth', async () => {
|
it('tests save and restore with no persisted auth', async () => {
|
||||||
await gitAuthHelper.savePersistedAuth()
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
await gitAuthHelper.restorePersistedAuth()
|
await gitConfigHelper.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests configure and removal of auth', async () => {
|
it('tests configure and removal of auth', async () => {
|
||||||
await gitAuthHelper.configureToken('github-token')
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
await gitConfigHelper.configureToken('github-token')
|
||||||
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
||||||
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
||||||
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
||||||
)
|
)
|
||||||
|
|
||||||
await gitAuthHelper.removeAuth()
|
await gitConfigHelper.close()
|
||||||
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -34,37 +34,53 @@ describe('git-auth-helper tests', () => {
|
||||||
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
||||||
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
||||||
|
|
||||||
await gitAuthHelper.savePersistedAuth()
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
|
||||||
const exists = await git.configExists(extraheaderConfigKey)
|
const exists = await git.configExists(extraheaderConfigKey)
|
||||||
expect(exists).toBeFalsy()
|
expect(exists).toBeFalsy()
|
||||||
|
|
||||||
await gitAuthHelper.restorePersistedAuth()
|
await gitConfigHelper.close()
|
||||||
|
|
||||||
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
||||||
expect(configValue).toEqual(extraheaderConfigValue)
|
expect(configValue).toEqual(extraheaderConfigValue)
|
||||||
|
|
||||||
await gitAuthHelper.removeAuth()
|
const unset = await git.tryConfigUnset(
|
||||||
|
extraheaderConfigKey,
|
||||||
|
'^AUTHORIZATION:'
|
||||||
|
)
|
||||||
|
expect(unset).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('tests not adding/removing the safe.directory config when it already exists', async () => {
|
||||||
|
await git.config('safe.directory', '/another-value', true, true)
|
||||||
|
|
||||||
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await git.configExists('safe.directory', '/another-value', true)
|
||||||
|
).toBeTruthy()
|
||||||
|
|
||||||
|
await gitConfigHelper.close()
|
||||||
|
|
||||||
|
const unset = await git.tryConfigUnset(
|
||||||
|
'safe.directory',
|
||||||
|
'/another-value',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
expect(unset).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests adding and removing the safe.directory config', async () => {
|
it('tests adding and removing the safe.directory config', async () => {
|
||||||
await git.config('safe.directory', '/another-value', true, true)
|
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
|
|
||||||
await gitAuthHelper.removeSafeDirectory()
|
|
||||||
await gitAuthHelper.addSafeDirectory()
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await git.configExists('safe.directory', REPO_PATH, true)
|
await git.configExists('safe.directory', REPO_PATH, true)
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
|
|
||||||
await gitAuthHelper.addSafeDirectory()
|
await gitConfigHelper.close()
|
||||||
await gitAuthHelper.removeSafeDirectory()
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await git.configExists('safe.directory', REPO_PATH, true)
|
await git.configExists('safe.directory', REPO_PATH, true)
|
||||||
).toBeFalsy()
|
).toBeFalsy()
|
||||||
expect(
|
|
||||||
await git.configExists('safe.directory', '/another-value', true)
|
|
||||||
).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
93
__test__/git-config-helper.unit.test.ts
Normal file
93
__test__/git-config-helper.unit.test.ts
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||||
|
|
||||||
|
describe('git-config-helper unit tests', () => {
|
||||||
|
test('parseGitRemote successfully parses HTTPS remote URLs', async () => {
|
||||||
|
const remote1 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/create-pull-request'
|
||||||
|
)
|
||||||
|
expect(remote1.hostname).toEqual('github.com')
|
||||||
|
expect(remote1.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote2 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
||||||
|
)
|
||||||
|
expect(remote2.hostname).toEqual('github.com')
|
||||||
|
expect(remote2.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote3 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/create-pull-request.git'
|
||||||
|
)
|
||||||
|
expect(remote3.hostname).toEqual('github.com')
|
||||||
|
expect(remote3.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote4 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/ungit'
|
||||||
|
)
|
||||||
|
expect(remote4.hostname).toEqual('github.com')
|
||||||
|
expect(remote4.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote4.repository).toEqual('peter-evans/ungit')
|
||||||
|
|
||||||
|
const remote5 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.com/peter-evans/ungit.git'
|
||||||
|
)
|
||||||
|
expect(remote5.hostname).toEqual('github.com')
|
||||||
|
expect(remote5.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote5.repository).toEqual('peter-evans/ungit')
|
||||||
|
|
||||||
|
const remote6 = GitConfigHelper.parseGitRemote(
|
||||||
|
'https://github.internal.company/peter-evans/create-pull-request'
|
||||||
|
)
|
||||||
|
expect(remote6.hostname).toEqual('github.internal.company')
|
||||||
|
expect(remote6.protocol).toEqual('HTTPS')
|
||||||
|
expect(remote6.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parseGitRemote successfully parses SSH remote URLs', async () => {
|
||||||
|
const remote1 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git@github.com:peter-evans/create-pull-request.git'
|
||||||
|
)
|
||||||
|
expect(remote1.hostname).toEqual('github.com')
|
||||||
|
expect(remote1.protocol).toEqual('SSH')
|
||||||
|
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
|
||||||
|
const remote2 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git@github.com:peter-evans/ungit.git'
|
||||||
|
)
|
||||||
|
expect(remote2.hostname).toEqual('github.com')
|
||||||
|
expect(remote2.protocol).toEqual('SSH')
|
||||||
|
expect(remote2.repository).toEqual('peter-evans/ungit')
|
||||||
|
|
||||||
|
const remote3 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git@github.internal.company:peter-evans/create-pull-request.git'
|
||||||
|
)
|
||||||
|
expect(remote3.hostname).toEqual('github.internal.company')
|
||||||
|
expect(remote3.protocol).toEqual('SSH')
|
||||||
|
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parseGitRemote successfully parses GIT remote URLs', async () => {
|
||||||
|
// Unauthenticated git protocol for integration tests only
|
||||||
|
const remote1 = GitConfigHelper.parseGitRemote(
|
||||||
|
'git://127.0.0.1/repos/test-base.git'
|
||||||
|
)
|
||||||
|
expect(remote1.hostname).toEqual('127.0.0.1')
|
||||||
|
expect(remote1.protocol).toEqual('GIT')
|
||||||
|
expect(remote1.repository).toEqual('repos/test-base')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parseGitRemote fails to parse a remote URL', async () => {
|
||||||
|
const remoteUrl = 'https://github.com/peter-evans'
|
||||||
|
try {
|
||||||
|
GitConfigHelper.parseGitRemote(remoteUrl)
|
||||||
|
// Fail the test if an error wasn't thrown
|
||||||
|
expect(true).toEqual(false)
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).toEqual(
|
||||||
|
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -8,7 +8,7 @@ if [[ "$(docker images -q $IMAGE 2> /dev/null)" == "" || $ARG1 == "build" ]]; th
|
||||||
echo "Building Docker image $IMAGE ..."
|
echo "Building Docker image $IMAGE ..."
|
||||||
|
|
||||||
cat > Dockerfile << EOF
|
cat > Dockerfile << EOF
|
||||||
FROM node:16-alpine
|
FROM node:20-alpine
|
||||||
RUN apk --no-cache add git git-daemon
|
RUN apk --no-cache add git git-daemon
|
||||||
RUN npm install jest jest-environment-jsdom --global
|
RUN npm install jest jest-environment-jsdom --global
|
||||||
WORKDIR /cpr
|
WORKDIR /cpr
|
||||||
|
|
|
@ -44,63 +44,6 @@ describe('utils tests', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('getRemoteDetail successfully parses remote URLs', async () => {
|
|
||||||
const remote1 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/create-pull-request'
|
|
||||||
)
|
|
||||||
expect(remote1.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote2 = utils.getRemoteDetail(
|
|
||||||
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
|
||||||
)
|
|
||||||
expect(remote2.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote3 = utils.getRemoteDetail(
|
|
||||||
'git@github.com:peter-evans/create-pull-request.git'
|
|
||||||
)
|
|
||||||
expect(remote3.protocol).toEqual('SSH')
|
|
||||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote4 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/create-pull-request.git'
|
|
||||||
)
|
|
||||||
expect(remote4.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote4.repository).toEqual('peter-evans/create-pull-request')
|
|
||||||
|
|
||||||
const remote5 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/ungit'
|
|
||||||
)
|
|
||||||
expect(remote5.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote5.repository).toEqual('peter-evans/ungit')
|
|
||||||
|
|
||||||
const remote6 = utils.getRemoteDetail(
|
|
||||||
'https://github.com/peter-evans/ungit.git'
|
|
||||||
)
|
|
||||||
expect(remote6.protocol).toEqual('HTTPS')
|
|
||||||
expect(remote6.repository).toEqual('peter-evans/ungit')
|
|
||||||
|
|
||||||
const remote7 = utils.getRemoteDetail(
|
|
||||||
'git@github.com:peter-evans/ungit.git'
|
|
||||||
)
|
|
||||||
expect(remote7.protocol).toEqual('SSH')
|
|
||||||
expect(remote7.repository).toEqual('peter-evans/ungit')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('getRemoteDetail fails to parse a remote URL', async () => {
|
|
||||||
const remoteUrl = 'https://github.com/peter-evans'
|
|
||||||
try {
|
|
||||||
utils.getRemoteDetail(remoteUrl)
|
|
||||||
// Fail the test if an error wasn't thrown
|
|
||||||
expect(true).toEqual(false)
|
|
||||||
} catch (e: any) {
|
|
||||||
expect(e.message).toEqual(
|
|
||||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test('getRemoteUrl successfully returns remote URLs', async () => {
|
test('getRemoteUrl successfully returns remote URLs', async () => {
|
||||||
const url1 = utils.getRemoteUrl(
|
const url1 = utils.getRemoteUrl(
|
||||||
'HTTPS',
|
'HTTPS',
|
||||||
|
|
10
action.yml
10
action.yml
|
@ -4,6 +4,10 @@ inputs:
|
||||||
token:
|
token:
|
||||||
description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
|
description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
|
git-token:
|
||||||
|
description: >
|
||||||
|
The Personal Access Token (PAT) that the action will use for git operations.
|
||||||
|
Defaults to the value of `token`.
|
||||||
path:
|
path:
|
||||||
description: >
|
description: >
|
||||||
Relative path under $GITHUB_WORKSPACE to the repository.
|
Relative path under $GITHUB_WORKSPACE to the repository.
|
||||||
|
@ -20,12 +24,12 @@ inputs:
|
||||||
description: >
|
description: >
|
||||||
The committer name and email address in the format `Display Name <email@address.com>`.
|
The committer name and email address in the format `Display Name <email@address.com>`.
|
||||||
Defaults to the GitHub Actions bot user.
|
Defaults to the GitHub Actions bot user.
|
||||||
default: 'GitHub <noreply@github.com>'
|
default: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
|
||||||
author:
|
author:
|
||||||
description: >
|
description: >
|
||||||
The author name and email address in the format `Display Name <email@address.com>`.
|
The author name and email address in the format `Display Name <email@address.com>`.
|
||||||
Defaults to the user who triggered the workflow run.
|
Defaults to the user who triggered the workflow run.
|
||||||
default: '${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>'
|
default: '${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>'
|
||||||
signoff:
|
signoff:
|
||||||
description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.'
|
description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.'
|
||||||
default: false
|
default: false
|
||||||
|
@ -80,7 +84,7 @@ outputs:
|
||||||
pull-request-head-sha:
|
pull-request-head-sha:
|
||||||
description: 'The commit SHA of the pull request branch.'
|
description: 'The commit SHA of the pull request branch.'
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node20'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
branding:
|
branding:
|
||||||
icon: 'git-pull-request'
|
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 (branchRemoteName == 'fork') {
|
||||||
// If pushing to a fork we must fetch with 'unshallow' to avoid the following error on git push
|
// 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)
|
// ! [remote rejected] HEAD -> tests/push-branch-to-fork (shallow update not allowed)
|
||||||
yield git.fetch([`${workingBase}:${workingBase}`], baseRemote, [
|
yield git.fetch([`${workingBase}:${workingBase}`], baseRemote, ['--force'], true);
|
||||||
'--force'
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// If the remote is 'origin' we can git reset
|
// 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 create_or_update_branch_1 = __nccwpck_require__(8363);
|
||||||
const github_helper_1 = __nccwpck_require__(446);
|
const github_helper_1 = __nccwpck_require__(446);
|
||||||
const git_command_manager_1 = __nccwpck_require__(738);
|
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));
|
const utils = __importStar(__nccwpck_require__(918));
|
||||||
function createPullRequest(inputs) {
|
function createPullRequest(inputs) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
let gitAuthHelper;
|
let gitConfigHelper, git;
|
||||||
try {
|
try {
|
||||||
if (!inputs.token) {
|
|
||||||
throw new Error(`Input 'token' not supplied. Unable to continue.`);
|
|
||||||
}
|
|
||||||
if (inputs.bodyPath) {
|
|
||||||
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
|
||||||
throw new Error(`File '${inputs.bodyPath}' does not exist.`);
|
|
||||||
}
|
|
||||||
// Update the body input with the contents of the file
|
|
||||||
inputs.body = utils.readFile(inputs.bodyPath);
|
|
||||||
}
|
|
||||||
// 65536 characters is the maximum allowed for the pull request body.
|
|
||||||
if (inputs.body.length > 65536) {
|
|
||||||
core.warning(`Pull request body is too long. Truncating to 65536 characters.`);
|
|
||||||
inputs.body = inputs.body.substring(0, 65536);
|
|
||||||
}
|
|
||||||
// Get the repository path
|
|
||||||
const repoPath = utils.getRepoPath(inputs.path);
|
|
||||||
// Create a git command manager
|
|
||||||
const git = yield git_command_manager_1.GitCommandManager.create(repoPath);
|
|
||||||
// Save and unset the extraheader auth config if it exists
|
|
||||||
core.startGroup('Prepare git configuration');
|
core.startGroup('Prepare git configuration');
|
||||||
gitAuthHelper = new git_auth_helper_1.GitAuthHelper(git);
|
const repoPath = utils.getRepoPath(inputs.path);
|
||||||
yield gitAuthHelper.addSafeDirectory();
|
git = yield git_command_manager_1.GitCommandManager.create(repoPath);
|
||||||
yield gitAuthHelper.savePersistedAuth();
|
gitConfigHelper = yield git_config_helper_1.GitConfigHelper.create(git);
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
// Init the GitHub client
|
|
||||||
const githubHelper = new github_helper_1.GitHubHelper(inputs.token);
|
|
||||||
core.startGroup('Determining the base and head repositories');
|
core.startGroup('Determining the base and head repositories');
|
||||||
// Determine the base repository from git config
|
const baseRemote = gitConfigHelper.getGitRemote();
|
||||||
const remoteUrl = yield git.tryGetRemoteUrl();
|
// Init the GitHub client
|
||||||
const baseRemote = utils.getRemoteDetail(remoteUrl);
|
const githubHelper = new github_helper_1.GitHubHelper(baseRemote.hostname, inputs.token);
|
||||||
// Determine the head repository; the target for the pull request branch
|
// Determine the head repository; the target for the pull request branch
|
||||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin';
|
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin';
|
||||||
const branchRepository = inputs.pushToFork
|
const branchRepository = inputs.pushToFork
|
||||||
|
@ -363,9 +339,14 @@ function createPullRequest(inputs) {
|
||||||
if (inputs.pushToFork) {
|
if (inputs.pushToFork) {
|
||||||
// Check if the supplied fork is really a fork of the base
|
// Check if the supplied fork is really a fork of the base
|
||||||
core.info(`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`);
|
core.info(`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`);
|
||||||
const parentRepository = yield githubHelper.getRepositoryParent(branchRepository);
|
const baseParentRepository = yield githubHelper.getRepositoryParent(baseRemote.repository);
|
||||||
if (parentRepository != baseRemote.repository) {
|
const branchParentRepository = yield githubHelper.getRepositoryParent(branchRepository);
|
||||||
throw new Error(`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`);
|
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
|
// Add a remote for the fork
|
||||||
const remoteUrl = utils.getRemoteUrl(baseRemote.protocol, baseRemote.hostname, branchRepository);
|
const remoteUrl = utils.getRemoteUrl(baseRemote.protocol, baseRemote.hostname, branchRepository);
|
||||||
|
@ -376,7 +357,7 @@ function createPullRequest(inputs) {
|
||||||
// Configure auth
|
// Configure auth
|
||||||
if (baseRemote.protocol == 'HTTPS') {
|
if (baseRemote.protocol == 'HTTPS') {
|
||||||
core.startGroup('Configuring credential for HTTPS authentication');
|
core.startGroup('Configuring credential for HTTPS authentication');
|
||||||
yield gitAuthHelper.configureToken(inputs.token);
|
yield gitConfigHelper.configureToken(inputs.gitToken);
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
}
|
}
|
||||||
core.startGroup('Checking the base repository state');
|
core.startGroup('Checking the base repository state');
|
||||||
|
@ -503,11 +484,11 @@ function createPullRequest(inputs) {
|
||||||
core.setFailed(utils.getErrorMessage(error));
|
core.setFailed(utils.getErrorMessage(error));
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
// Remove auth and restore persisted auth config if it existed
|
|
||||||
core.startGroup('Restore git configuration');
|
core.startGroup('Restore git configuration');
|
||||||
yield gitAuthHelper.removeAuth();
|
if (inputs.pushToFork) {
|
||||||
yield gitAuthHelper.restorePersistedAuth();
|
yield git.exec(['remote', 'rm', 'fork']);
|
||||||
yield gitAuthHelper.removeSafeDirectory();
|
}
|
||||||
|
yield gitConfigHelper.close();
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -515,163 +496,6 @@ function createPullRequest(inputs) {
|
||||||
exports.createPullRequest = createPullRequest;
|
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:
|
/***/ 738:
|
||||||
|
@ -793,14 +617,15 @@ class GitCommandManager {
|
||||||
return output.exitCode === 0;
|
return output.exitCode === 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fetch(refSpec, remoteName, options) {
|
fetch(refSpec, remoteName, options, unshallow = false) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const args = ['-c', 'protocol.version=2', 'fetch'];
|
const args = ['-c', 'protocol.version=2', 'fetch'];
|
||||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||||
args.push('--no-tags');
|
args.push('--no-tags');
|
||||||
}
|
}
|
||||||
args.push('--progress', '--no-recurse-submodules');
|
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');
|
args.push('--unshallow');
|
||||||
}
|
}
|
||||||
if (options) {
|
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:
|
/***/ 446:
|
||||||
|
@ -1048,12 +1085,17 @@ const octokit_client_1 = __nccwpck_require__(5040);
|
||||||
const utils = __importStar(__nccwpck_require__(918));
|
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';
|
const ERROR_PR_REVIEW_TOKEN_SCOPE = 'Validation Failed: "Could not resolve to a node with the global id of';
|
||||||
class GitHubHelper {
|
class GitHubHelper {
|
||||||
constructor(token) {
|
constructor(githubServerHostname, token) {
|
||||||
const options = {};
|
const options = {};
|
||||||
if (token) {
|
if (token) {
|
||||||
options.auth = `${token}`;
|
options.auth = `${token}`;
|
||||||
}
|
}
|
||||||
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com';
|
if (githubServerHostname !== 'github.com') {
|
||||||
|
options.baseUrl = `https://${githubServerHostname}/api/v3`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
options.baseUrl = 'https://api.github.com';
|
||||||
|
}
|
||||||
this.octokit = new octokit_client_1.Octokit(options);
|
this.octokit = new octokit_client_1.Octokit(options);
|
||||||
}
|
}
|
||||||
parseRepository(repository) {
|
parseRepository(repository) {
|
||||||
|
@ -1104,7 +1146,7 @@ class GitHubHelper {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const { data: headRepo } = yield this.octokit.rest.repos.get(Object.assign({}, this.parseRepository(headRepository)));
|
const { data: headRepo } = yield this.octokit.rest.repos.get(Object.assign({}, this.parseRepository(headRepository)));
|
||||||
if (!headRepo.parent) {
|
if (!headRepo.parent) {
|
||||||
throw new Error(`Repository '${headRepository}' is not a fork. Unable to continue.`);
|
return null;
|
||||||
}
|
}
|
||||||
return headRepo.parent.full_name;
|
return headRepo.parent.full_name;
|
||||||
});
|
});
|
||||||
|
@ -1206,6 +1248,7 @@ function run() {
|
||||||
try {
|
try {
|
||||||
const inputs = {
|
const inputs = {
|
||||||
token: core.getInput('token'),
|
token: core.getInput('token'),
|
||||||
|
gitToken: core.getInput('git-token'),
|
||||||
path: core.getInput('path'),
|
path: core.getInput('path'),
|
||||||
addPaths: utils.getInputAsArray('add-paths'),
|
addPaths: utils.getInputAsArray('add-paths'),
|
||||||
commitMessage: core.getInput('commit-message'),
|
commitMessage: core.getInput('commit-message'),
|
||||||
|
@ -1228,6 +1271,24 @@ function run() {
|
||||||
draft: core.getBooleanInput('draft')
|
draft: core.getBooleanInput('draft')
|
||||||
};
|
};
|
||||||
core.debug(`Inputs: ${(0, util_1.inspect)(inputs)}`);
|
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);
|
yield (0, create_pull_request_1.createPullRequest)(inputs);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
@ -1295,7 +1356,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
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 core = __importStar(__nccwpck_require__(2186));
|
||||||
const fs = __importStar(__nccwpck_require__(7147));
|
const fs = __importStar(__nccwpck_require__(7147));
|
||||||
const path = __importStar(__nccwpck_require__(1017));
|
const path = __importStar(__nccwpck_require__(1017));
|
||||||
|
@ -1334,36 +1395,6 @@ function getRepoPath(relativePath) {
|
||||||
return repoPath;
|
return repoPath;
|
||||||
}
|
}
|
||||||
exports.getRepoPath = getRepoPath;
|
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) {
|
function getRemoteUrl(protocol, hostname, repository) {
|
||||||
return protocol == 'HTTPS'
|
return protocol == 'HTTPS'
|
||||||
? `https://${hostname}/${repository}`
|
? `https://${hostname}/${repository}`
|
||||||
|
|
|
@ -180,9 +180,12 @@ export async function createOrUpdateBranch(
|
||||||
if (branchRemoteName == 'fork') {
|
if (branchRemoteName == 'fork') {
|
||||||
// If pushing to a fork we must fetch with 'unshallow' to avoid the following error on git push
|
// 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)
|
// ! [remote rejected] HEAD -> tests/push-branch-to-fork (shallow update not allowed)
|
||||||
await git.fetch([`${workingBase}:${workingBase}`], baseRemote, [
|
await git.fetch(
|
||||||
'--force'
|
[`${workingBase}:${workingBase}`],
|
||||||
])
|
baseRemote,
|
||||||
|
['--force'],
|
||||||
|
true
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// If the remote is 'origin' we can git reset
|
// If the remote is 'origin' we can git reset
|
||||||
await git.checkout(workingBase)
|
await git.checkout(workingBase)
|
||||||
|
|
|
@ -6,11 +6,12 @@ import {
|
||||||
} from './create-or-update-branch'
|
} from './create-or-update-branch'
|
||||||
import {GitHubHelper} from './github-helper'
|
import {GitHubHelper} from './github-helper'
|
||||||
import {GitCommandManager} from './git-command-manager'
|
import {GitCommandManager} from './git-command-manager'
|
||||||
import {GitAuthHelper} from './git-auth-helper'
|
import {GitConfigHelper} from './git-config-helper'
|
||||||
import * as utils from './utils'
|
import * as utils from './utils'
|
||||||
|
|
||||||
export interface Inputs {
|
export interface Inputs {
|
||||||
token: string
|
token: string
|
||||||
|
gitToken: string
|
||||||
path: string
|
path: string
|
||||||
addPaths: string[]
|
addPaths: string[]
|
||||||
commitMessage: string
|
commitMessage: string
|
||||||
|
@ -34,45 +35,18 @@ export interface Inputs {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
let gitAuthHelper
|
let gitConfigHelper, git
|
||||||
try {
|
try {
|
||||||
if (!inputs.token) {
|
|
||||||
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
|
||||||
}
|
|
||||||
if (inputs.bodyPath) {
|
|
||||||
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
|
||||||
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
|
|
||||||
}
|
|
||||||
// Update the body input with the contents of the file
|
|
||||||
inputs.body = utils.readFile(inputs.bodyPath)
|
|
||||||
}
|
|
||||||
// 65536 characters is the maximum allowed for the pull request body.
|
|
||||||
if (inputs.body.length > 65536) {
|
|
||||||
core.warning(
|
|
||||||
`Pull request body is too long. Truncating to 65536 characters.`
|
|
||||||
)
|
|
||||||
inputs.body = inputs.body.substring(0, 65536)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the repository path
|
|
||||||
const repoPath = utils.getRepoPath(inputs.path)
|
|
||||||
// Create a git command manager
|
|
||||||
const git = await GitCommandManager.create(repoPath)
|
|
||||||
|
|
||||||
// Save and unset the extraheader auth config if it exists
|
|
||||||
core.startGroup('Prepare git configuration')
|
core.startGroup('Prepare git configuration')
|
||||||
gitAuthHelper = new GitAuthHelper(git)
|
const repoPath = utils.getRepoPath(inputs.path)
|
||||||
await gitAuthHelper.addSafeDirectory()
|
git = await GitCommandManager.create(repoPath)
|
||||||
await gitAuthHelper.savePersistedAuth()
|
gitConfigHelper = await GitConfigHelper.create(git)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
// Init the GitHub client
|
|
||||||
const githubHelper = new GitHubHelper(inputs.token)
|
|
||||||
|
|
||||||
core.startGroup('Determining the base and head repositories')
|
core.startGroup('Determining the base and head repositories')
|
||||||
// Determine the base repository from git config
|
const baseRemote = gitConfigHelper.getGitRemote()
|
||||||
const remoteUrl = await git.tryGetRemoteUrl()
|
// Init the GitHub client
|
||||||
const baseRemote = utils.getRemoteDetail(remoteUrl)
|
const githubHelper = new GitHubHelper(baseRemote.hostname, inputs.token)
|
||||||
// Determine the head repository; the target for the pull request branch
|
// Determine the head repository; the target for the pull request branch
|
||||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
||||||
const branchRepository = inputs.pushToFork
|
const branchRepository = inputs.pushToFork
|
||||||
|
@ -83,11 +57,22 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
core.info(
|
core.info(
|
||||||
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
||||||
)
|
)
|
||||||
const parentRepository =
|
const baseParentRepository = await githubHelper.getRepositoryParent(
|
||||||
|
baseRemote.repository
|
||||||
|
)
|
||||||
|
const branchParentRepository =
|
||||||
await githubHelper.getRepositoryParent(branchRepository)
|
await githubHelper.getRepositoryParent(branchRepository)
|
||||||
if (parentRepository != baseRemote.repository) {
|
if (branchParentRepository == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`
|
`Repository '${branchRepository}' is not a fork. Unable to continue.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
branchParentRepository != baseRemote.repository &&
|
||||||
|
baseParentRepository != branchParentRepository
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}', nor are they siblings. Unable to continue.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Add a remote for the fork
|
// Add a remote for the fork
|
||||||
|
@ -106,7 +91,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
// Configure auth
|
// Configure auth
|
||||||
if (baseRemote.protocol == 'HTTPS') {
|
if (baseRemote.protocol == 'HTTPS') {
|
||||||
core.startGroup('Configuring credential for HTTPS authentication')
|
core.startGroup('Configuring credential for HTTPS authentication')
|
||||||
await gitAuthHelper.configureToken(inputs.token)
|
await gitConfigHelper.configureToken(inputs.gitToken)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,11 +251,11 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(utils.getErrorMessage(error))
|
core.setFailed(utils.getErrorMessage(error))
|
||||||
} finally {
|
} finally {
|
||||||
// Remove auth and restore persisted auth config if it existed
|
|
||||||
core.startGroup('Restore git configuration')
|
core.startGroup('Restore git configuration')
|
||||||
await gitAuthHelper.removeAuth()
|
if (inputs.pushToFork) {
|
||||||
await gitAuthHelper.restorePersistedAuth()
|
await git.exec(['remote', 'rm', 'fork'])
|
||||||
await gitAuthHelper.removeSafeDirectory()
|
}
|
||||||
|
await gitConfigHelper.close()
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,8 @@ export class GitCommandManager {
|
||||||
async fetch(
|
async fetch(
|
||||||
refSpec: string[],
|
refSpec: string[],
|
||||||
remoteName?: string,
|
remoteName?: string,
|
||||||
options?: string[]
|
options?: string[],
|
||||||
|
unshallow = false
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const args = ['-c', 'protocol.version=2', 'fetch']
|
const args = ['-c', 'protocol.version=2', 'fetch']
|
||||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||||
|
@ -113,7 +114,9 @@ export class GitCommandManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
args.push('--progress', '--no-recurse-submodules')
|
args.push('--progress', '--no-recurse-submodules')
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
unshallow &&
|
||||||
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))
|
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))
|
||||||
) {
|
) {
|
||||||
args.push('--unshallow')
|
args.push('--unshallow')
|
||||||
|
|
|
@ -5,22 +5,42 @@ import * as path from 'path'
|
||||||
import {URL} from 'url'
|
import {URL} from 'url'
|
||||||
import * as utils from './utils'
|
import * as utils from './utils'
|
||||||
|
|
||||||
export class GitAuthHelper {
|
interface GitRemote {
|
||||||
|
hostname: string
|
||||||
|
protocol: string
|
||||||
|
repository: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitConfigHelper {
|
||||||
private git: GitCommandManager
|
private git: GitCommandManager
|
||||||
private gitConfigPath = ''
|
private gitConfigPath = ''
|
||||||
private workingDirectory: string
|
private workingDirectory: string
|
||||||
private safeDirectoryConfigKey = 'safe.directory'
|
private safeDirectoryConfigKey = 'safe.directory'
|
||||||
private safeDirectoryAdded = false
|
private safeDirectoryAdded = false
|
||||||
private extraheaderConfigKey: string
|
private remoteUrl = ''
|
||||||
|
private extraheaderConfigKey = ''
|
||||||
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
||||||
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
||||||
private persistedExtraheaderConfigValue = ''
|
private persistedExtraheaderConfigValue = ''
|
||||||
|
|
||||||
constructor(git: GitCommandManager) {
|
private constructor(git: GitCommandManager) {
|
||||||
this.git = git
|
this.git = git
|
||||||
this.workingDirectory = this.git.getWorkingDirectory()
|
this.workingDirectory = this.git.getWorkingDirectory()
|
||||||
const serverUrl = this.getServerUrl()
|
}
|
||||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
|
||||||
|
static async create(git: GitCommandManager): Promise<GitConfigHelper> {
|
||||||
|
const gitConfigHelper = new GitConfigHelper(git)
|
||||||
|
await gitConfigHelper.addSafeDirectory()
|
||||||
|
await gitConfigHelper.fetchRemoteDetail()
|
||||||
|
await gitConfigHelper.savePersistedAuth()
|
||||||
|
return gitConfigHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
// Remove auth and restore persisted auth config if it existed
|
||||||
|
await this.removeAuth()
|
||||||
|
await this.restorePersistedAuth()
|
||||||
|
await this.removeSafeDirectory()
|
||||||
}
|
}
|
||||||
|
|
||||||
async addSafeDirectory(): Promise<void> {
|
async addSafeDirectory(): Promise<void> {
|
||||||
|
@ -50,7 +70,57 @@ export class GitAuthHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchRemoteDetail(): Promise<void> {
|
||||||
|
this.remoteUrl = await this.git.tryGetRemoteUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
getGitRemote(): GitRemote {
|
||||||
|
return GitConfigHelper.parseGitRemote(this.remoteUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseGitRemote(remoteUrl: string): GitRemote {
|
||||||
|
const httpsUrlPattern = new RegExp(
|
||||||
|
'^(https?)://(?:.+@)?(.+?)/(.+/.+?)(\\.git)?$',
|
||||||
|
'i'
|
||||||
|
)
|
||||||
|
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
||||||
|
if (httpsMatch) {
|
||||||
|
return {
|
||||||
|
hostname: httpsMatch[2],
|
||||||
|
protocol: 'HTTPS',
|
||||||
|
repository: httpsMatch[3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sshUrlPattern = new RegExp('^git@(.+?):(.+/.+)\\.git$', 'i')
|
||||||
|
const sshMatch = remoteUrl.match(sshUrlPattern)
|
||||||
|
if (sshMatch) {
|
||||||
|
return {
|
||||||
|
hostname: sshMatch[1],
|
||||||
|
protocol: 'SSH',
|
||||||
|
repository: sshMatch[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unauthenticated git protocol for integration tests only
|
||||||
|
const gitUrlPattern = new RegExp('^git://(.+?)/(.+/.+)\\.git$', 'i')
|
||||||
|
const gitMatch = remoteUrl.match(gitUrlPattern)
|
||||||
|
if (gitMatch) {
|
||||||
|
return {
|
||||||
|
hostname: gitMatch[1],
|
||||||
|
protocol: 'GIT',
|
||||||
|
repository: gitMatch[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async savePersistedAuth(): Promise<void> {
|
async savePersistedAuth(): Promise<void> {
|
||||||
|
const serverUrl = new URL(`https://${this.getGitRemote().hostname}`)
|
||||||
|
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
||||||
// Save and unset persisted extraheader credential in git config if it exists
|
// Save and unset persisted extraheader credential in git config if it exists
|
||||||
this.persistedExtraheaderConfigValue = await this.getAndUnset()
|
this.persistedExtraheaderConfigValue = await this.getAndUnset()
|
||||||
}
|
}
|
||||||
|
@ -144,8 +214,4 @@ export class GitAuthHelper {
|
||||||
content = content.replace(find, replace)
|
content = content.replace(find, replace)
|
||||||
await fs.promises.writeFile(this.gitConfigPath, content)
|
await fs.promises.writeFile(this.gitConfigPath, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getServerUrl(): URL {
|
|
||||||
return new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com')
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -20,12 +20,16 @@ interface Pull {
|
||||||
export class GitHubHelper {
|
export class GitHubHelper {
|
||||||
private octokit: InstanceType<typeof Octokit>
|
private octokit: InstanceType<typeof Octokit>
|
||||||
|
|
||||||
constructor(token: string) {
|
constructor(githubServerHostname: string, token: string) {
|
||||||
const options: OctokitOptions = {}
|
const options: OctokitOptions = {}
|
||||||
if (token) {
|
if (token) {
|
||||||
options.auth = `${token}`
|
options.auth = `${token}`
|
||||||
}
|
}
|
||||||
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com'
|
if (githubServerHostname !== 'github.com') {
|
||||||
|
options.baseUrl = `https://${githubServerHostname}/api/v3`
|
||||||
|
} else {
|
||||||
|
options.baseUrl = 'https://api.github.com'
|
||||||
|
}
|
||||||
this.octokit = new Octokit(options)
|
this.octokit = new Octokit(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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({
|
const {data: headRepo} = await this.octokit.rest.repos.get({
|
||||||
...this.parseRepository(headRepository)
|
...this.parseRepository(headRepository)
|
||||||
})
|
})
|
||||||
if (!headRepo.parent) {
|
if (!headRepo.parent) {
|
||||||
throw new Error(
|
return null
|
||||||
`Repository '${headRepository}' is not a fork. Unable to continue.`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return headRepo.parent.full_name
|
return headRepo.parent.full_name
|
||||||
}
|
}
|
||||||
|
|
22
src/main.ts
22
src/main.ts
|
@ -7,6 +7,7 @@ async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const inputs: Inputs = {
|
const inputs: Inputs = {
|
||||||
token: core.getInput('token'),
|
token: core.getInput('token'),
|
||||||
|
gitToken: core.getInput('git-token'),
|
||||||
path: core.getInput('path'),
|
path: core.getInput('path'),
|
||||||
addPaths: utils.getInputAsArray('add-paths'),
|
addPaths: utils.getInputAsArray('add-paths'),
|
||||||
commitMessage: core.getInput('commit-message'),
|
commitMessage: core.getInput('commit-message'),
|
||||||
|
@ -30,6 +31,27 @@ async function run(): Promise<void> {
|
||||||
}
|
}
|
||||||
core.debug(`Inputs: ${inspect(inputs)}`)
|
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||||
|
|
||||||
|
if (!inputs.token) {
|
||||||
|
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
||||||
|
}
|
||||||
|
if (!inputs.gitToken) {
|
||||||
|
inputs.gitToken = inputs.token
|
||||||
|
}
|
||||||
|
if (inputs.bodyPath) {
|
||||||
|
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
||||||
|
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
|
||||||
|
}
|
||||||
|
// Update the body input with the contents of the file
|
||||||
|
inputs.body = utils.readFile(inputs.bodyPath)
|
||||||
|
}
|
||||||
|
// 65536 characters is the maximum allowed for the pull request body.
|
||||||
|
if (inputs.body.length > 65536) {
|
||||||
|
core.warning(
|
||||||
|
`Pull request body is too long. Truncating to 65536 characters.`
|
||||||
|
)
|
||||||
|
inputs.body = inputs.body.substring(0, 65536)
|
||||||
|
}
|
||||||
|
|
||||||
await createPullRequest(inputs)
|
await createPullRequest(inputs)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(utils.getErrorMessage(error))
|
core.setFailed(utils.getErrorMessage(error))
|
||||||
|
|
47
src/utils.ts
47
src/utils.ts
|
@ -41,53 +41,6 @@ export function getRepoPath(relativePath?: string): string {
|
||||||
return repoPath
|
return repoPath
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoteDetail {
|
|
||||||
hostname: string
|
|
||||||
protocol: string
|
|
||||||
repository: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRemoteDetail(remoteUrl: string): RemoteDetail {
|
|
||||||
// Parse the protocol and github repository from a URL
|
|
||||||
// e.g. HTTPS, peter-evans/create-pull-request
|
|
||||||
const githubUrl = process.env['GITHUB_SERVER_URL'] || 'https://github.com'
|
|
||||||
|
|
||||||
const githubServerMatch = githubUrl.match(/^https?:\/\/(.+)$/i)
|
|
||||||
if (!githubServerMatch) {
|
|
||||||
throw new Error('Could not parse GitHub Server name')
|
|
||||||
}
|
|
||||||
|
|
||||||
const hostname = githubServerMatch[1]
|
|
||||||
|
|
||||||
const httpsUrlPattern = new RegExp(
|
|
||||||
'^https?://.*@?' + hostname + '/(.+/.+?)(\\.git)?$',
|
|
||||||
'i'
|
|
||||||
)
|
|
||||||
const sshUrlPattern = new RegExp('^git@' + hostname + ':(.+/.+)\\.git$', 'i')
|
|
||||||
|
|
||||||
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
|
||||||
if (httpsMatch) {
|
|
||||||
return {
|
|
||||||
hostname,
|
|
||||||
protocol: 'HTTPS',
|
|
||||||
repository: httpsMatch[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sshMatch = remoteUrl.match(sshUrlPattern)
|
|
||||||
if (sshMatch) {
|
|
||||||
return {
|
|
||||||
hostname,
|
|
||||||
protocol: 'SSH',
|
|
||||||
repository: sshMatch[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRemoteUrl(
|
export function getRemoteUrl(
|
||||||
protocol: string,
|
protocol: string,
|
||||||
hostname: string,
|
hostname: string,
|
||||||
|
|
Loading…
Reference in a new issue