Compare commits
60 Commits
v6.0.0
...
commit-wit
Author | SHA1 | Date | |
---|---|---|---|
9b00b13f5b | |||
3707121594 | |||
058cde4277 | |||
a0390aed21 | |||
93bc7fd9cd | |||
a7dcd19d46 | |||
ed59299f13 | |||
cb4a03daab | |||
ca2ba77cd9 | |||
d884c1e53f | |||
85c93e4595 | |||
cd5c7e4b8b | |||
67f9e1be23 | |||
c5a7806660 | |||
4383ba9ef0 | |||
36f7648874 | |||
5f7c1586fd | |||
db1713da3a | |||
ca98a71ccc | |||
ce008085c8 | |||
7318c0b7b6 | |||
e30bbbb3c9 | |||
bad19b8e0b | |||
098cf60ce4 | |||
1ec1358801 | |||
b5ed4c38bc | |||
b67febc3a3 | |||
7c71392b65 | |||
bdffaf9259 | |||
0a0317b2d8 | |||
d9e8da8566 | |||
59e72ed4d2 | |||
15410bdb79 | |||
ce1b5d49b6 | |||
479b20f068 | |||
8c75f4ab5f | |||
5874ea5902 | |||
6d6857d369 | |||
9153d834b6 | |||
c55203cfde | |||
6ce4eca6b6 | |||
36ef0ed92f | |||
8500972a13 | |||
bda5ade93c | |||
70a41aba78 | |||
57a101480a | |||
b3a2c5d525 | |||
02c7da59e8 | |||
bac6da8071 | |||
a4f52f8033 | |||
853c071bcf | |||
d2c126edc7 | |||
43d39c6836 | |||
5a9d206da2 | |||
e0743ed96c | |||
e1529cb8ab | |||
aad52e87e7 | |||
a64ebdd734 | |||
51b40aff5f | |||
49006b2a60 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -121,7 +121,7 @@ jobs:
|
||||
name: dist
|
||||
path: dist
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
commit-message: 'build: update distribution'
|
||||
|
2
.github/workflows/slash-command-dispatch.yml
vendored
2
.github/workflows/slash-command-dispatch.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slash Command Dispatch
|
||||
uses: peter-evans/slash-command-dispatch@v3
|
||||
uses: peter-evans/slash-command-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
config: >
|
||||
|
10
README.md
10
README.md
@ -57,7 +57,7 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
||||
| `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-actions[bot] <41898282+github-actions[bot]@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 on github.com. | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` |
|
||||
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>` |
|
||||
| `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` |
|
||||
@ -74,6 +74,7 @@ All inputs are **optional**. If not set, sensible defaults will be used.
|
||||
| `team-reviewers` | A comma or newline-separated list of GitHub teams to request a review from. Note that a `repo` scoped [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token), or equivalent [GitHub App permissions](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens), are required. | |
|
||||
| `milestone` | The number of the milestone to associate this pull request with. | |
|
||||
| `draft` | Create a [draft pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). It is not possible to change draft status after creation except through the web interface. | `false` |
|
||||
| `sign-commit` | Sign the commit as bot [refer: [Signature verification for bots](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#signature-verification-for-bots)]. This can be useful if your repo or org has enforced commit-signing. | `false` |
|
||||
|
||||
#### commit-message
|
||||
|
||||
@ -91,7 +92,11 @@ In addition to a message, the `commit-message` input can also be used to populat
|
||||
#### delete-branch
|
||||
|
||||
The `delete-branch` feature doesn't delete branches immediately on merge. (It can't do that because it would require the merge to somehow trigger the action.)
|
||||
The intention of the feature is that when the action next runs it will delete the `branch` if it doesn't have an active pull request associated with it.
|
||||
The intention of the feature is that when the action next runs it will delete the `branch` if there is no diff.
|
||||
|
||||
Enabling this feature leads to the following behaviour:
|
||||
1. If a pull request was merged and the branch is left undeleted, when the action next runs it will delete the branch if there is no further diff.
|
||||
2. If a pull request is open, but there is now no longer a diff and the PR is unnecessary, the action will delete the branch causing the PR to close.
|
||||
|
||||
If you want branches to be deleted immediately on merge then you should use GitHub's `Automatically delete head branches` feature in your repository settings.
|
||||
|
||||
@ -113,6 +118,7 @@ The following outputs can be used by subsequent workflow steps.
|
||||
- `pull-request-url` - The URL of the pull request.
|
||||
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated` or `closed`.
|
||||
- `pull-request-head-sha` - The commit SHA of the pull request branch.
|
||||
- `pull-request-branch` - The branch name of the pull request.
|
||||
|
||||
Step outputs can be accessed as in the following example.
|
||||
Note that in order to read the step outputs the action step must have an id.
|
||||
|
@ -1,7 +1,9 @@
|
||||
import {
|
||||
createOrUpdateBranch,
|
||||
tryFetch,
|
||||
getWorkingBaseAndType
|
||||
getWorkingBaseAndType,
|
||||
buildBranchFileChanges,
|
||||
buildBranchCommits
|
||||
} from '../lib/create-or-update-branch'
|
||||
import * as fs from 'fs'
|
||||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
@ -140,10 +142,22 @@ describe('create-or-update-branch tests', () => {
|
||||
})
|
||||
|
||||
async function beforeTest(): Promise<void> {
|
||||
await git.fetch(
|
||||
[`${DEFAULT_BRANCH}:${DEFAULT_BRANCH}`],
|
||||
REMOTE_NAME,
|
||||
['--force', '--update-head-ok'],
|
||||
true
|
||||
)
|
||||
await git.checkout(DEFAULT_BRANCH)
|
||||
}
|
||||
|
||||
async function afterTest(deleteRemote = true): Promise<void> {
|
||||
await git.fetch(
|
||||
[`${DEFAULT_BRANCH}:${DEFAULT_BRANCH}`],
|
||||
REMOTE_NAME,
|
||||
['--force', '--update-head-ok'],
|
||||
true
|
||||
)
|
||||
await git.checkout(DEFAULT_BRANCH)
|
||||
try {
|
||||
// Get the upstream branch if it exists
|
||||
@ -198,8 +212,8 @@ describe('create-or-update-branch tests', () => {
|
||||
}
|
||||
|
||||
it('tests if a branch exists and can be fetched', async () => {
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_BASE_BRANCH)).toBeTruthy()
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_EXIST_BRANCH)).toBeFalsy()
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_BASE_BRANCH, 1)).toBeTruthy()
|
||||
expect(await tryFetch(git, REMOTE_NAME, NOT_EXIST_BRANCH, 1)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('tests getWorkingBaseAndType on a checked out ref', async () => {
|
||||
@ -217,6 +231,147 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(workingBaseType).toEqual('commit')
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with no diff', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
expect(branchCommits.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with addition and modification', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
await createChanges()
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(1)
|
||||
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||
expect(branchCommits[0].changes.length).toEqual(2)
|
||||
expect(branchCommits[0].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: 'A'}
|
||||
])
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with addition and deletion', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
await createChanges()
|
||||
const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt'
|
||||
const filepath = path.join(REPO_PATH, TRACKED_FILE_NEW_PATH)
|
||||
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
|
||||
await fs.promises.rename(path.join(REPO_PATH, TRACKED_FILE), filepath)
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(1)
|
||||
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||
expect(branchCommits[0].changes.length).toEqual(3)
|
||||
expect(branchCommits[0].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'D'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: 'A'},
|
||||
{mode: '100644', path: TRACKED_FILE_NEW_PATH, status: 'A'}
|
||||
])
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with multiple commits', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await createChanges()
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', `Test changes ${i}`])
|
||||
}
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(3)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
expect(branchCommits[i].subject).toEqual(`Test changes ${i}`)
|
||||
expect(branchCommits[i].changes.length).toEqual(2)
|
||||
const untrackedFileStatus = i == 0 ? 'A' : 'M'
|
||||
expect(branchCommits[i].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: untrackedFileStatus}
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
it('tests buildBranchFileChanges with no diff', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||
expect(branchFileChanges.additions.length).toEqual(0)
|
||||
expect(branchFileChanges.deletions.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('tests buildBranchFileChanges with addition and modification', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const changes = await createChanges()
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||
|
||||
expect(branchFileChanges.additions).toEqual([
|
||||
{
|
||||
path: TRACKED_FILE,
|
||||
contents: Buffer.from(changes.tracked, 'binary').toString('base64')
|
||||
},
|
||||
{
|
||||
path: UNTRACKED_FILE,
|
||||
contents: Buffer.from(changes.untracked, 'binary').toString('base64')
|
||||
}
|
||||
])
|
||||
expect(branchFileChanges.deletions.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('tests buildBranchFileChanges with addition and deletion', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const changes = await createChanges()
|
||||
const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt'
|
||||
const filepath = path.join(REPO_PATH, TRACKED_FILE_NEW_PATH)
|
||||
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
|
||||
await fs.promises.rename(path.join(REPO_PATH, TRACKED_FILE), filepath)
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||
|
||||
expect(branchFileChanges.additions).toEqual([
|
||||
{
|
||||
path: UNTRACKED_FILE,
|
||||
contents: Buffer.from(changes.untracked, 'binary').toString('base64')
|
||||
},
|
||||
{
|
||||
path: TRACKED_FILE_NEW_PATH,
|
||||
contents: Buffer.from(changes.tracked, 'binary').toString('base64')
|
||||
}
|
||||
])
|
||||
expect(branchFileChanges.deletions).toEqual([{path: TRACKED_FILE}])
|
||||
})
|
||||
|
||||
it('tests buildBranchFileChanges with binary files', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const filename = 'c/untracked-binary-file'
|
||||
const filepath = path.join(REPO_PATH, filename)
|
||||
const binaryData = Buffer.from([0x00, 0xff, 0x10, 0x20])
|
||||
await fs.promises.mkdir(path.dirname(filepath), {recursive: true})
|
||||
await fs.promises.writeFile(filepath, binaryData)
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||
|
||||
expect(branchFileChanges.additions).toEqual([
|
||||
{
|
||||
path: filename,
|
||||
contents: binaryData.toString('base64')
|
||||
}
|
||||
])
|
||||
expect(branchFileChanges.deletions.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('tests no changes resulting in no new branch being created', async () => {
|
||||
const commitMessage = uuidv4()
|
||||
const result = await createOrUpdateBranch(
|
||||
@ -1454,8 +1609,7 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(
|
||||
await gitLogMatches([
|
||||
_commitMessage,
|
||||
...commits.commitMsgs,
|
||||
INIT_COMMIT_MESSAGE
|
||||
commits.commitMsgs[0] // fetch depth of base is 1
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
@ -1590,7 +1744,9 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||
expect(
|
||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
||||
await gitLogMatches([
|
||||
commits.commitMsgs[0] // fetch depth of base is 1
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
@ -1668,7 +1824,9 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||
expect(
|
||||
await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE])
|
||||
await gitLogMatches([
|
||||
commits.commitMsgs[0] // fetch depth of base is 1
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
@ -1951,8 +2109,7 @@ describe('create-or-update-branch tests', () => {
|
||||
await gitLogMatches([
|
||||
_commitMessage,
|
||||
..._commits.commitMsgs,
|
||||
...commitsOnBase.commitMsgs,
|
||||
INIT_COMMIT_MESSAGE
|
||||
commitsOnBase.commitMsgs[0] // fetch depth of base is 1
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
@ -2147,8 +2304,7 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(
|
||||
await gitLogMatches([
|
||||
_commitMessage,
|
||||
...commitsOnBase.commitMsgs,
|
||||
INIT_COMMIT_MESSAGE
|
||||
commitsOnBase.commitMsgs[0] // fetch depth of base is 1
|
||||
])
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
@ -13,7 +13,7 @@ git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all
|
||||
# Give the daemon time to start
|
||||
sleep 2
|
||||
|
||||
# Create a local clone and make an initial commit
|
||||
# Create a local clone and make initial commits
|
||||
mkdir -p /git/local/repos
|
||||
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
||||
cd /git/local/repos/test-base
|
||||
@ -22,6 +22,10 @@ git config --global user.name "Your Name"
|
||||
echo "#test-base" > README.md
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
echo "#test-base :sparkles:" > README.md
|
||||
git add .
|
||||
git commit -m "add sparkles" -m "Change description:
|
||||
- updates README.md to add sparkles to the title"
|
||||
git push -u
|
||||
git log -1 --pretty=oneline
|
||||
git config --global --unset user.email
|
||||
|
26
__test__/git-command-manager.int.test.ts
Normal file
26
__test__/git-command-manager.int.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {GitCommandManager, Commit} from '../lib/git-command-manager'
|
||||
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
|
||||
describe('git-command-manager integration tests', () => {
|
||||
let git: GitCommandManager
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
await git.checkout('main')
|
||||
})
|
||||
|
||||
it('tests getCommit', async () => {
|
||||
const parent = await git.getCommit('HEAD^')
|
||||
const commit = await git.getCommit('HEAD')
|
||||
expect(parent.subject).toEqual('initial commit')
|
||||
expect(parent.changes).toEqual([
|
||||
{mode: '100644', status: 'A', path: 'README.md'}
|
||||
])
|
||||
expect(commit.subject).toEqual('add sparkles')
|
||||
expect(commit.parents[0]).toEqual(parent.sha)
|
||||
expect(commit.changes).toEqual([
|
||||
{mode: '100644', status: 'M', path: 'README.md'}
|
||||
])
|
||||
})
|
||||
})
|
@ -7,7 +7,6 @@ const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||
|
||||
describe('git-config-helper integration tests', () => {
|
||||
let git: GitCommandManager
|
||||
let gitConfigHelper: GitConfigHelper
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
|
@ -74,6 +74,9 @@ inputs:
|
||||
draft:
|
||||
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
|
||||
default: false
|
||||
sign-commit:
|
||||
description: 'Sign the commit as github-actions bot (and as custom app if a different github-token is provided)'
|
||||
default: true
|
||||
outputs:
|
||||
pull-request-number:
|
||||
description: 'The pull request number'
|
||||
@ -83,6 +86,8 @@ outputs:
|
||||
description: 'The pull request operation performed by the action, `created`, `updated` or `closed`.'
|
||||
pull-request-head-sha:
|
||||
description: 'The commit SHA of the pull request branch.'
|
||||
pull-request-branch:
|
||||
description: 'The pull request branch name'
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
|
53484
dist/index.js
vendored
53484
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
@ -35,7 +35,12 @@ The reason is that I'm trying very hard to keep the interface for this action to
|
||||
Git hooks must be installed after a repository is checked out in order for them to work.
|
||||
So the straightforward solution is to just not install them during the workflow where this action is used.
|
||||
|
||||
- If hooks are automatically enabled by a framework, use an option provided by the framework to disable them. For example, for Husky users, they can be disabled with the `--ignore-scripts` flag.
|
||||
- If hooks are automatically enabled by a framework, use an option provided by the framework to disable them. For example, for Husky users, they can be disabled with the `--ignore-scripts` flag, or by setting the `HUSKY` environment variable when the action runs.
|
||||
```yml
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
env:
|
||||
HUSKY: '0'
|
||||
```
|
||||
- If hooks are installed in a script, then add a condition checking if the `CI` environment variable exists.
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
4061
package-lock.json
generated
4061
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@ -32,29 +32,32 @@
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@octokit/core": "^4.2.4",
|
||||
"@octokit/graphql": "^8.1.1",
|
||||
"@octokit/graphql-schema": "^15.25.0",
|
||||
"@octokit/plugin-paginate-rest": "^5.0.1",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
"undici": "^6.19.5",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^18.19.10",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^18.19.43",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-github": "^4.10.1",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest": "^27.6.3",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^3.2.4",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
import * as core from '@actions/core'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {GitCommandManager, Commit} from './git-command-manager'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
import * as utils from './utils'
|
||||
|
||||
const CHERRYPICK_EMPTY =
|
||||
'The previous cherry-pick is now empty, possibly due to conflict resolution.'
|
||||
const NOTHING_TO_COMMIT = 'nothing to commit, working tree clean'
|
||||
|
||||
const FETCH_DEPTH_MARGIN = 10
|
||||
|
||||
export enum WorkingBaseType {
|
||||
Branch = 'branch',
|
||||
Commit = 'commit'
|
||||
@ -31,11 +34,13 @@ export async function getWorkingBaseAndType(
|
||||
export async function tryFetch(
|
||||
git: GitCommandManager,
|
||||
remote: string,
|
||||
branch: string
|
||||
branch: string,
|
||||
depth: number
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await git.fetch([`${branch}:refs/remotes/${remote}/${branch}`], remote, [
|
||||
'--force'
|
||||
'--force',
|
||||
`--depth=${depth}`
|
||||
])
|
||||
return true
|
||||
} catch {
|
||||
@ -43,6 +48,56 @@ export async function tryFetch(
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildBranchCommits(
|
||||
git: GitCommandManager,
|
||||
base: string,
|
||||
branch: string
|
||||
): Promise<Commit[]> {
|
||||
const output = await git.exec(['log', '--format=%H', `${base}..${branch}`])
|
||||
const shas = output.stdout
|
||||
.split('\n')
|
||||
.filter(x => x !== '')
|
||||
.reverse()
|
||||
const commits: Commit[] = []
|
||||
for (const sha of shas) {
|
||||
const commit = await git.getCommit(sha)
|
||||
commits.push(commit)
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
export async function buildBranchFileChanges(
|
||||
git: GitCommandManager,
|
||||
base: string,
|
||||
branch: string
|
||||
): Promise<BranchFileChanges> {
|
||||
const branchFileChanges: BranchFileChanges = {
|
||||
additions: [],
|
||||
deletions: []
|
||||
}
|
||||
const changedFiles = await git.getChangedFiles([
|
||||
'--diff-filter=AM',
|
||||
`${base}..${branch}`
|
||||
])
|
||||
const deletedFiles = await git.getChangedFiles([
|
||||
'--diff-filter=D',
|
||||
`${base}..${branch}`
|
||||
])
|
||||
const repoPath = git.getWorkingDirectory()
|
||||
for (const file of changedFiles) {
|
||||
branchFileChanges.additions!.push({
|
||||
path: file,
|
||||
contents: utils.readFileBase64([repoPath, file])
|
||||
})
|
||||
}
|
||||
for (const file of deletedFiles) {
|
||||
branchFileChanges.deletions!.push({
|
||||
path: file
|
||||
})
|
||||
}
|
||||
return branchFileChanges
|
||||
}
|
||||
|
||||
// Return the number of commits that branch2 is ahead of branch1
|
||||
async function commitsAhead(
|
||||
git: GitCommandManager,
|
||||
@ -106,11 +161,23 @@ function splitLines(multilineString: string): string[] {
|
||||
.filter(x => x !== '')
|
||||
}
|
||||
|
||||
export interface BranchFileChanges {
|
||||
additions: {
|
||||
path: string
|
||||
contents: string
|
||||
}[]
|
||||
deletions: {
|
||||
path: string
|
||||
}[]
|
||||
}
|
||||
|
||||
interface CreateOrUpdateBranchResult {
|
||||
action: string
|
||||
base: string
|
||||
hasDiffWithBase: boolean
|
||||
headSha: string
|
||||
branchFileChanges?: BranchFileChanges
|
||||
branchCommits: Commit[]
|
||||
}
|
||||
|
||||
export async function createOrUpdateBranch(
|
||||
@ -140,7 +207,8 @@ export async function createOrUpdateBranch(
|
||||
action: 'none',
|
||||
base: base,
|
||||
hasDiffWithBase: false,
|
||||
headSha: ''
|
||||
headSha: '',
|
||||
branchCommits: []
|
||||
}
|
||||
|
||||
// Save the working base changes to a temporary branch
|
||||
@ -173,24 +241,12 @@ export async function createOrUpdateBranch(
|
||||
// Stash any uncommitted tracked and untracked changes
|
||||
const stashed = await git.stashPush(['--include-untracked'])
|
||||
|
||||
// Perform fetch and reset the working base
|
||||
// Reset the working base
|
||||
// Commits made during the workflow will be removed
|
||||
if (workingBaseType == WorkingBaseType.Branch) {
|
||||
core.info(`Resetting working base branch '${workingBase}'`)
|
||||
if (branchRemoteName == 'fork') {
|
||||
// If pushing to a fork we must fetch with 'unshallow' to avoid the following error on git push
|
||||
// ! [remote rejected] HEAD -> tests/push-branch-to-fork (shallow update not allowed)
|
||||
await git.fetch(
|
||||
[`${workingBase}:${workingBase}`],
|
||||
baseRemote,
|
||||
['--force'],
|
||||
true
|
||||
)
|
||||
} else {
|
||||
// If the remote is 'origin' we can git reset
|
||||
await git.checkout(workingBase)
|
||||
await git.exec(['reset', '--hard', `${baseRemote}/${workingBase}`])
|
||||
}
|
||||
await git.checkout(workingBase)
|
||||
await git.exec(['reset', '--hard', `${baseRemote}/${workingBase}`])
|
||||
}
|
||||
|
||||
// If the working base is not the base, rebase the temp branch commits
|
||||
@ -199,8 +255,13 @@ export async function createOrUpdateBranch(
|
||||
core.info(
|
||||
`Rebasing commits made to ${workingBaseType} '${workingBase}' on to base branch '${base}'`
|
||||
)
|
||||
const fetchArgs = ['--force']
|
||||
if (branchRemoteName != 'fork') {
|
||||
// If pushing to a fork we cannot shallow fetch otherwise the 'shallow update not allowed' error occurs
|
||||
fetchArgs.push('--depth=1')
|
||||
}
|
||||
// Checkout the actual base
|
||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
||||
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||
await git.checkout(base)
|
||||
// Cherrypick commits from the temporary branch starting from the working base
|
||||
const commits = await git.revList(
|
||||
@ -219,11 +280,18 @@ export async function createOrUpdateBranch(
|
||||
// Reset the temp branch to the working index
|
||||
await git.checkout(tempBranch, 'HEAD')
|
||||
// Reset the base
|
||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
||||
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||
}
|
||||
|
||||
// Determine the fetch depth for the pull request branch (best effort)
|
||||
const tempBranchCommitsAhead = await commitsAhead(git, base, tempBranch)
|
||||
const fetchDepth =
|
||||
tempBranchCommitsAhead > 0
|
||||
? tempBranchCommitsAhead + FETCH_DEPTH_MARGIN
|
||||
: FETCH_DEPTH_MARGIN
|
||||
|
||||
// Try to fetch the pull request branch
|
||||
if (!(await tryFetch(git, branchRemoteName, branch))) {
|
||||
if (!(await tryFetch(git, branchRemoteName, branch, fetchDepth))) {
|
||||
// The pull request branch does not exist
|
||||
core.info(`Pull request branch '${branch}' does not exist yet.`)
|
||||
// Create the pull request branch
|
||||
@ -257,7 +325,6 @@ export async function createOrUpdateBranch(
|
||||
// temp branch. This catches a case where the base branch has been force pushed to
|
||||
// a new commit.
|
||||
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
||||
const tempBranchCommitsAhead = await commitsAhead(git, base, tempBranch)
|
||||
const branchCommitsAhead = await commitsAhead(git, base, branch)
|
||||
if (
|
||||
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||
@ -286,6 +353,12 @@ export async function createOrUpdateBranch(
|
||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||
}
|
||||
|
||||
// Build the branch commits
|
||||
result.branchCommits = await buildBranchCommits(git, base, branch)
|
||||
|
||||
// Build the branch file changes
|
||||
result.branchFileChanges = await buildBranchFileChanges(git, base, branch)
|
||||
|
||||
// Get the pull request branch SHA
|
||||
result.headSha = await git.revParse('HEAD')
|
||||
|
||||
|
@ -32,6 +32,7 @@ export interface Inputs {
|
||||
teamReviewers: string[]
|
||||
milestone: number
|
||||
draft: boolean
|
||||
signCommit: boolean
|
||||
}
|
||||
|
||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
@ -185,6 +186,8 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
inputs.signoff,
|
||||
inputs.addPaths
|
||||
)
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
core.endGroup()
|
||||
|
||||
if (['created', 'updated'].includes(result.action)) {
|
||||
@ -192,17 +195,37 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
core.startGroup(
|
||||
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
||||
)
|
||||
await git.push([
|
||||
'--force-with-lease',
|
||||
branchRemoteName,
|
||||
`${inputs.branch}:refs/heads/${inputs.branch}`
|
||||
])
|
||||
if (inputs.signCommit) {
|
||||
// Stash any uncommitted tracked and untracked changes
|
||||
const stashed = await git.stashPush(['--include-untracked'])
|
||||
await git.checkout(inputs.branch)
|
||||
await githubHelper.pushSignedCommits(
|
||||
result.branchCommits,
|
||||
repoPath,
|
||||
branchRepository,
|
||||
inputs.branch
|
||||
)
|
||||
// await githubHelper.pushSignedCommit(
|
||||
// branchRepository,
|
||||
// inputs.branch,
|
||||
// inputs.base,
|
||||
// inputs.commitMessage,
|
||||
// result.branchFileChanges
|
||||
// )
|
||||
await git.checkout('-')
|
||||
if (stashed) {
|
||||
await git.stashPop()
|
||||
}
|
||||
} else {
|
||||
await git.push([
|
||||
'--force-with-lease',
|
||||
branchRemoteName,
|
||||
`${inputs.branch}:refs/heads/${inputs.branch}`
|
||||
])
|
||||
}
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
|
||||
if (result.hasDiffWithBase) {
|
||||
// Create or update the pull request
|
||||
core.startGroup('Create or update the pull request')
|
||||
@ -223,6 +246,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
core.setOutput('pull-request-operation', 'updated')
|
||||
}
|
||||
core.setOutput('pull-request-head-sha', result.headSha)
|
||||
core.setOutput('pull-request-branch', inputs.branch)
|
||||
// Deprecated
|
||||
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
|
||||
core.endGroup()
|
||||
|
@ -5,6 +5,19 @@ import * as path from 'path'
|
||||
|
||||
const tagsRefSpec = '+refs/tags/*:refs/tags/*'
|
||||
|
||||
export type Commit = {
|
||||
sha: string
|
||||
tree: string
|
||||
parents: string[]
|
||||
subject: string
|
||||
body: string
|
||||
changes: {
|
||||
mode: string
|
||||
status: 'A' | 'M' | 'D'
|
||||
path: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export class GitCommandManager {
|
||||
private gitPath: string
|
||||
private workingDirectory: string
|
||||
@ -138,6 +151,43 @@ export class GitCommandManager {
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
async getCommit(ref: string): Promise<Commit> {
|
||||
const endOfBody = '###EOB###'
|
||||
const output = await this.exec([
|
||||
'show',
|
||||
'--raw',
|
||||
'--cc',
|
||||
'--diff-filter=AMD',
|
||||
`--format=%H%n%T%n%P%n%s%n%b%n${endOfBody}`,
|
||||
ref
|
||||
])
|
||||
const lines = output.stdout.split('\n')
|
||||
const endOfBodyIndex = lines.lastIndexOf(endOfBody)
|
||||
const detailLines = lines.slice(0, endOfBodyIndex)
|
||||
|
||||
return <Commit>{
|
||||
sha: detailLines[0],
|
||||
tree: detailLines[1],
|
||||
parents: detailLines[2].split(' '),
|
||||
subject: detailLines[3],
|
||||
body: detailLines.slice(4, endOfBodyIndex).join('\n'),
|
||||
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
|
||||
const change = line.match(
|
||||
/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/
|
||||
)
|
||||
if (change) {
|
||||
return {
|
||||
mode: change[3] === 'D' ? change[1] : change[2],
|
||||
status: change[3],
|
||||
path: change[4]
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unexpected line format: ${line}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
||||
const output = await this.exec([
|
||||
'config',
|
||||
@ -166,6 +216,15 @@ export class GitCommandManager {
|
||||
return output.exitCode === 1
|
||||
}
|
||||
|
||||
async getChangedFiles(options?: string[]): Promise<string[]> {
|
||||
const args = ['diff', '--name-only']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
const output = await this.exec(args)
|
||||
return output.stdout.split('\n').filter(filename => filename != '')
|
||||
}
|
||||
|
||||
async isDirty(untracked: boolean, pathspec?: string[]): Promise<boolean> {
|
||||
const pathspecArgs = pathspec ? ['--', ...pathspec] : []
|
||||
// Check untracked changes
|
||||
|
@ -1,6 +1,14 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Inputs} from './create-pull-request'
|
||||
import {Commit} from './git-command-manager'
|
||||
import {Octokit, OctokitOptions} from './octokit-client'
|
||||
import type {
|
||||
Repository as TempRepository,
|
||||
Ref,
|
||||
Commit as CommitTemp,
|
||||
FileChanges
|
||||
} from '@octokit/graphql-schema'
|
||||
import {BranchFileChanges} from './create-or-update-branch'
|
||||
import * as utils from './utils'
|
||||
|
||||
const ERROR_PR_REVIEW_TOKEN_SCOPE =
|
||||
@ -17,6 +25,13 @@ interface Pull {
|
||||
created: boolean
|
||||
}
|
||||
|
||||
type TreeObject = {
|
||||
path: string
|
||||
mode: '100644' | '100755' | '040000' | '160000' | '120000'
|
||||
sha: string | null
|
||||
type: 'blob'
|
||||
}
|
||||
|
||||
export class GitHubHelper {
|
||||
private octokit: InstanceType<typeof Octokit>
|
||||
|
||||
@ -48,7 +63,6 @@ export class GitHubHelper {
|
||||
): Promise<Pull> {
|
||||
const [headOwner] = headRepository.split('/')
|
||||
const headBranch = `${headOwner}:${inputs.branch}`
|
||||
const headBranchFull = `${headRepository}:${inputs.branch}`
|
||||
|
||||
// Try to create the pull request
|
||||
try {
|
||||
@ -85,7 +99,7 @@ export class GitHubHelper {
|
||||
const {data: pulls} = await this.octokit.rest.pulls.list({
|
||||
...this.parseRepository(baseRepository),
|
||||
state: 'open',
|
||||
head: headBranchFull,
|
||||
head: headBranch,
|
||||
base: inputs.base
|
||||
})
|
||||
core.info(`Attempting update of pull request`)
|
||||
@ -185,4 +199,268 @@ export class GitHubHelper {
|
||||
|
||||
return pull
|
||||
}
|
||||
|
||||
async pushSignedCommits(
|
||||
branchCommits: Commit[],
|
||||
repoPath: string,
|
||||
branchRepository: string,
|
||||
branch: string
|
||||
): Promise<void> {
|
||||
let headSha = ''
|
||||
for (const commit of branchCommits) {
|
||||
headSha = await this.createCommit(commit, repoPath, branchRepository)
|
||||
}
|
||||
await this.createOrUpdateRef(branchRepository, branch, headSha)
|
||||
}
|
||||
|
||||
private async createCommit(
|
||||
commit: Commit,
|
||||
repoPath: string,
|
||||
branchRepository: string
|
||||
): Promise<string> {
|
||||
const repository = this.parseRepository(branchRepository)
|
||||
let treeSha = commit.tree
|
||||
if (commit.changes.length > 0) {
|
||||
core.debug(`Creating tree objects for local commit ${commit.sha}`)
|
||||
const treeObjects = await Promise.all(
|
||||
commit.changes.map(async ({path, mode, status}) => {
|
||||
let sha: string | null = null
|
||||
if (status === 'A' || status === 'M') {
|
||||
core.debug(`Creating blob for file '${path}'`)
|
||||
const {data: blob} = await this.octokit.rest.git.createBlob({
|
||||
...repository,
|
||||
content: utils.readFileBase64([repoPath, path]),
|
||||
encoding: 'base64'
|
||||
})
|
||||
sha = blob.sha
|
||||
}
|
||||
return <TreeObject>{
|
||||
path,
|
||||
mode,
|
||||
sha,
|
||||
type: 'blob'
|
||||
}
|
||||
})
|
||||
)
|
||||
core.debug(`Creating tree for local commit ${commit.sha}`)
|
||||
const {data: tree} = await this.octokit.rest.git.createTree({
|
||||
...repository,
|
||||
base_tree: commit.parents[0],
|
||||
tree: treeObjects
|
||||
})
|
||||
treeSha = tree.sha
|
||||
core.debug(`Created tree ${treeSha} for local commit ${commit.sha}`)
|
||||
}
|
||||
|
||||
const {data: remoteCommit} = await this.octokit.rest.git.createCommit({
|
||||
...repository,
|
||||
parents: commit.parents,
|
||||
tree: treeSha,
|
||||
message: `${commit.subject}\n\n${commit.body}`
|
||||
})
|
||||
core.debug(
|
||||
`Created commit ${remoteCommit.sha} for local commit ${commit.sha}`
|
||||
)
|
||||
return remoteCommit.sha
|
||||
}
|
||||
|
||||
private async createOrUpdateRef(
|
||||
branchRepository: string,
|
||||
branch: string,
|
||||
newHead: string
|
||||
) {
|
||||
const repository = this.parseRepository(branchRepository)
|
||||
const branchExists = await this.octokit.rest.git
|
||||
.getRef({
|
||||
...repository,
|
||||
ref: branch
|
||||
})
|
||||
.then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
|
||||
if (branchExists) {
|
||||
core.debug(`Branch ${branch} exists, updating ref`)
|
||||
await this.octokit.rest.git.updateRef({
|
||||
...repository,
|
||||
sha: newHead,
|
||||
ref: `heads/${branch}`
|
||||
})
|
||||
} else {
|
||||
core.debug(`Branch ${branch} does not exist, creating ref`)
|
||||
await this.octokit.rest.git.createRef({
|
||||
...repository,
|
||||
sha: newHead,
|
||||
ref: `refs/heads/${branch}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async pushSignedCommit(
|
||||
branchRepository: string,
|
||||
branch: string,
|
||||
base: string,
|
||||
commitMessage: string,
|
||||
branchFileChanges?: BranchFileChanges
|
||||
): Promise<void> {
|
||||
core.info(`Use API to push a signed commit`)
|
||||
|
||||
const [repoOwner, repoName] = branchRepository.split('/')
|
||||
core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`)
|
||||
const refQuery = `
|
||||
query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) {
|
||||
repository(owner: $repoOwner, name: $repoName){
|
||||
id
|
||||
ref(qualifiedName: $branchName){
|
||||
id
|
||||
name
|
||||
prefix
|
||||
target{
|
||||
id
|
||||
oid
|
||||
commitUrl
|
||||
commitResourcePath
|
||||
abbreviatedOid
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
`
|
||||
|
||||
let branchRef = await this.octokit.graphql<{repository: TempRepository}>(
|
||||
refQuery,
|
||||
{
|
||||
repoOwner: repoOwner,
|
||||
repoName: repoName,
|
||||
branchName: branch
|
||||
}
|
||||
)
|
||||
core.debug(
|
||||
`Fetched information for branch '${branch}' - '${JSON.stringify(branchRef)}'`
|
||||
)
|
||||
|
||||
// if the branch does not exist, then first we need to create the branch from base
|
||||
if (branchRef.repository.ref == null) {
|
||||
core.debug(`Branch does not exist - '${branch}'`)
|
||||
branchRef = await this.octokit.graphql<{repository: TempRepository}>(
|
||||
refQuery,
|
||||
{
|
||||
repoOwner: repoOwner,
|
||||
repoName: repoName,
|
||||
branchName: base
|
||||
}
|
||||
)
|
||||
core.debug(
|
||||
`Fetched information for base branch '${base}' - '${JSON.stringify(branchRef)}'`
|
||||
)
|
||||
|
||||
core.info(
|
||||
`Creating new branch '${branch}' from '${base}', with ref '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`
|
||||
)
|
||||
if (branchRef.repository.ref != null) {
|
||||
core.debug(`Send request for creating new branch`)
|
||||
const newBranchMutation = `
|
||||
mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) {
|
||||
createRef(input: {
|
||||
name: $branchName,
|
||||
oid: $oid,
|
||||
repositoryId: $repoId
|
||||
}) {
|
||||
ref {
|
||||
id
|
||||
name
|
||||
prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const newBranch = await this.octokit.graphql<{createRef: {ref: Ref}}>(
|
||||
newBranchMutation,
|
||||
{
|
||||
repoId: branchRef.repository.id,
|
||||
oid: branchRef.repository.ref.target!.oid,
|
||||
branchName: 'refs/heads/' + branch
|
||||
}
|
||||
)
|
||||
core.debug(
|
||||
`Created new branch '${branch}': '${JSON.stringify(newBranch.createRef.ref)}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
core.info(
|
||||
`Hash ref of branch '${branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`
|
||||
)
|
||||
|
||||
const fileChanges = <FileChanges>{
|
||||
additions: branchFileChanges!.additions,
|
||||
deletions: branchFileChanges!.deletions
|
||||
}
|
||||
|
||||
const pushCommitMutation = `
|
||||
mutation PushCommit(
|
||||
$repoNameWithOwner: String!,
|
||||
$branchName: String!,
|
||||
$headOid: GitObjectID!,
|
||||
$commitMessage: String!,
|
||||
$fileChanges: FileChanges
|
||||
) {
|
||||
createCommitOnBranch(input: {
|
||||
branch: {
|
||||
repositoryNameWithOwner: $repoNameWithOwner,
|
||||
branchName: $branchName,
|
||||
}
|
||||
fileChanges: $fileChanges
|
||||
message: {
|
||||
headline: $commitMessage
|
||||
}
|
||||
expectedHeadOid: $headOid
|
||||
}){
|
||||
clientMutationId
|
||||
ref{
|
||||
id
|
||||
name
|
||||
prefix
|
||||
}
|
||||
commit{
|
||||
id
|
||||
abbreviatedOid
|
||||
oid
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const pushCommitVars = {
|
||||
branchName: branch,
|
||||
repoNameWithOwner: repoOwner + '/' + repoName,
|
||||
headOid: branchRef.repository.ref!.target!.oid,
|
||||
commitMessage: commitMessage,
|
||||
fileChanges: fileChanges
|
||||
}
|
||||
|
||||
const pushCommitVarsWithoutContents = {
|
||||
...pushCommitVars,
|
||||
fileChanges: {
|
||||
...pushCommitVars.fileChanges,
|
||||
additions: pushCommitVars.fileChanges.additions?.map(addition => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {contents, ...rest} = addition
|
||||
return rest
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
core.debug(
|
||||
`Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'`
|
||||
)
|
||||
|
||||
const commit = await this.octokit.graphql<{
|
||||
createCommitOnBranch: {ref: Ref; commit: CommitTemp}
|
||||
}>(pushCommitMutation, pushCommitVars)
|
||||
|
||||
core.debug(`Pushed commit - '${JSON.stringify(commit)}'`)
|
||||
core.info(
|
||||
`Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ async function run(): Promise<void> {
|
||||
reviewers: utils.getInputAsArray('reviewers'),
|
||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||
milestone: Number(core.getInput('milestone')),
|
||||
draft: core.getBooleanInput('draft')
|
||||
draft: core.getBooleanInput('draft'),
|
||||
signCommit: core.getBooleanInput('sign-commit')
|
||||
}
|
||||
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {Octokit as Core} from '@octokit/core'
|
||||
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
||||
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
||||
import {HttpsProxyAgent} from 'https-proxy-agent'
|
||||
import {getProxyForUrl} from 'proxy-from-env'
|
||||
import {ProxyAgent, fetch as undiciFetch} from 'undici'
|
||||
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
||||
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||
|
||||
@ -12,12 +12,23 @@ export const Octokit = Core.plugin(
|
||||
autoProxyAgent
|
||||
)
|
||||
|
||||
const proxyFetch =
|
||||
(proxyUrl: string): typeof undiciFetch =>
|
||||
(url, opts) => {
|
||||
return undiciFetch(url, {
|
||||
...opts,
|
||||
dispatcher: new ProxyAgent({
|
||||
uri: proxyUrl
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
|
||||
function autoProxyAgent(octokit: Core) {
|
||||
octokit.hook.before('request', options => {
|
||||
const proxy = getProxyForUrl(options.baseUrl)
|
||||
if (proxy) {
|
||||
options.request.agent = new HttpsProxyAgent(proxy)
|
||||
options.request.fetch = proxyFetch(proxy)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -126,6 +126,10 @@ export function readFile(path: string): string {
|
||||
return fs.readFileSync(path, 'utf-8')
|
||||
}
|
||||
|
||||
export function readFileBase64(pathParts: string[]): string {
|
||||
return fs.readFileSync(path.resolve(...pathParts)).toString('base64')
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
function hasErrorCode(error: any): error is {code: string} {
|
||||
return typeof (error && error.code) === 'string'
|
||||
|
Reference in New Issue
Block a user