Compare commits

..

11 Commits

Author SHA1 Message Date
5e914681df fix: support symlinks when commit signing (#3359) 2024-09-18 10:40:10 -07:00
2f38cd26bf fix: support submodules when commit signing (#3354)
* fix: support submodules when commit signing

* create correct tree object for submodule

* update log messages
2024-09-18 09:46:39 -07:00
7a8aeac749 build(deps-dev): bump eslint from 8.57.0 to 8.57.1 (#3344)
Bumps [eslint](https://github.com/eslint/eslint) from 8.57.0 to 8.57.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.57.0...v8.57.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 12:34:38 +00:00
d39d596a77 build(deps-dev): bump @types/jest from 29.5.12 to 29.5.13 (#3343)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.5.12 to 29.5.13.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 12:34:19 +00:00
f6f978fd3d docs: correct suggestion for bot setup (#3342)
The option suggested here (Read only) is not an option, and wouldn't mean/do anything - you can read these files if you have access to the repo files. The description says this is needed if the PR could change the workflow files, so you need "Read and Write". Pretty sure this is just a typo, copied from the line below instead of the line above.
2024-09-16 09:49:30 -07:00
6cd32fd936 fix: disable abbreviated commit shas in diff (#3337) 2024-09-16 02:17:19 -07:00
d121e62763 fix: disable diff detection for renames and copies (#3330)
* fix: disable diff detection for renames and copies

* fix format
2024-09-12 04:48:43 -07:00
f4d66f4d5a build(deps-dev): bump typescript from 5.5.4 to 5.6.2 (#3319)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.5.4 to 5.6.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.5.4...v5.6.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 12:49:31 +00:00
488c869d17 build(deps-dev): bump @types/node from 18.19.48 to 18.19.50 (#3320)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.19.48 to 18.19.50.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 12:49:21 +00:00
5354f85616 docs: update readme 2024-09-09 07:01:59 -07:00
8867c4aba1 fix: handle ambiguous argument failure on diff stat (#3312) 2024-09-05 09:23:05 +01:00
13 changed files with 210 additions and 141 deletions

View File

@ -246,26 +246,6 @@ Note that the repository must be checked out on a branch with a remote, it won't
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7
``` ```
<!--
### Create a project card
To create a project card for the pull request, pass the `pull-request-number` step output to [create-or-update-project-card](https://github.com/peter-evans/create-or-update-project-card) action.
```yml
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v7
- name: Create or Update Project Card
if: ${{ steps.cpr.outputs.pull-request-number }}
uses: peter-evans/create-or-update-project-card@v2
with:
project-name: My project
column-name: My column
issue-number: ${{ steps.cpr.outputs.pull-request-number }}
```
-->
### Auto-merge ### Auto-merge
Auto-merge can be enabled on a pull request allowing it to be automatically merged once requirements have been satisfied. Auto-merge can be enabled on a pull request allowing it to be automatically merged once requirements have been satisfied.

View File

@ -250,11 +250,15 @@ describe('create-or-update-branch tests', () => {
expect(branchCommits.length).toEqual(1) expect(branchCommits.length).toEqual(1)
expect(branchCommits[0].subject).toEqual('Test changes') expect(branchCommits[0].subject).toEqual('Test changes')
expect(branchCommits[0].changes.length).toEqual(3) expect(branchCommits[0].changes.length).toEqual(3)
expect(branchCommits[0].changes).toEqual([ expect(branchCommits[0].changes[0].mode).toEqual('100755')
{mode: '100755', path: UNTRACKED_EXE_FILE, status: 'A'}, expect(branchCommits[0].changes[0].path).toEqual(UNTRACKED_EXE_FILE)
{mode: '100644', path: TRACKED_FILE, status: 'M'}, expect(branchCommits[0].changes[0].status).toEqual('A')
{mode: '100644', path: UNTRACKED_FILE, status: 'A'} expect(branchCommits[0].changes[1].mode).toEqual('100644')
]) expect(branchCommits[0].changes[1].path).toEqual(TRACKED_FILE)
expect(branchCommits[0].changes[1].status).toEqual('M')
expect(branchCommits[0].changes[2].mode).toEqual('100644')
expect(branchCommits[0].changes[2].path).toEqual(UNTRACKED_FILE)
expect(branchCommits[0].changes[2].status).toEqual('A')
}) })
it('tests buildBranchCommits with addition and deletion', async () => { it('tests buildBranchCommits with addition and deletion', async () => {
@ -272,11 +276,15 @@ describe('create-or-update-branch tests', () => {
expect(branchCommits.length).toEqual(1) expect(branchCommits.length).toEqual(1)
expect(branchCommits[0].subject).toEqual('Test changes') expect(branchCommits[0].subject).toEqual('Test changes')
expect(branchCommits[0].changes.length).toEqual(3) expect(branchCommits[0].changes.length).toEqual(3)
expect(branchCommits[0].changes).toEqual([ expect(branchCommits[0].changes[0].mode).toEqual('100644')
{mode: '100644', path: TRACKED_FILE, status: 'D'}, expect(branchCommits[0].changes[0].path).toEqual(TRACKED_FILE)
{mode: '100644', path: UNTRACKED_FILE, status: 'A'}, expect(branchCommits[0].changes[0].status).toEqual('D')
{mode: '100644', path: TRACKED_FILE_NEW_PATH, status: 'A'} expect(branchCommits[0].changes[1].mode).toEqual('100644')
]) expect(branchCommits[0].changes[1].path).toEqual(UNTRACKED_FILE)
expect(branchCommits[0].changes[1].status).toEqual('A')
expect(branchCommits[0].changes[2].mode).toEqual('100644')
expect(branchCommits[0].changes[2].path).toEqual(TRACKED_FILE_NEW_PATH)
expect(branchCommits[0].changes[2].status).toEqual('A')
}) })
it('tests buildBranchCommits with multiple commits', async () => { it('tests buildBranchCommits with multiple commits', async () => {
@ -294,10 +302,13 @@ describe('create-or-update-branch tests', () => {
expect(branchCommits[i].subject).toEqual(`Test changes ${i}`) expect(branchCommits[i].subject).toEqual(`Test changes ${i}`)
expect(branchCommits[i].changes.length).toEqual(2) expect(branchCommits[i].changes.length).toEqual(2)
const untrackedFileStatus = i == 0 ? 'A' : 'M' const untrackedFileStatus = i == 0 ? 'A' : 'M'
expect(branchCommits[i].changes).toEqual([
{mode: '100644', path: TRACKED_FILE, status: 'M'}, expect(branchCommits[i].changes[0].mode).toEqual('100644')
{mode: '100644', path: UNTRACKED_FILE, status: untrackedFileStatus} expect(branchCommits[i].changes[0].path).toEqual(TRACKED_FILE)
]) expect(branchCommits[i].changes[0].status).toEqual('M')
expect(branchCommits[i].changes[1].mode).toEqual('100644')
expect(branchCommits[i].changes[1].path).toEqual(UNTRACKED_FILE)
expect(branchCommits[i].changes[1].status).toEqual(untrackedFileStatus)
} }
}) })

View File

@ -19,14 +19,17 @@ git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
cd /git/local/repos/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_TEMP.md
git add . git add .
git commit -m "initial commit" git commit -m "initial commit"
git commit --allow-empty -m "empty commit for tests" git commit --allow-empty -m "empty commit for tests"
echo "#test-base :sparkles:" > README.md echo "#test-base :sparkles:" > README_TEMP.md
git add . git add .
git commit -m "add sparkles" -m "Change description: git commit -m "add sparkles" -m "Change description:
- updates README.md to add sparkles to the title" - updates README_TEMP.md to add sparkles to the title"
mv README_TEMP.md README.md
git add .
git commit -m "rename readme"
git push -u git push -u
git log -1 --pretty=oneline git log -1 --pretty=oneline
git config --global --unset user.email git config --global --unset user.email

View File

@ -11,15 +11,16 @@ describe('git-command-manager integration tests', () => {
}) })
it('tests getCommit', async () => { it('tests getCommit', async () => {
const initialCommit = await git.getCommit('HEAD^^') const initialCommit = await git.getCommit('HEAD^^^')
const emptyCommit = await git.getCommit('HEAD^') const emptyCommit = await git.getCommit('HEAD^^')
const modifiedCommit = await git.getCommit('HEAD^')
const headCommit = await git.getCommit('HEAD') const headCommit = await git.getCommit('HEAD')
expect(initialCommit.subject).toEqual('initial commit') expect(initialCommit.subject).toEqual('initial commit')
expect(initialCommit.signed).toBeFalsy() expect(initialCommit.signed).toBeFalsy()
expect(initialCommit.changes).toEqual([ expect(initialCommit.changes[0].mode).toEqual('100644')
{mode: '100644', status: 'A', path: 'README.md'} expect(initialCommit.changes[0].status).toEqual('A')
]) expect(initialCommit.changes[0].path).toEqual('README_TEMP.md')
expect(emptyCommit.subject).toEqual('empty commit for tests') expect(emptyCommit.subject).toEqual('empty commit for tests')
expect(emptyCommit.tree).toEqual(initialCommit.tree) // empty commits have no tree and reference the parent's expect(emptyCommit.tree).toEqual(initialCommit.tree) // empty commits have no tree and reference the parent's
@ -27,11 +28,21 @@ describe('git-command-manager integration tests', () => {
expect(emptyCommit.signed).toBeFalsy() expect(emptyCommit.signed).toBeFalsy()
expect(emptyCommit.changes).toEqual([]) expect(emptyCommit.changes).toEqual([])
expect(headCommit.subject).toEqual('add sparkles') expect(modifiedCommit.subject).toEqual('add sparkles')
expect(headCommit.parents[0]).toEqual(emptyCommit.sha) expect(modifiedCommit.parents[0]).toEqual(emptyCommit.sha)
expect(modifiedCommit.signed).toBeFalsy()
expect(modifiedCommit.changes[0].mode).toEqual('100644')
expect(modifiedCommit.changes[0].status).toEqual('M')
expect(modifiedCommit.changes[0].path).toEqual('README_TEMP.md')
expect(headCommit.subject).toEqual('rename readme')
expect(headCommit.parents[0]).toEqual(modifiedCommit.sha)
expect(headCommit.signed).toBeFalsy() expect(headCommit.signed).toBeFalsy()
expect(headCommit.changes).toEqual([ expect(headCommit.changes[0].mode).toEqual('100644')
{mode: '100644', status: 'M', path: 'README.md'} expect(headCommit.changes[0].status).toEqual('A')
]) expect(headCommit.changes[0].path).toEqual('README.md')
expect(headCommit.changes[1].mode).toEqual('100644')
expect(headCommit.changes[1].status).toEqual('D')
expect(headCommit.changes[1].path).toEqual('README_TEMP.md')
}) })
}) })

84
dist/index.js vendored
View File

@ -46,6 +46,7 @@ exports.buildBranchCommits = buildBranchCommits;
exports.createOrUpdateBranch = createOrUpdateBranch; exports.createOrUpdateBranch = createOrUpdateBranch;
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const uuid_1 = __nccwpck_require__(5840); const uuid_1 = __nccwpck_require__(5840);
const utils = __importStar(__nccwpck_require__(918));
const CHERRYPICK_EMPTY = 'The previous cherry-pick is now empty, possibly due to conflict resolution.'; 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 NOTHING_TO_COMMIT = 'nothing to commit, working tree clean';
const FETCH_DEPTH_MARGIN = 10; const FETCH_DEPTH_MARGIN = 10;
@ -136,9 +137,19 @@ function isEven(git, branch1, branch2) {
// Return true if the specified number of commits on branch1 and branch2 have a diff // Return true if the specified number of commits on branch1 and branch2 have a diff
function commitsHaveDiff(git, branch1, branch2, depth) { function commitsHaveDiff(git, branch1, branch2, depth) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const diff1 = (yield git.exec(['diff', '--stat', `${branch1}..${branch1}~${depth}`])).stdout.trim(); // Some action use cases lead to the depth being a very large number and the diff fails.
const diff2 = (yield git.exec(['diff', '--stat', `${branch2}..${branch2}~${depth}`])).stdout.trim(); // I've made this check optional for now because it was a fix for an edge case that is
return diff1 !== diff2; // very rare, anyway.
try {
const diff1 = (yield git.exec(['diff', '--stat', `${branch1}..${branch1}~${depth}`])).stdout.trim();
const diff2 = (yield git.exec(['diff', '--stat', `${branch2}..${branch2}~${depth}`])).stdout.trim();
return diff1 !== diff2;
}
catch (error) {
core.info('Failed optional check of commits diff; Skipping.');
core.debug(utils.getErrorMessage(error));
return false;
}
}); });
} }
function splitLines(multilineString) { function splitLines(multilineString) {
@ -734,6 +745,8 @@ class GitCommandManager {
'show', 'show',
'--raw', '--raw',
'--cc', '--cc',
'--no-renames',
'--no-abbrev',
`--format=%H%n%T%n%P%n%G?%n%s%n%b%n${endOfBody}`, `--format=%H%n%T%n%P%n%G?%n%s%n%b%n${endOfBody}`,
ref ref
]); ]);
@ -749,12 +762,13 @@ class GitCommandManager {
subject: detailLines[4], subject: detailLines[4],
body: detailLines.slice(5, endOfBodyIndex).join('\n'), body: detailLines.slice(5, endOfBodyIndex).join('\n'),
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => { changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
const change = line.match(/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/); const change = line.match(/^:(\d{6}) (\d{6}) \w{40} (\w{40}) ([AMD])\s+(.*)$/);
if (change) { if (change) {
return { return {
mode: change[3] === 'D' ? change[1] : change[2], mode: change[4] === 'D' ? change[1] : change[2],
status: change[3], dstSha: change[3],
path: change[4] status: change[4],
path: change[5]
}; };
} }
else { else {
@ -1355,25 +1369,37 @@ class GitHubHelper {
let treeSha = parentCommit.tree; let treeSha = parentCommit.tree;
if (commit.changes.length > 0) { if (commit.changes.length > 0) {
core.info(`Creating tree objects for local commit ${commit.sha}`); core.info(`Creating tree objects for local commit ${commit.sha}`);
const treeObjects = yield Promise.all(commit.changes.map((_a) => __awaiter(this, [_a], void 0, function* ({ path, mode, status }) { const treeObjects = yield Promise.all(commit.changes.map((_a) => __awaiter(this, [_a], void 0, function* ({ path, mode, status, dstSha }) {
let sha = null; if (mode === '160000') {
if (status === 'A' || status === 'M') { // submodule
try { core.info(`Creating tree object for submodule commit at '${path}'`);
const { data: blob } = yield blobCreationLimit(() => this.octokit.rest.git.createBlob(Object.assign(Object.assign({}, repository), { content: utils.readFileBase64([repoPath, path]), encoding: 'base64' }))); return {
sha = blob.sha; path,
} mode,
catch (error) { sha: dstSha,
core.error(`Error creating blob for file '${path}': ${utils.getErrorMessage(error)}`); type: 'commit'
throw error; };
} }
else {
let sha = null;
if (status === 'A' || status === 'M') {
try {
const { data: blob } = yield blobCreationLimit(() => this.octokit.rest.git.createBlob(Object.assign(Object.assign({}, repository), { content: utils.readFileBase64([repoPath, path]), encoding: 'base64' })));
sha = blob.sha;
}
catch (error) {
core.error(`Error creating blob for file '${path}': ${utils.getErrorMessage(error)}`);
throw error;
}
}
core.info(`Creating tree object for blob at '${path}' with status '${status}'`);
return {
path,
mode,
sha,
type: 'blob'
};
} }
core.info(`Created blob for file '${path}'`);
return {
path,
mode,
sha,
type: 'blob'
};
}))); })));
const chunkSize = 100; const chunkSize = 100;
const chunkedTreeObjects = Array.from({ length: Math.ceil(treeObjects.length / chunkSize) }, (_, i) => treeObjects.slice(i * chunkSize, i * chunkSize + chunkSize)); const chunkedTreeObjects = Array.from({ length: Math.ceil(treeObjects.length / chunkSize) }, (_, i) => treeObjects.slice(i * chunkSize, i * chunkSize + chunkSize));
@ -1755,7 +1781,13 @@ function readFile(path) {
return fs.readFileSync(path, 'utf-8'); return fs.readFileSync(path, 'utf-8');
} }
function readFileBase64(pathParts) { function readFileBase64(pathParts) {
return fs.readFileSync(path.resolve(...pathParts)).toString('base64'); const resolvedPath = path.resolve(...pathParts);
if (fs.lstatSync(resolvedPath).isSymbolicLink()) {
return fs
.readlinkSync(resolvedPath, { encoding: 'buffer' })
.toString('base64');
}
return fs.readFileSync(resolvedPath).toString('base64');
} }
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
function hasErrorCode(error) { function hasErrorCode(error) {

View File

@ -303,7 +303,7 @@ GitHub App generated tokens can be configured with fine-grained permissions and
- Uncheck `Active` under `Webhook`. You do not need to enter a `Webhook URL`. - Uncheck `Active` under `Webhook`. You do not need to enter a `Webhook URL`.
- Under `Repository permissions: Contents` select `Access: Read & write`. - Under `Repository permissions: Contents` select `Access: Read & write`.
- Under `Repository permissions: Pull requests` select `Access: Read & write`. - Under `Repository permissions: Pull requests` select `Access: Read & write`.
- Under `Repository permissions: Workflows` select `Access: Read-only`. - Under `Repository permissions: Workflows` select `Access: Read & write`.
- **NOTE**: Only needed if pull requests could contain changes to Actions workflows. - **NOTE**: Only needed if pull requests could contain changes to Actions workflows.
- Under `Organization permissions: Members` select `Access: Read-only`. - Under `Organization permissions: Members` select `Access: Read-only`.
- **NOTE**: Only needed if you would like add teams as reviewers to PRs. - **NOTE**: Only needed if you would like add teams as reviewers to PRs.

50
package-lock.json generated
View File

@ -21,12 +21,12 @@
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.13",
"@types/node": "^18.19.48", "@types/node": "^18.19.50",
"@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0", "@typescript-eslint/parser": "^7.18.0",
"@vercel/ncc": "^0.38.1", "@vercel/ncc": "^0.38.1",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-import-resolver-typescript": "^3.6.3", "eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-github": "^4.10.2", "eslint-plugin-github": "^4.10.2",
"eslint-plugin-import": "^2.30.0", "eslint-plugin-import": "^2.30.0",
@ -38,7 +38,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.5.4" "typescript": "^5.6.2"
} }
}, },
"node_modules/@actions/core": { "node_modules/@actions/core": {
@ -735,9 +735,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.57.0", "version": "8.57.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -758,13 +758,13 @@
"dev": true "dev": true
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
"deprecated": "Use @eslint/config-array instead", "deprecated": "Use @eslint/config-array instead",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@humanwhocodes/object-schema": "^2.0.2", "@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1", "debug": "^4.3.1",
"minimatch": "^3.0.5" "minimatch": "^3.0.5"
}, },
@ -1527,9 +1527,9 @@
} }
}, },
"node_modules/@types/jest": { "node_modules/@types/jest": {
"version": "29.5.12", "version": "29.5.13",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz",
"integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"expect": "^29.0.0", "expect": "^29.0.0",
@ -1560,9 +1560,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.19.48", "version": "18.19.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.48.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz",
"integrity": "sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg==", "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -3501,16 +3501,16 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.57.0", "version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4", "@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.0", "@eslint/js": "8.57.1",
"@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/config-array": "^0.13.0",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0", "@ungap/structured-clone": "^1.2.0",
@ -7756,9 +7756,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.5.4", "version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",

View File

@ -41,12 +41,12 @@
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.13",
"@types/node": "^18.19.48", "@types/node": "^18.19.50",
"@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0", "@typescript-eslint/parser": "^7.18.0",
"@vercel/ncc": "^0.38.1", "@vercel/ncc": "^0.38.1",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-import-resolver-typescript": "^3.6.3", "eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-github": "^4.10.2", "eslint-plugin-github": "^4.10.2",
"eslint-plugin-import": "^2.30.0", "eslint-plugin-import": "^2.30.0",
@ -58,6 +58,6 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.5.4" "typescript": "^5.6.2"
} }
} }

View File

@ -1 +0,0 @@
1725466856

View File

@ -1,6 +1,7 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {GitCommandManager, Commit} from './git-command-manager' import {GitCommandManager, Commit} from './git-command-manager'
import {v4 as uuidv4} from 'uuid' import {v4 as uuidv4} from 'uuid'
import * as utils from './utils'
const CHERRYPICK_EMPTY = const CHERRYPICK_EMPTY =
'The previous cherry-pick is now empty, possibly due to conflict resolution.' 'The previous cherry-pick is now empty, possibly due to conflict resolution.'
@ -131,13 +132,22 @@ async function commitsHaveDiff(
branch2: string, branch2: string,
depth: number depth: number
): Promise<boolean> { ): Promise<boolean> {
const diff1 = ( // Some action use cases lead to the depth being a very large number and the diff fails.
await git.exec(['diff', '--stat', `${branch1}..${branch1}~${depth}`]) // I've made this check optional for now because it was a fix for an edge case that is
).stdout.trim() // very rare, anyway.
const diff2 = ( try {
await git.exec(['diff', '--stat', `${branch2}..${branch2}~${depth}`]) const diff1 = (
).stdout.trim() await git.exec(['diff', '--stat', `${branch1}..${branch1}~${depth}`])
return diff1 !== diff2 ).stdout.trim()
const diff2 = (
await git.exec(['diff', '--stat', `${branch2}..${branch2}~${depth}`])
).stdout.trim()
return diff1 !== diff2
} catch (error) {
core.info('Failed optional check of commits diff; Skipping.')
core.debug(utils.getErrorMessage(error))
return false
}
} }
function splitLines(multilineString: string): string[] { function splitLines(multilineString: string): string[] {

View File

@ -14,6 +14,7 @@ export type Commit = {
body: string body: string
changes: { changes: {
mode: string mode: string
dstSha: string
status: 'A' | 'M' | 'D' status: 'A' | 'M' | 'D'
path: string path: string
}[] }[]
@ -159,6 +160,8 @@ export class GitCommandManager {
'show', 'show',
'--raw', '--raw',
'--cc', '--cc',
'--no-renames',
'--no-abbrev',
`--format=%H%n%T%n%P%n%G?%n%s%n%b%n${endOfBody}`, `--format=%H%n%T%n%P%n%G?%n%s%n%b%n${endOfBody}`,
ref ref
]) ])
@ -176,13 +179,14 @@ export class GitCommandManager {
body: detailLines.slice(5, endOfBodyIndex).join('\n'), body: detailLines.slice(5, endOfBodyIndex).join('\n'),
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => { changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
const change = line.match( const change = line.match(
/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/ /^:(\d{6}) (\d{6}) \w{40} (\w{40}) ([AMD])\s+(.*)$/
) )
if (change) { if (change) {
return { return {
mode: change[3] === 'D' ? change[1] : change[2], mode: change[4] === 'D' ? change[1] : change[2],
status: change[3], dstSha: change[3],
path: change[4] status: change[4],
path: change[5]
} }
} else { } else {
unparsedChanges.push(line) unparsedChanges.push(line)

View File

@ -35,7 +35,7 @@ type TreeObject = {
path: string path: string
mode: '100644' | '100755' | '040000' | '160000' | '120000' mode: '100644' | '100755' | '040000' | '160000' | '120000'
sha: string | null sha: string | null
type: 'blob' type: 'blob' | 'commit'
} }
export class GitHubHelper { export class GitHubHelper {
@ -255,31 +255,44 @@ export class GitHubHelper {
if (commit.changes.length > 0) { if (commit.changes.length > 0) {
core.info(`Creating tree objects for local commit ${commit.sha}`) core.info(`Creating tree objects for local commit ${commit.sha}`)
const treeObjects = await Promise.all( const treeObjects = await Promise.all(
commit.changes.map(async ({path, mode, status}) => { commit.changes.map(async ({path, mode, status, dstSha}) => {
let sha: string | null = null if (mode === '160000') {
if (status === 'A' || status === 'M') { // submodule
try { core.info(`Creating tree object for submodule commit at '${path}'`)
const {data: blob} = await blobCreationLimit(() => return <TreeObject>{
this.octokit.rest.git.createBlob({ path,
...repository, mode,
content: utils.readFileBase64([repoPath, path]), sha: dstSha,
encoding: 'base64' type: 'commit'
}) }
) } else {
sha = blob.sha let sha: string | null = null
} catch (error) { if (status === 'A' || status === 'M') {
core.error( try {
`Error creating blob for file '${path}': ${utils.getErrorMessage(error)}` const {data: blob} = await blobCreationLimit(() =>
) this.octokit.rest.git.createBlob({
throw error ...repository,
content: utils.readFileBase64([repoPath, path]),
encoding: 'base64'
})
)
sha = blob.sha
} catch (error) {
core.error(
`Error creating blob for file '${path}': ${utils.getErrorMessage(error)}`
)
throw error
}
}
core.info(
`Creating tree object for blob at '${path}' with status '${status}'`
)
return <TreeObject>{
path,
mode,
sha,
type: 'blob'
} }
}
core.info(`Created blob for file '${path}'`)
return <TreeObject>{
path,
mode,
sha,
type: 'blob'
} }
}) })
) )

View File

@ -127,7 +127,13 @@ export function readFile(path: string): string {
} }
export function readFileBase64(pathParts: string[]): string { export function readFileBase64(pathParts: string[]): string {
return fs.readFileSync(path.resolve(...pathParts)).toString('base64') const resolvedPath = path.resolve(...pathParts)
if (fs.lstatSync(resolvedPath).isSymbolicLink()) {
return fs
.readlinkSync(resolvedPath, {encoding: 'buffer'})
.toString('base64')
}
return fs.readFileSync(resolvedPath).toString('base64')
} }
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */