Compare commits

...
24 Commits
Author SHA1 Message Date
dsh-mulm fe3085894a docs: mirror README (guard rules + sync procedure + incident record) 2026-08-26 17:21:24 +08:00
dsh-mulm f7e934116a chore: remove upstream CI workflows (must never run on Gitea; Actions disabled on this mirror) 2026-08-26 17:05:17 +08:00
Aiqiao Yan f548e57e54 Consolidate dependency updates in CHANGELOG (#2537)
* Consolidate dependency updates in CHANGELOG

Removed specific dependency updates and consolidated them into a single entry.

* Fix capitalization in v7.0.1 changelog entries

Updated changelog entries for v7.0.1 to use consistent capitalization.
2026-07-20 12:20:47 -04:00
Aiqiao YanandCopilot Autofix powered by AI 3d3c42e5aa prep v7.0.1 release (#2531)
* prep v7.0.1 release

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-17 14:45:11 -04:00
Aiqiao Yan 28802689a1 escape values passed to --unset (#2530) 2026-07-17 13:44:40 -04:00
Aiqiao Yan 12cd2235ef trim only ascii whitespace for branch (#2521)
* trim only ascii whitespace for branch

* rebuild
2026-07-15 15:05:42 -04:00
Aiqiao Yan 62661c4e71 skip running unsafe pr check if input is default (#2518) 2026-07-15 10:02:03 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e8d4307400 Bump the minor-actions-dependencies group with 2 updates (#2499)
Bumps the minor-actions-dependencies group with 2 updates: [docker/login-action](https://github.com/docker/login-action) and [docker/build-push-action](https://github.com/docker/build-push-action).


Updates `docker/login-action` from 4.2.0 to 4.4.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v4.2.0...v4.4.0)

Updates `docker/build-push-action` from 7.2.0 to 7.3.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-actions-dependencies
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-07 16:34:44 -04:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Aiqiao Yan
631c942040 eslint 9 (#2474)
* Bump eslint-plugin-github from 4.10.2 to 6.0.0

Bumps [eslint-plugin-github](https://github.com/github/eslint-plugin-github) from 4.10.2 to 6.0.0.
- [Release notes](https://github.com/github/eslint-plugin-github/releases)
- [Commits](https://github.com/github/eslint-plugin-github/compare/v4.10.2...v6.0.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-github
  dependency-version: 6.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* eslint 9 upgrade

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aiqiao Yan <55104035+aiqiaoy@users.noreply.github.com>
2026-07-07 16:26:50 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4f1f4aec02 Bump actions/upload-artifact from 4 to 7 (#2476)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 10:34:21 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ba097532fb Bump actions/checkout from 6 to 7 (#2488)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 10:33:37 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b9e0990d21 Bump docker/login-action from 3.3.0 to 4.2.0 (#2479)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.3.0 to 4.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.3.0...v4.2.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 10:13:43 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e8cb398be4 Bump docker/build-push-action from 6.5.0 to 7.2.0 (#2478)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.5.0 to 7.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.5.0...v7.2.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 10:12:50 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 5de26ee9b1 Bump actions/setup-node from 4 to 6 (#2477)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 10:12:03 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 79102f2503 Bump github/codeql-action from 3 to 4 (#2475)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 10:11:28 -04:00
Aiqiao Yan 9c091bb21b update error wording (#2467) 2026-06-17 13:51:53 -04:00
Aiqiao YanandCopilot Autofix powered by AI 1044a6dea9 getting ready for checkout v7 release (#2464)
* getting ready for checkout v7 release

* update changelog wording

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-17 09:59:35 -04:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Aiqiao Yan
f0282184c7 Bump the minor-npm-dependencies group across 1 directory with 3 updates (#2462)
* Bump the minor-npm-dependencies group across 1 directory with 3 updates

Bumps the minor-npm-dependencies group with 3 updates in the / directory: [@vercel/ncc](https://github.com/vercel/ncc), [prettier](https://github.com/prettier/prettier) and [ts-jest](https://github.com/kulshekhar/ts-jest).


Updates `@vercel/ncc` from 0.38.4 to 0.44.0
- [Release notes](https://github.com/vercel/ncc/releases)
- [Commits](https://github.com/vercel/ncc/compare/0.38.4...0.44.0)

Updates `prettier` from 3.3.3 to 3.8.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.3.3...3.8.4)

Updates `ts-jest` from 29.2.5 to 29.4.11
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.2.5...v29.4.11)

---
updated-dependencies:
- dependency-name: "@vercel/ncc"
  dependency-version: 0.44.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-npm-dependencies
- dependency-name: prettier
  dependency-version: 3.8.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-npm-dependencies
- dependency-name: ts-jest
  dependency-version: 29.4.11
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* run prettier

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aiqiao Yan <55104035+aiqiaoy@users.noreply.github.com>
2026-06-16 17:30:00 -04:00
Aiqiao Yan d914b262ff upgrade module to esm and update dependencies (#2463)
* upgrade module to esm so I can update dependencies

* fix ci failures
2026-06-16 17:10:58 -04:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Aiqiao Yan
537c7ef99c Bump @actions/core and @actions/tool-cache and Remove uuid (#2459)
* Bump uuid, @actions/core and @actions/tool-cache

Bumps [uuid](https://github.com/uuidjs/uuid), [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) and [@actions/tool-cache](https://github.com/actions/toolkit/tree/HEAD/packages/tool-cache). These dependencies needed to be updated together.

Updates `uuid` from 9.0.1 to 14.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.1...v14.0.0)

Updates `@actions/core` from 1.10.1 to 1.11.1
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

Updates `@actions/tool-cache` from 2.0.1 to 2.0.2
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/tool-cache/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/tool-cache)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: direct:production
- dependency-name: "@actions/core"
  dependency-version: 1.11.1
  dependency-type: direct:production
- dependency-name: "@actions/tool-cache"
  dependency-version: 2.0.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* switch to use crpto.randomUUID

* update license

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aiqiao Yan <55104035+aiqiaoy@users.noreply.github.com>
2026-06-16 14:28:24 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 130a169078 Bump js-yaml from 4.1.0 to 4.2.0 (#2461)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/commits)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.2.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 12:44:07 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7d09575332 Bump flatted from 3.3.1 to 3.4.2 (#2460)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.1 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.1...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 12:43:32 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 0f9f3aa320 Bump actions/publish-immutable-action (#2458)
Bumps the minor-actions-dependencies group with 1 update in the / directory: [actions/publish-immutable-action](https://github.com/actions/publish-immutable-action).


Updates `actions/publish-immutable-action` from 0.0.3 to 0.0.4
- [Release notes](https://github.com/actions/publish-immutable-action/releases)
- [Commits](https://github.com/actions/publish-immutable-action/compare/0.0.3...v0.0.4)

---
updated-dependencies:
- dependency-name: actions/publish-immutable-action
  dependency-version: 0.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 12:33:25 -04:00
Aiqiao Yan f9e715a95f block checking out fork pr for pull_request_target and workflow_run (#2454)
* block checking out fork pr for some events

* address copilot and reviewer feedback

* run prettier formatting

* build

* update urls

* update readme

* update description and url again

* edit url one more time
2026-06-16 10:03:43 -04:00
74 changed files with 31848 additions and 27514 deletions
-3
View File
@@ -1,3 +0,0 @@
dist/
lib/
node_modules/
-53
View File
@@ -1,53 +0,0 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"camelcase": "off",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error"
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}
-51
View File
@@ -1,51 +0,0 @@
# `dist/index.js` is a special file in Actions.
# When you reference an action with `uses:` in a workflow,
# `index.js` is the code that will run.
# For our project, we generate this file through a build process
# from other source files.
# We need to make sure the checked-in `index.js` actually matches what we expect it to be.
name: Check dist
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
workflow_dispatch:
jobs:
check-dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set Node.js 24.x
uses: actions/setup-node@v4
with:
node-version: 24.x
- name: Install dependencies
run: npm ci
- name: Rebuild the index.js file
run: npm run build
- name: Compare the expected and actual dist/ directories
run: |
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi
# If dist/ was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v4
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
path: dist/
-58
View File
@@ -1,58 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '28 9 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- run: npm ci
- run: npm run build
- run: rm -rf dist # We want code scanning to analyze lib instead (individual .js files)
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
-14
View File
@@ -1,14 +0,0 @@
name: Licensed
on:
push: {branches: main}
pull_request: {branches: main}
jobs:
test:
runs-on: ubuntu-latest
name: Check licenses
steps:
- uses: actions/checkout@v6
- run: npm ci
- run: npm run licensed-check
@@ -1,20 +0,0 @@
name: 'Publish Immutable Action Version'
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- name: Checking out
uses: actions/checkout@v6
- name: Publish
id: publish
uses: actions/publish-immutable-action@0.0.3
-357
View File
@@ -1,357 +0,0 @@
name: Build and Test
on:
pull_request:
push:
branches:
- main
- releases/*
# Note that when you see patterns like "ref: test-data/v2/basic" within this workflow,
# these refer to "test-data" branches on this actions/checkout repo.
# (For example, test-data/v2/basic -> https://github.com/actions/checkout/tree/test-data/v2/basic)
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: 24.x
- uses: actions/checkout@v6
- run: npm ci
- run: npm run build
- run: npm run format-check
- run: npm run lint
- run: npm test
- name: Verify no unstaged changes
run: __test__/verify-no-unstaged-changes.sh
test:
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.runs-on }}
steps:
# Clone this repo
- name: Checkout
uses: actions/checkout@v6
# Basic checkout
- name: Checkout basic
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify basic
shell: bash
run: __test__/verify-basic.sh
# Clean
- name: Modify work tree
shell: bash
run: __test__/modify-work-tree.sh
- name: Checkout clean
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify clean
shell: bash
run: __test__/verify-clean.sh
# Side by side
- name: Checkout side by side 1
uses: ./
with:
ref: test-data/v2/side-by-side-1
path: side-by-side-1
- name: Checkout side by side 2
uses: ./
with:
ref: test-data/v2/side-by-side-2
path: side-by-side-2
- name: Verify side by side
shell: bash
run: __test__/verify-side-by-side.sh
# Filter
- name: Fetch filter
uses: ./
with:
filter: 'blob:none'
path: fetch-filter
- name: Verify fetch filter
run: __test__/verify-fetch-filter.sh
# Fetch tags
- name: Checkout with fetch-tags
uses: ./
with:
ref: test-data/v2/basic
path: fetch-tags-test
fetch-tags: true
- name: Verify fetch-tags
shell: bash
run: __test__/verify-fetch-tags.sh
# Sparse checkout
- name: Sparse checkout
uses: ./
with:
sparse-checkout: |
__test__
.github
dist
path: sparse-checkout
- name: Verify sparse checkout
run: __test__/verify-sparse-checkout.sh
# Disabled sparse checkout in existing checkout
- name: Disabled sparse checkout
uses: ./
with:
path: sparse-checkout
- name: Verify disabled sparse checkout
shell: bash
run: set -x && ls -l sparse-checkout/src/git-command-manager.ts
# Sparse checkout (non-cone mode)
- name: Sparse checkout (non-cone mode)
uses: ./
with:
sparse-checkout: |
/__test__/
/.github/
/dist/
sparse-checkout-cone-mode: false
path: sparse-checkout-non-cone-mode
- name: Verify sparse checkout (non-cone mode)
run: __test__/verify-sparse-checkout-non-cone-mode.sh
# LFS
- name: Checkout LFS
uses: ./
with:
repository: actions/checkout # hardcoded, otherwise doesn't work from a fork
ref: test-data/v2/lfs
path: lfs
lfs: true
- name: Verify LFS
shell: bash
run: __test__/verify-lfs.sh
# Submodules false
- name: Checkout submodules false
uses: ./
with:
ref: test-data/v2/submodule-ssh-url
path: submodules-false
- name: Verify submodules false
run: __test__/verify-submodules-false.sh
# Submodules one level
- name: Checkout submodules true
uses: ./
with:
ref: test-data/v2/submodule-ssh-url
path: submodules-true
submodules: true
- name: Verify submodules true
run: __test__/verify-submodules-true.sh
# Submodules recursive
- name: Checkout submodules recursive
uses: ./
with:
ref: test-data/v2/submodule-ssh-url
path: submodules-recursive
submodules: recursive
- name: Verify submodules recursive
run: __test__/verify-submodules-recursive.sh
# Worktree credentials
- name: Checkout for worktree test
uses: ./
with:
path: worktree-test
- name: Verify worktree credentials
shell: bash
run: __test__/verify-worktree.sh worktree-test worktree-branch
# Worktree credentials in container step
- name: Verify worktree credentials in container step
if: runner.os == 'Linux'
uses: docker://bitnami/git:latest
with:
args: bash __test__/verify-worktree.sh worktree-test container-worktree-branch
# Basic checkout using REST API
- name: Remove basic
if: runner.os != 'windows'
run: rm -rf basic
- name: Remove basic (Windows)
if: runner.os == 'windows'
shell: cmd
run: rmdir /s /q basic
- name: Override git version
if: runner.os != 'windows'
run: __test__/override-git-version.sh
- name: Override git version (Windows)
if: runner.os == 'windows'
run: __test__\\override-git-version.cmd
- name: Checkout basic using REST API
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify basic
run: __test__/verify-basic.sh --archive
test-proxy:
runs-on: ubuntu-latest
container:
image: ghcr.io/actions/test-ubuntu-git:main.20240221.114913.703z
options: --dns 127.0.0.1
services:
squid-proxy:
image: ubuntu/squid:latest
ports:
- 3128:3128
env:
https_proxy: http://squid-proxy:3128
steps:
# Clone this repo
- name: Checkout
uses: actions/checkout@v6
# Basic checkout using git
- name: Checkout basic
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify basic
run: __test__/verify-basic.sh
# Basic checkout using REST API
- name: Remove basic
run: rm -rf basic
- name: Override git version
run: __test__/override-git-version.sh
- name: Basic checkout using REST API
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify basic
run: __test__/verify-basic.sh --archive
test-bypass-proxy:
runs-on: ubuntu-latest
env:
https_proxy: http://no-such-proxy:3128
no_proxy: api.github.com,github.com
steps:
# Clone this repo
- name: Checkout
uses: actions/checkout@v6
# Basic checkout using git
- name: Checkout basic
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify basic
run: __test__/verify-basic.sh
- name: Remove basic
run: rm -rf basic
# Basic checkout using REST API
- name: Override git version
run: __test__/override-git-version.sh
- name: Checkout basic using REST API
uses: ./
with:
ref: test-data/v2/basic
path: basic
- name: Verify basic
run: __test__/verify-basic.sh --archive
test-git-container:
runs-on: ubuntu-latest
container: bitnami/git:latest
steps:
# Clone this repo
- name: Checkout
uses: actions/checkout@v6
with:
path: localClone
# Basic checkout using git
- name: Checkout basic
uses: ./localClone
with:
ref: test-data/v2/basic
- name: Verify basic
run: |
if [ ! -f "./basic-file.txt" ]; then
echo "Expected basic file does not exist"
exit 1
fi
# Verify .git folder
if [ ! -d "./.git" ]; then
echo "Expected ./.git folder to exist"
exit 1
fi
# Verify auth token
git config --global --add safe.directory "*"
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
# needed to make checkout post cleanup succeed
- name: Fix Checkout v6
uses: actions/checkout@v6
with:
path: localClone
test-output:
runs-on: ubuntu-latest
steps:
# Clone this repo
- name: Checkout
uses: actions/checkout@v6
with:
path: actions-checkout
# Basic checkout using git
- name: Checkout basic
id: checkout
uses: ./actions-checkout
with:
path: cloned-using-local-action
ref: test-data/v2/basic
# Verify output
- name: Verify output
run: |
echo "Commit: ${{ steps.checkout.outputs.commit }}"
echo "Ref: ${{ steps.checkout.outputs.ref }}"
if [ "${{ steps.checkout.outputs.ref }}" != "test-data/v2/basic" ]; then
echo "Expected ref to be test-data/v2/basic"
exit 1
fi
if [ "${{ steps.checkout.outputs.commit }}" != "82f71901cf8c021332310dcc8cdba84c4193ff5d" ]; then
echo "Expected commit to be 82f71901cf8c021332310dcc8cdba84c4193ff5d"
exit 1
fi
-36
View File
@@ -1,36 +0,0 @@
name: Update Main Version
run-name: Move ${{ github.event.inputs.major_version }} to ${{ github.event.inputs.target }}
on:
workflow_dispatch:
inputs:
target:
description: The tag or reference to use
required: true
major_version:
type: choice
description: The major version to update
options:
- v5
- v4
- v3
- v2
jobs:
tag:
runs-on: ubuntu-latest
steps:
# Note this update workflow can also be used as a rollback tool.
# For that reason, it's best to pin `actions/checkout` to a known, stable version
# (typically, about two releases back).
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Git config
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Tag new target
run: git tag -f ${{ github.event.inputs.major_version }} ${{ github.event.inputs.target }}
- name: Push new tag
run: git push origin ${{ github.event.inputs.major_version }} --force
@@ -1,59 +0,0 @@
name: Publish test-ubuntu-git Container
on:
# Use an on demand workflow trigger.
# (Forked copies of actions/checkout won't have permission to update GHCR.io/actions,
# so avoid trigger events that run automatically.)
workflow_dispatch:
inputs:
publish:
description: 'Publish to ghcr.io? (main branch only)'
type: boolean
required: true
default: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: actions/test-ubuntu-git
jobs:
build-and-push-image:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
# Use `docker/login-action` to log in to GHCR.io.
# Once published, the packages are scoped to the account defined here.
- name: Log in to the ghcr.io container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Format Timestamp
id: timestamp
# Use `date` with a custom format to achieve the key=value format GITHUB_OUTPUT expects.
run: date -u "+now=%Y%m%d.%H%M%S.%3NZ" >> "$GITHUB_OUTPUT"
- name: Issue Image Publish Warning
if: ${{ inputs.publish && github.ref_name != 'main' }}
run: echo "::warning::test-ubuntu-git images can only be published from the actions/checkout 'main' branch. Workflow will continue with push/publish disabled."
# Use `docker/build-push-action` to build (and optionally publish) the image.
- name: Build Docker Image (with optional Push)
uses: docker/build-push-action@v6.5.0
with:
context: .
file: images/test-ubuntu-git.Dockerfile
# For now, attempts to push to ghcr.io must target the `main` branch.
# In the future, consider also allowing attempts from `releases/*` branches.
push: ${{ inputs.publish && github.ref_name == 'main' }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}.${{ steps.timestamp.outputs.now }}
+2 -1
View File
@@ -11,4 +11,5 @@ allowed:
- unlicense
reviewed:
npm:
npm:
- "@actions/http-client" # MIT
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/core"
version: 1.10.1
version: 3.0.1
type: npm
summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/exec"
version: 1.1.1
version: 3.0.0
type: npm
summary: Actions exec lib
homepage: https://github.com/actions/toolkit/tree/main/packages/exec
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/github"
version: 6.0.0
version: 9.1.1
type: npm
summary: Actions github lib
homepage: https://github.com/actions/toolkit/tree/main/packages/github
@@ -1,10 +1,10 @@
---
name: "@actions/http-client"
version: 2.2.1
version: 3.0.2
type: npm
summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
license: mit
license: other
licenses:
- sources: LICENSE
text: |
+32
View File
@@ -0,0 +1,32 @@
---
name: "@actions/http-client"
version: 4.0.1
type: npm
summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
license: other
licenses:
- sources: LICENSE
text: |
Actions Http Client for Node.js
Copyright (c) GitHub, Inc.
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/io"
version: 1.1.3
version: 3.0.2
type: npm
summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/tool-cache"
version: 2.0.1
version: 4.0.0
type: npm
summary: Actions tool-cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/tool-cache
-30
View File
@@ -1,30 +0,0 @@
---
name: "@fastify/busboy"
version: 2.1.1
type: npm
summary: A streaming parser for HTML form data for node.js
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
notices: []
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@octokit/auth-token"
version: 4.0.0
version: 6.0.0
type: npm
summary: GitHub API token authentication for browsers and Node.js
homepage:
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@octokit/core"
version: 5.2.0
version: 7.0.6
type: npm
summary: Extendable client for GitHub's REST & GraphQL APIs
homepage:
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@octokit/endpoint"
version: 9.0.6
version: 11.0.3
type: npm
summary: Turns REST API endpoints into generic request options
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@octokit/graphql"
version: 7.1.0
version: 9.0.3
type: npm
summary: GitHub GraphQL API client for browsers and Node
homepage:
-20
View File
@@ -1,20 +0,0 @@
---
name: "@octokit/openapi-types"
version: 20.0.0
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright 2020 Gregor Martynus
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
@@ -1,14 +1,14 @@
---
name: "@octokit/openapi-types"
version: 22.1.0
version: 27.0.0
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright 2020 Gregor Martynus
text: |
Copyright (c) GitHub 2025 - Licensed as MIT.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@octokit/plugin-paginate-rest"
version: 9.2.2
version: 14.0.0
type: npm
summary: Octokit plugin to paginate REST API endpoint responses
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@octokit/plugin-rest-endpoint-methods"
version: 10.4.1
version: 17.0.0
type: npm
summary: Octokit plugin adding one method for all of api.github.com REST API endpoints
homepage:
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@octokit/request-error"
version: 5.1.1
version: 7.1.0
type: npm
summary: Error class for Octokit request errors
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+2 -2
View File
@@ -1,10 +1,10 @@
---
name: "@octokit/request"
version: 8.4.1
version: 10.0.10
type: npm
summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers
and Node
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
-20
View File
@@ -1,20 +0,0 @@
---
name: "@octokit/types"
version: 12.6.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
@@ -1,6 +1,6 @@
---
name: "@octokit/types"
version: 13.4.1
version: 16.0.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: before-after-hook
version: 2.2.3
version: 4.0.0
type: npm
summary: asynchronous before/error/after hooks for internal functionality
homepage:
+47
View File
@@ -0,0 +1,47 @@
---
name: content-type
version: 2.0.0
type: npm
summary: Create and parse HTTP Content-Type header
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
(The MIT License)
Copyright (c) 2015 Douglas Christopher Wilson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: |-
[MIT](LICENSE)
[npm-image]: https://img.shields.io/npm/v/content-type
[npm-url]: https://npmjs.org/package/content-type
[downloads-image]: https://img.shields.io/npm/dm/content-type
[downloads-url]: https://npmjs.org/package/content-type
[build-image]: https://img.shields.io/github/actions/workflow/status/jshttp/content-type/ci.yml?branch=master
[build-url]: https://github.com/jshttp/content-type/actions/workflows/ci.yml?query=branch%3Amaster
[coverage-image]: https://img.shields.io/codecov/c/gh/jshttp/content-type
[coverage-url]: https://codecov.io/gh/jshttp/content-type
[license-image]: http://img.shields.io/npm/l/content-type.svg?style=flat
[license-url]: LICENSE
notices: []
-28
View File
@@ -1,28 +0,0 @@
---
name: deprecation
version: 2.3.1
type: npm
summary: Log a deprecation message with stack
homepage: https://github.com/gr2m/deprecation#readme
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Gregor Martynus and contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- sources: README.md
text: "[ISC](LICENSE)"
notices: []
@@ -1,16 +1,17 @@
---
name: uuid
version: 3.4.0
name: json-with-bigint
version: 3.5.8
type: npm
summary: RFC4122 (v1, v4, and v5) UUIDs
summary: JS library that allows you to easily serialize and deserialize data with
BigInt values
homepage:
license: mit
licenses:
- sources: LICENSE.md
- sources: LICENSE
text: |
The MIT License (MIT)
MIT License
Copyright (c) 2010-2016 Robert Kieffer and other contributors
Copyright (c) 2023 Ivan Korolenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -29,11 +30,4 @@ licenses:
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices:
- sources: AUTHORS
text: |-
Robert Kieffer <robert@broofa.com>
Christoph Tavan <dev@tavan.de>
AJ ONeal <coolaj86@gmail.com>
Vincent Voyer <vincent@zeroload.net>
Roman Shtylman <shtylman@gmail.com>
notices: []
-26
View File
@@ -1,26 +0,0 @@
---
name: once
version: 1.4.0
type: npm
summary: Run a function exactly one time
homepage: https://github.com/isaacs/once#readme
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: semver
version: 6.3.1
version: 7.8.4
type: npm
summary: The semantic version parser used by npm.
homepage:
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: undici
version: 5.29.0
version: 6.27.0
type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org
+3 -3
View File
@@ -1,8 +1,8 @@
---
name: universal-user-agent
version: 6.0.1
version: 7.0.3
type: npm
summary: Get a user agent string in both browser and node
summary: Get a user agent string across all JavaScript Runtime Environments
homepage:
license: isc
licenses:
@@ -10,7 +10,7 @@ licenses:
text: |
# [ISC License](https://spdx.org/licenses/ISC)
Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m)
Copyright (c) 2018-2021, Gregor Martynus (https://github.com/gr2m)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
-20
View File
@@ -1,20 +0,0 @@
---
name: uuid
version: 8.3.2
type: npm
summary: RFC4122 (v1, v4, and v5) UUIDs
homepage: https://github.com/uuidjs/uuid#readme
license: mit
licenses:
- sources: LICENSE.md
text: |
The MIT License (MIT)
Copyright (c) 2010-2020 Robert Kieffer and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []
-20
View File
@@ -1,20 +0,0 @@
---
name: uuid
version: 9.0.1
type: npm
summary: RFC4122 (v1, v4, and v5) UUIDs
homepage:
license: mit
licenses:
- sources: LICENSE.md
text: |
The MIT License (MIT)
Copyright (c) 2010-2020 Robert Kieffer and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []
-26
View File
@@ -1,26 +0,0 @@
---
name: wrappy
version: 1.0.2
type: npm
summary: Callback wrapping utility
homepage: https://github.com/npm/wrappy
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []
+10
View File
@@ -1,5 +1,15 @@
# Changelog
## v7.0.1
* Skip running unsafe pr check if input is default by @aiqiaoy in https://github.com/actions/checkout/pull/2518
* Trim only ascii whitespace for branch by @aiqiaoy in https://github.com/actions/checkout/pull/2521
* Escape values passed to --unset by @aiqiaoy in https://github.com/actions/checkout/pull/2530
* Various dependency updates
## v7.0.0
* Block checking out fork PR for pull_request_target and workflow_run by @aiqiaoy in https://github.com/actions/checkout/pull/2454
* Various dependency updates
## v6.0.3
* Fix checkout init for SHA-256 repositories by @yaananth in https://github.com/actions/checkout/pull/2439
* fix: expand merge commit SHA regex and add SHA-256 test cases by @yaananth in https://github.com/actions/checkout/pull/2414
+29 -363
View File
@@ -1,371 +1,37 @@
[![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml)
# actions-checkoutactions 本地镜像(mu-ref
# Checkout v6
- 上游: https://github.com/actions/checkout GitHub 公开仓库的只读镜像)
- 引用方式(workflow 内绝对 URL:
```yaml
uses: https://git.moneywood.site/mu-ref/actions-checkout@v4
```
- 标签: 上游全部 tag(v4 为流动标签,随上游发布移动)
## What's new
## 防护规则(每次同步后必须复核,违反其一即回归 2026-08-26 调度事故)
- Improved credential security: `persist-credentials` now stores credentials in a separate file under `$RUNNER_TEMP` instead of directly in `.git/config`
- No workflow changes required — `git fetch`, `git push`, etc. continue to work automatically
- Running authenticated git commands from a [Docker container action](https://docs.github.com/actions/sharing-automations/creating-actions/creating-a-docker-container-action) requires Actions Runner [v2.329.0](https://github.com/actions/runner/releases/tag/v2.329.0) or later
1. 本仓 Actions 保持禁用(repo 设置 has_actions=false;事故证明它挡不住已登记的 schedule,必须配合第 2 条)
2. 仓内不得存在任何 workflow 文件(.github/workflows/ 已剥离;Gitea 1.24.7 不会随文件删除清理 schedule 登记)
# Checkout v5
## 同步步骤(更新上游时执行)
## What's new
- Updated to the node24 runtime
- This requires a minimum Actions Runner version of [v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) to run.
# Checkout v4
This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it.
Only a single commit is fetched by default, for the ref/SHA that triggered the workflow. Set `fetch-depth: 0` to fetch all history for all branches and tags. Refer [here](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events.
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out.
When Git 2.18 or higher is not in your PATH, falls back to the REST API to download the files.
### Note
Thank you for your interest in this GitHub action, however, right now we are not taking contributions.
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features were working on and what stage theyre in.
We are taking the following steps to better direct requests related to GitHub Actions, including:
1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)
2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.
3. Security Issues should be handled as per our [security.md](security.md)
We will still provide security updates for this project and fix major breaking changes during this time.
You are welcome to still raise bugs in this repo.
# What's new
Please refer to the [release page](https://github.com/actions/checkout/releases/latest) for the latest release notes.
# Usage
<!-- start usage -->
```yaml
- uses: actions/checkout@v6
with:
# Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }}
repository: ''
# The branch, tag or SHA to checkout. When checking out the repository that
# triggered a workflow, this defaults to the reference or SHA for that event.
# Otherwise, uses the default branch.
ref: ''
# Personal access token (PAT) used to fetch the repository. The PAT is configured
# with the local git config, which enables your scripts to run authenticated git
# commands. The post-job step removes the PAT.
#
# We recommend using a service account with the least permissions necessary. Also
# when generating a new PAT, select the least scopes necessary.
#
# [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
#
# Default: ${{ github.token }}
token: ''
# SSH key used to fetch the repository. The SSH key is configured with the local
# git config, which enables your scripts to run authenticated git commands. The
# post-job step removes the SSH key.
#
# We recommend using a service account with the least permissions necessary.
#
# [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
ssh-key: ''
# Known hosts in addition to the user and global host key database. The public SSH
# keys for a host may be obtained using the utility `ssh-keyscan`. For example,
# `ssh-keyscan github.com`. The public key for github.com is always implicitly
# added.
ssh-known-hosts: ''
# Whether to perform strict host key checking. When true, adds the options
# `StrictHostKeyChecking=yes` and `CheckHostIP=no` to the SSH command line. Use
# the input `ssh-known-hosts` to configure additional hosts.
# Default: true
ssh-strict: ''
# The user to use when connecting to the remote SSH host. By default 'git' is
# used.
# Default: git
ssh-user: ''
# Whether to configure the token or SSH key with the local git config
# Default: true
persist-credentials: ''
# Relative path under $GITHUB_WORKSPACE to place the repository
path: ''
# Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching
# Default: true
clean: ''
# Partially clone against a given filter. Overrides sparse-checkout if set.
# Default: null
filter: ''
# Do a sparse checkout on given patterns. Each pattern should be separated with
# new lines.
# Default: null
sparse-checkout: ''
# Specifies whether to use cone-mode when doing a sparse checkout.
# Default: true
sparse-checkout-cone-mode: ''
# Number of commits to fetch. 0 indicates all history for all branches and tags.
# Default: 1
fetch-depth: ''
# Whether to fetch tags, even if fetch-depth > 0.
# Default: false
fetch-tags: ''
# Whether to show progress status output when fetching.
# Default: true
show-progress: ''
# Whether to download Git-LFS files
# Default: false
lfs: ''
# Whether to checkout submodules: `true` to checkout submodules or `recursive` to
# recursively checkout submodules.
#
# When the `ssh-key` input is not provided, SSH URLs beginning with
# `git@github.com:` are converted to HTTPS.
#
# Default: false
submodules: ''
# Add repository path as safe.directory for Git global config by running `git
# config --global --add safe.directory <path>`
# Default: true
set-safe-directory: ''
# The base URL for the GitHub instance that you are trying to clone from, will use
# environment defaults to fetch from the same instance that the workflow is
# running from unless specified. Example URLs are https://github.com or
# https://my-ghes-server.example.com
github-server-url: ''
```
<!-- end usage -->
# Scenarios
- [Checkout V5](#checkout-v5)
- [What's new](#whats-new)
- [Checkout V4](#checkout-v4)
- [Note](#note)
- [What's new](#whats-new-1)
- [Usage](#usage)
- [Scenarios](#scenarios)
- [Fetch only the root files](#fetch-only-the-root-files)
- [Fetch only the root files and `.github` and `src` folder](#fetch-only-the-root-files-and-github-and-src-folder)
- [Fetch only a single file](#fetch-only-a-single-file)
- [Fetch all history for all tags and branches](#fetch-all-history-for-all-tags-and-branches)
- [Checkout a different branch](#checkout-a-different-branch)
- [Checkout HEAD^](#checkout-head)
- [Checkout multiple repos (side by side)](#checkout-multiple-repos-side-by-side)
- [Checkout multiple repos (nested)](#checkout-multiple-repos-nested)
- [Checkout multiple repos (private)](#checkout-multiple-repos-private)
- [Checkout pull request HEAD commit instead of merge commit](#checkout-pull-request-head-commit-instead-of-merge-commit)
- [Checkout pull request on closed event](#checkout-pull-request-on-closed-event)
- [Push a commit using the built-in token](#push-a-commit-using-the-built-in-token)
- [Push a commit to a PR using the built-in token](#push-a-commit-to-a-pr-using-the-built-in-token)
- [Recommended permissions](#recommended-permissions)
- [License](#license)
## Fetch only the root files
```yaml
- uses: actions/checkout@v6
with:
sparse-checkout: .
```bash
REPO=actions-checkout
SRC=https://github.com/actions/checkout.git
git clone --mirror "$SRC" /tmp/mirror-$REPO.git
# 1) 剥掉 GitHub 的 PR 引用(Gitea hook 拒绝 refs/pull/*
git -C /tmp/mirror-$REPO.git for-each-ref --format='delete %(refname)' refs/pull | git -C /tmp/mirror-$REPO.git update-ref --stdin
# 2) 剥掉 .github/workflows(上游 CI 不得在 Gitea 执行)
rm -rf /tmp/wt-$REPO && git clone -q /tmp/mirror-$REPO.git /tmp/wt-$REPO
git -C /tmp/wt-$REPO checkout -q main
git -C /tmp/wt-$REPO rm -r -q .github/workflows
git -C /tmp/wt-$REPO -c user.name=dsh-mulm -c user.email=dsh-mulm@est.local commit -q -m 'chore: strip upstream CI workflows'
git -C /tmp/wt-$REPO push -q gitea-dsh:mu-ref/$REPO.git main --tags
# 3) 复核防护规则(缺一不可)
curl -s -H "Authorization: token $GITEA_TOKEN" http://127.0.0.1:3300/api/v1/repos/mu-ref/$REPO | jq '{has_actions}'
git ls-tree gitea-dsh:mu-ref/$REPO.git main --name-only | grep -q '.github' && echo 'FAIL: workflow dir present' || echo OK
git ls-remote gitea-dsh:mu-ref/$REPO.git refs/tags/v4
```
## Fetch only the root files and `.github` and `src` folder
## 事故记录
```yaml
- uses: actions/checkout@v6
with:
sparse-checkout: |
.github
src
```
## Fetch only a single file
```yaml
- uses: actions/checkout@v6
with:
sparse-checkout: |
README.md
sparse-checkout-cone-mode: false
```
## Fetch all history for all tags and branches
```yaml
- uses: actions/checkout@v6
with:
fetch-depth: 0
```
## Checkout a different branch
```yaml
- uses: actions/checkout@v6
with:
ref: my-branch
```
## Checkout HEAD^
```yaml
- uses: actions/checkout@v6
with:
fetch-depth: 2
- run: git checkout HEAD^
```
## Checkout multiple repos (side by side)
```yaml
- name: Checkout
uses: actions/checkout@v6
with:
path: main
- name: Checkout tools repo
uses: actions/checkout@v6
with:
repository: my-org/my-tools
path: my-tools
```
> - If your secondary repository is private or internal you will need to add the option noted in [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
## Checkout multiple repos (nested)
```yaml
- name: Checkout
uses: actions/checkout@v6
- name: Checkout tools repo
uses: actions/checkout@v6
with:
repository: my-org/my-tools
path: my-tools
```
> - If your secondary repository is private or internal you will need to add the option noted in [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
## Checkout multiple repos (private)
```yaml
- name: Checkout
uses: actions/checkout@v6
with:
path: main
- name: Checkout private tools
uses: actions/checkout@v6
with:
repository: my-org/my-private-tools
token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
path: my-tools
```
> - `${{ github.token }}` is scoped to the current repository, so if you want to checkout a different repository that is private you will need to provide your own [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line).
## Checkout pull request HEAD commit instead of merge commit
```yaml
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
```
## Checkout pull request on closed event
```yaml
on:
pull_request:
branches: [main]
types: [opened, synchronize, closed]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
```
## Push a commit using the built-in token
```yaml
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: |
date > generated.txt
# Note: the following account information will not work on GHES
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "generated"
git push
```
*NOTE:* The user email is `{user.id}+{user.login}@users.noreply.github.com`. See users API: https://api.github.com/users/github-actions%5Bbot%5D
## Push a commit to a PR using the built-in token
In a pull request trigger, `ref` is required as GitHub Actions checks out in detached HEAD mode, meaning it doesnt check out your branch by default.
```yaml
on: pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.head_ref }}
- run: |
date > generated.txt
# Note: the following account information will not work on GHES
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "generated"
git push
```
*NOTE:* The user email is `{user.id}+{user.login}@users.noreply.github.com`. See users API: https://api.github.com/users/github-actions%5Bbot%5D
# Recommended permissions
When using the `checkout` action in your GitHub Actions workflow, it is recommended to set the following `GITHUB_TOKEN` permissions to ensure proper functionality, unless alternative auth is provided via the `token` or `ssh-key` inputs:
```yaml
permissions:
contents: read
```
# License
The scripts and documentation in this project are released under the [MIT License](LICENSE)
- 2026-08-26: 初版镜像(est org)把上游 .github/workflows 一并推入,codeql.yml 的 schedule 被 Gitea 1.24.7 以 ~30 秒间隔重复执行,CodeQL job 挂起 1730 分钟/个,4 并发占满 tn runner,阻断全平台 CI。处置:删仓重建 + 剥离 workflow + 禁用 Actions,最终归位 mu-ref。详见 Est-Platform-Coordination 交接文档第四节(reviews/gitea-build-pitfalls-handoff-2026-08-26.md)。
+50 -28
View File
@@ -1,12 +1,46 @@
import * as core from '@actions/core'
import {
jest,
describe,
it,
expect,
beforeAll,
beforeEach,
afterEach,
afterAll
} from '@jest/globals'
import * as fs from 'fs'
import * as gitAuthHelper from '../lib/git-auth-helper'
import * as io from '@actions/io'
import * as os from 'os'
import * as path from 'path'
import * as stateHelper from '../lib/state-helper'
import {IGitCommandManager} from '../lib/git-command-manager'
import {IGitSourceSettings} from '../lib/git-source-settings'
import {fileURLToPath} from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// Mock @actions/core before loading git-auth-helper
jest.unstable_mockModule('@actions/core', () => ({
setSecret: jest.fn(),
error: jest.fn(),
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
setFailed: jest.fn()
}))
// Mock state-helper
jest.unstable_mockModule('../src/state-helper.js', () => ({
setSshKeyPath: jest.fn(),
setSshKnownHostsPath: jest.fn(),
IsPost: false,
RepositoryPath: ''
}))
// Dynamic imports after mocking
const core = await import('@actions/core')
const gitAuthHelper = await import('../src/git-auth-helper.js')
type IGitCommandManager =
import('../src/git-command-manager.js').IGitCommandManager
type IGitSourceSettings =
import('../src/git-source-settings.js').IGitSourceSettings
const isWindows = process.platform === 'win32'
const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper')
@@ -32,25 +66,12 @@ describe('git-auth-helper tests', () => {
})
beforeEach(() => {
// Mock setSecret
jest.spyOn(core, 'setSecret').mockImplementation((secret: string) => {})
// Mock error/warning/info/debug
jest.spyOn(core, 'error').mockImplementation(jest.fn())
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
jest.spyOn(core, 'info').mockImplementation(jest.fn())
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
// Mock state helper
jest.spyOn(stateHelper, 'setSshKeyPath').mockImplementation(jest.fn())
jest
.spyOn(stateHelper, 'setSshKnownHostsPath')
.mockImplementation(jest.fn())
jest.clearAllMocks()
})
afterEach(() => {
// Unregister mocks
jest.restoreAllMocks()
jest.clearAllMocks()
// Restore HOME
if (originalHome) {
@@ -229,7 +250,7 @@ describe('git-auth-helper tests', () => {
await authHelper.configureAuth()
// Assert secret
const setSecretSpy = core.setSecret as jest.Mock<any, any>
const setSecretSpy = core.setSecret as jest.Mock<any>
expect(setSecretSpy).toHaveBeenCalledTimes(1)
const expectedSecret = Buffer.from(
`x-access-token:${settings.authToken}`,
@@ -529,7 +550,7 @@ describe('git-auth-helper tests', () => {
settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
@@ -562,7 +583,7 @@ describe('git-auth-helper tests', () => {
settings.persistCredentials = false
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
@@ -588,7 +609,7 @@ describe('git-auth-helper tests', () => {
settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
@@ -627,7 +648,7 @@ describe('git-auth-helper tests', () => {
)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
@@ -809,7 +830,7 @@ describe('git-auth-helper tests', () => {
// Mock getSubmoduleConfigPaths to return our fake submodules (for both configure and remove)
const mockGetSubmoduleConfigPaths =
git.getSubmoduleConfigPaths as jest.Mock<any, any>
git.getSubmoduleConfigPaths as jest.Mock<any>
mockGetSubmoduleConfigPaths.mockResolvedValue([
submodule1ConfigPath,
submodule2ConfigPath
@@ -1147,7 +1168,7 @@ async function setup(testName: string): Promise<void> {
),
tryReset: jest.fn(),
version: jest.fn()
}
} as unknown as IGitCommandManager & {env: {[key: string]: string}}
settings = {
authToken: 'some auth token',
@@ -1173,7 +1194,8 @@ async function setup(testName: string): Promise<void> {
sshUser: '',
workflowOrganizationId: 123456,
setSafeDirectory: true,
githubServerUrl: githubServerUrl
githubServerUrl: githubServerUrl,
allowUnsafePrCheckout: false
}
}
+64 -39
View File
@@ -1,26 +1,51 @@
import * as exec from '@actions/exec'
import * as fshelper from '../lib/fs-helper'
import * as commandManager from '../lib/git-command-manager'
import {
jest,
describe,
it,
expect,
beforeAll,
beforeEach,
afterEach,
afterAll
} from '@jest/globals'
let git: commandManager.IGitCommandManager
let mockExec = jest.fn()
// Mock @actions/exec
const mockExec = jest.fn()
jest.unstable_mockModule('@actions/exec', () => ({
exec: mockExec
}))
// Mock fs-helper
const mockFileExistsSync = jest.fn()
const mockDirectoryExistsSync = jest.fn()
jest.unstable_mockModule('../src/fs-helper.js', () => ({
fileExistsSync: mockFileExistsSync,
directoryExistsSync: mockDirectoryExistsSync
}))
// Dynamic imports after mocking
const commandManager = await import('../src/git-command-manager.js')
type IGitCommandManager =
import('../src/git-command-manager.js').IGitCommandManager
let git: IGitCommandManager
describe('git-auth-helper tests', () => {
beforeAll(async () => {})
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
mockFileExistsSync.mockReset()
mockDirectoryExistsSync.mockReset()
})
afterEach(() => {
jest.restoreAllMocks()
jest.clearAllMocks()
})
afterAll(() => {})
it('branch list matches', async () => {
mockExec.mockImplementation((path, args, options) => {
mockExec.mockImplementation((path: any, args: any, options: any) => {
console.log(args, options.listeners.stdout)
if (args.includes('version')) {
@@ -36,7 +61,7 @@ describe('git-auth-helper tests', () => {
return 1
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
@@ -53,7 +78,7 @@ describe('git-auth-helper tests', () => {
})
it('ambiguous ref name output is captured', async () => {
mockExec.mockImplementation((path, args, options) => {
mockExec.mockImplementation((path: any, args: any, options: any) => {
console.log(args, options.listeners.stdout)
if (args.includes('version')) {
@@ -72,7 +97,7 @@ describe('git-auth-helper tests', () => {
return 1
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
@@ -91,9 +116,9 @@ describe('git-auth-helper tests', () => {
describe('Test fetchDepth and fetchTags options', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
mockExec.mockImplementation((path, args, options) => {
mockFileExistsSync.mockReset()
mockDirectoryExistsSync.mockReset()
mockExec.mockImplementation((path: any, args: any, options: any) => {
console.log(args, options.listeners.stdout)
if (args.includes('version')) {
@@ -105,11 +130,11 @@ describe('Test fetchDepth and fetchTags options', () => {
})
afterEach(() => {
jest.restoreAllMocks()
jest.clearAllMocks()
})
it('should call execGit with the correct arguments when fetchDepth is 0', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
@@ -146,7 +171,7 @@ describe('Test fetchDepth and fetchTags options', () => {
})
it('should call execGit with the correct arguments when fetchDepth is 0 and refSpec includes tags', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -184,7 +209,7 @@ describe('Test fetchDepth and fetchTags options', () => {
})
it('should call execGit with the correct arguments when fetchDepth is 1', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -222,7 +247,7 @@ describe('Test fetchDepth and fetchTags options', () => {
})
it('should call execGit with the correct arguments when fetchDepth is 1 and refSpec includes tags', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -261,7 +286,7 @@ describe('Test fetchDepth and fetchTags options', () => {
})
it('should call execGit with the correct arguments when showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -299,7 +324,7 @@ describe('Test fetchDepth and fetchTags options', () => {
})
it('should call execGit with the correct arguments when fetchDepth is 42 and showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -339,7 +364,7 @@ describe('Test fetchDepth and fetchTags options', () => {
})
it('should call execGit with the correct arguments when showProgress is true and refSpec includes tags', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -380,23 +405,23 @@ describe('Test fetchDepth and fetchTags options', () => {
describe('repository initialization object format', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
mockFileExistsSync.mockReset()
mockDirectoryExistsSync.mockReset()
})
afterEach(() => {
jest.restoreAllMocks()
jest.clearAllMocks()
})
it('initializes SHA-256 repositories with the matching object format', async () => {
mockExec.mockImplementation((path, args, options) => {
mockExec.mockImplementation((path: any, args: any, options: any) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
}
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
git = await commandManager.createCommandManager('test', false, false)
@@ -410,14 +435,14 @@ describe('repository initialization object format', () => {
})
it('initializes SHA-1 repositories with existing default arguments', async () => {
mockExec.mockImplementation((path, args, options) => {
mockExec.mockImplementation((path: any, args: any, options: any) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('git version 2.50.1'))
}
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
git = await commandManager.createCommandManager('test', false, false)
@@ -433,12 +458,12 @@ describe('repository initialization object format', () => {
describe('git user-agent with orchestration ID', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
mockFileExistsSync.mockReset()
mockDirectoryExistsSync.mockReset()
})
afterEach(() => {
jest.restoreAllMocks()
jest.clearAllMocks()
// Clean up environment variable to prevent test pollution
delete process.env['ACTIONS_ORCHESTRATION_ID']
})
@@ -448,7 +473,7 @@ describe('git user-agent with orchestration ID', () => {
process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
let capturedEnv: any = null
mockExec.mockImplementation((path, args, options) => {
mockExec.mockImplementation((path: any, args: any, options: any) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
@@ -456,7 +481,7 @@ describe('git user-agent with orchestration ID', () => {
capturedEnv = options.env
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -483,7 +508,7 @@ describe('git user-agent with orchestration ID', () => {
process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
let capturedEnv: any = null
mockExec.mockImplementation((path, args, options) => {
mockExec.mockImplementation((path: any, args: any, options: any) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
@@ -491,7 +516,7 @@ describe('git user-agent with orchestration ID', () => {
capturedEnv = options.env
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
@@ -517,7 +542,7 @@ describe('git user-agent with orchestration ID', () => {
delete process.env['ACTIONS_ORCHESTRATION_ID']
let capturedEnv: any = null
mockExec.mockImplementation((path, args, options) => {
mockExec.mockImplementation((path: any, args: any, options: any) => {
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
@@ -525,7 +550,7 @@ describe('git user-agent with orchestration ID', () => {
capturedEnv = options.env
return 0
})
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
// exec.exec is already mockExec
const workingDirectory = 'test'
const lfs = false
+41 -19
View File
@@ -1,9 +1,36 @@
import * as core from '@actions/core'
import {
jest,
describe,
it,
expect,
beforeAll,
beforeEach,
afterEach
} from '@jest/globals'
import * as fs from 'fs'
import * as gitDirectoryHelper from '../lib/git-directory-helper'
import * as io from '@actions/io'
import * as path from 'path'
import {IGitCommandManager} from '../lib/git-command-manager'
import {fileURLToPath} from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// Mock @actions/core before loading git-directory-helper
jest.unstable_mockModule('@actions/core', () => ({
error: jest.fn(),
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
setFailed: jest.fn(),
startGroup: jest.fn(),
endGroup: jest.fn()
}))
// Dynamic imports after mocking
const core = await import('@actions/core')
const gitDirectoryHelper = await import('../src/git-directory-helper.js')
type IGitCommandManager =
import('../src/git-command-manager.js').IGitCommandManager
const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper')
let repositoryPath: string
@@ -19,16 +46,11 @@ describe('git-directory-helper tests', () => {
})
beforeEach(() => {
// Mock error/warning/info/debug
jest.spyOn(core, 'error').mockImplementation(jest.fn())
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
jest.spyOn(core, 'info').mockImplementation(jest.fn())
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
jest.clearAllMocks()
})
afterEach(() => {
// Unregister mocks
jest.restoreAllMocks()
jest.clearAllMocks()
})
const cleansWhenCleanTrue = 'cleans when clean true'
@@ -81,7 +103,7 @@ describe('git-directory-helper tests', () => {
// Arrange
await setup(doesNotCheckoutDetachWhenNotAlreadyDetached)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockIsDetached = git.isDetached as jest.Mock<any, any>
const mockIsDetached = git.isDetached as jest.Mock<any>
mockIsDetached.mockImplementation(async () => {
return true
})
@@ -132,7 +154,7 @@ describe('git-directory-helper tests', () => {
// Arrange
await setup(removesContentsWhenCleanFails)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
let mockTryClean = git.tryClean as jest.Mock<any, any>
let mockTryClean = git.tryClean as jest.Mock<any>
mockTryClean.mockImplementation(async () => {
return false
})
@@ -210,7 +232,7 @@ describe('git-directory-helper tests', () => {
// Arrange
await setup(removesContentsWhenResetFails)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
let mockTryReset = git.tryReset as jest.Mock<any, any>
let mockTryReset = git.tryReset as jest.Mock<any>
mockTryReset.mockImplementation(async () => {
return false
})
@@ -260,7 +282,7 @@ describe('git-directory-helper tests', () => {
// Arrange
await setup(removesLocalBranches)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
const mockBranchList = git.branchList as jest.Mock<any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote ? [] : ['local-branch-1', 'local-branch-2']
})
@@ -291,7 +313,7 @@ describe('git-directory-helper tests', () => {
//mock bad submodule
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
const submoduleStatus = git.submoduleStatus as jest.Mock<any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return false
})
@@ -319,7 +341,7 @@ describe('git-directory-helper tests', () => {
await setup(doesNotCleanWhenSubmoduleStatusIsTrue)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
const submoduleStatus = git.submoduleStatus as jest.Mock<any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return true
})
@@ -381,7 +403,7 @@ describe('git-directory-helper tests', () => {
// Arrange
await setup(removesAncestorRemoteBranch)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
const mockBranchList = git.branchList as jest.Mock<any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote ? ['origin/remote-branch-1', 'origin/remote-branch-2'] : []
})
@@ -411,7 +433,7 @@ describe('git-directory-helper tests', () => {
// Arrange
await setup(removesDescendantRemoteBranches)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
const mockBranchList = git.branchList as jest.Mock<any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote
? ['origin/remote-branch-1/conflict', 'origin/remote-branch-2']
@@ -507,5 +529,5 @@ async function setup(testName: string): Promise<void> {
return true
}),
version: jest.fn()
}
} as unknown as IGitCommandManager
}
+3 -2
View File
@@ -1,5 +1,6 @@
import {GitVersion} from '../src/git-version'
import {MinimumGitSparseCheckoutVersion} from '../src/git-command-manager'
import {describe, it, expect} from '@jest/globals'
import {GitVersion} from '../src/git-version.js'
import {MinimumGitSparseCheckoutVersion} from '../src/git-command-manager.js'
describe('git-version tests', () => {
it('basics', async () => {
+29 -15
View File
@@ -1,11 +1,25 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import * as githubApiHelper from '../lib/github-api-helper'
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals'
// Mock @actions/core
const mockDebug = jest.fn()
jest.unstable_mockModule('@actions/core', () => ({
debug: mockDebug,
info: jest.fn(),
warning: jest.fn(),
error: jest.fn()
}))
// Mock @actions/github
const mockGetOctokit = jest.fn()
jest.unstable_mockModule('@actions/github', () => ({
getOctokit: mockGetOctokit
}))
// Dynamic imports after mocking
const githubApiHelper = await import('../src/github-api-helper.js')
describe('github-api-helper object format', () => {
let getOctokitSpy: jest.SpyInstance
let debugSpy: jest.SpyInstance
let request: jest.Mock
let request: jest.Mock<any>
function mockHashAlgorithmApi(hashAlgorithm: string): void {
request = jest.fn(async () => ({
@@ -13,17 +27,18 @@ describe('github-api-helper object format', () => {
hash_algorithm: hashAlgorithm
}
}))
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
mockGetOctokit.mockReturnValue({
request
} as any)
}
beforeEach(() => {
debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn())
mockDebug.mockClear()
mockGetOctokit.mockClear()
})
afterEach(() => {
jest.restoreAllMocks()
jest.clearAllMocks()
})
it('detects SHA-256 from the repository hash algorithm endpoint', async () => {
@@ -33,7 +48,7 @@ describe('github-api-helper object format', () => {
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: 'sha256', succeeded: true})
expect(getOctokitSpy).toHaveBeenCalledWith(
expect(mockGetOctokit).toHaveBeenCalledWith(
'token',
expect.objectContaining({baseUrl: 'https://api.github.com'})
)
@@ -54,7 +69,6 @@ describe('github-api-helper object format', () => {
it('detects object format from an existing commit without API calls', async () => {
const commitSha =
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92'
getOctokitSpy = jest.spyOn(github, 'getOctokit')
await expect(
githubApiHelper.tryGetRepositoryObjectFormat(
@@ -66,7 +80,7 @@ describe('github-api-helper object format', () => {
)
).resolves.toEqual({format: 'sha256', succeeded: true})
expect(getOctokitSpy).not.toHaveBeenCalled()
expect(mockGetOctokit).not.toHaveBeenCalled()
})
it('returns unsuccessful when the hash algorithm endpoint value is not recognized', async () => {
@@ -75,7 +89,7 @@ describe('github-api-helper object format', () => {
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: '', succeeded: false})
expect(debugSpy).toHaveBeenCalledWith(
expect(mockDebug).toHaveBeenCalledWith(
'Unable to determine repository object format from hash-algorithm endpoint'
)
})
@@ -84,14 +98,14 @@ describe('github-api-helper object format', () => {
request = jest.fn(async () => {
throw new Error('not found')
})
jest.spyOn(github, 'getOctokit').mockReturnValue({
mockGetOctokit.mockReturnValue({
request
} as any)
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: '', succeeded: false})
expect(debugSpy).toHaveBeenCalledWith(
expect(mockDebug).toHaveBeenCalledWith(
'Unable to determine repository object format from hash-algorithm endpoint: not found'
)
})
+158 -47
View File
@@ -1,10 +1,13 @@
import * as core from '@actions/core'
import * as fsHelper from '../lib/fs-helper'
import * as github from '@actions/github'
import * as inputHelper from '../lib/input-helper'
import {
jest,
describe,
it,
expect,
beforeAll,
beforeEach,
afterAll
} from '@jest/globals'
import * as path from 'path'
import * as workflowContextHelper from '../lib/workflow-context-helper'
import {IGitSourceSettings} from '../lib/git-source-settings'
const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE']
const gitHubWorkspace = path.resolve('/checkout-tests/workspace')
@@ -12,42 +15,69 @@ const gitHubWorkspace = path.resolve('/checkout-tests/workspace')
// Inputs for mock @actions/core
let inputs = {} as any
// Shallow clone original @actions/github context
let originalContext = {...github.context}
// Mutable mock github context
const mockGithubContext: any = {
ref: 'refs/heads/some-ref',
sha: '1234567890123456789012345678901234567890',
repo: {owner: 'some-owner', repo: 'some-repo'},
eventName: '',
payload: {}
}
// Replicate @actions/core getInput behavior: it trims whitespace by default
// (String.prototype.trim(), which strips characters such as a leading U+FEFF BOM)
// unless trimWhitespace is explicitly set to false.
const getInputImpl = (name: string, options?: {trimWhitespace?: boolean}) => {
const val = inputs[name] ?? ''
if (options && options.trimWhitespace === false) {
return val
}
return typeof val === 'string' ? val.trim() : val
}
// Mock @actions/core before loading input-helper
jest.unstable_mockModule('@actions/core', () => ({
getInput: jest.fn(getInputImpl),
getBooleanInput: jest.fn((name: string) => inputs[name]),
getMultilineInput: jest.fn((name: string) =>
inputs[name] ? String(inputs[name]).split('\n').filter(Boolean) : []
),
error: jest.fn(),
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
setSecret: jest.fn()
}))
// Mock @actions/github before loading input-helper
jest.unstable_mockModule('@actions/github', () => ({
context: mockGithubContext,
getOctokit: jest.fn()
}))
// Mock fs-helper
const mockDirectoryExistsSync = jest.fn((p: string) => p === gitHubWorkspace)
jest.unstable_mockModule('../src/fs-helper.js', () => ({
directoryExistsSync: mockDirectoryExistsSync,
fileExistsSync: jest.fn()
}))
// Mock workflow-context-helper
const mockGetOrganizationId = jest.fn(async () => 123456)
jest.unstable_mockModule('../src/workflow-context-helper.js', () => ({
getOrganizationId: mockGetOrganizationId
}))
// Dynamic imports after mocking
const core = await import('@actions/core')
const inputHelper = await import('../src/input-helper.js')
type IGitSourceSettings =
import('../src/git-source-settings.js').IGitSourceSettings
describe('input-helper tests', () => {
beforeAll(() => {
// Mock getInput
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
return inputs[name]
})
// Mock error/warning/info/debug
jest.spyOn(core, 'error').mockImplementation(jest.fn())
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
jest.spyOn(core, 'info').mockImplementation(jest.fn())
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
// Mock github context
jest.spyOn(github.context, 'repo', 'get').mockImplementation(() => {
return {
owner: 'some-owner',
repo: 'some-repo'
}
})
github.context.ref = 'refs/heads/some-ref'
github.context.sha = '1234567890123456789012345678901234567890'
// Mock ./fs-helper directoryExistsSync()
jest
.spyOn(fsHelper, 'directoryExistsSync')
.mockImplementation((path: string) => path == gitHubWorkspace)
// Mock ./workflowContextHelper getOrganizationId()
jest
.spyOn(workflowContextHelper, 'getOrganizationId')
.mockImplementation(() => Promise.resolve(123456))
// GitHub workspace
process.env['GITHUB_WORKSPACE'] = gitHubWorkspace
})
@@ -55,6 +85,13 @@ describe('input-helper tests', () => {
beforeEach(() => {
// Reset inputs
inputs = {}
jest.clearAllMocks()
// Re-apply default mocks
;(core.getInput as jest.Mock<any>).mockImplementation(getInputImpl as any)
mockDirectoryExistsSync.mockImplementation(
(p: string) => p === gitHubWorkspace
)
mockGetOrganizationId.mockResolvedValue(123456)
})
afterAll(() => {
@@ -65,11 +102,8 @@ describe('input-helper tests', () => {
}
// Restore @actions/github context
github.context.ref = originalContext.ref
github.context.sha = originalContext.sha
// Restore
jest.restoreAllMocks()
mockGithubContext.ref = 'refs/heads/some-ref'
mockGithubContext.sha = '1234567890123456789012345678901234567890'
})
it('sets defaults', async () => {
@@ -91,18 +125,19 @@ describe('input-helper tests', () => {
expect(settings.repositoryOwner).toBe('some-owner')
expect(settings.repositoryPath).toBe(gitHubWorkspace)
expect(settings.setSafeDirectory).toBe(true)
expect(settings.allowUnsafePrCheckout).toBe(false)
})
it('qualifies ref', async () => {
let originalRef = github.context.ref
let originalRef = mockGithubContext.ref
try {
github.context.ref = 'some-unqualified-ref'
mockGithubContext.ref = 'some-unqualified-ref'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890')
expect(settings.ref).toBe('refs/heads/some-unqualified-ref')
} finally {
github.context.ref = originalRef
mockGithubContext.ref = originalRef
}
})
@@ -150,8 +185,84 @@ describe('input-helper tests', () => {
expect(settings.commit).toBeFalsy()
})
it('does not reclassify a ref as sha when a BOM is prefixed', async () => {
// A fork branch named "<U+FEFF>" + 40 hex chars. core.getInput trims the
// BOM by default, which previously collapsed this into a bare SHA and
// bypassed the unsafe fork PR checkout guard.
inputs.ref = '\uFEFF522d932fae5296da51fdf431934425ecf891c6a2'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.commit).toBeFalsy()
expect(settings.ref).toBe('522d932fae5296da51fdf431934425ecf891c6a2')
})
it('does not reclassify a sha-256 ref as sha when a BOM is prefixed', async () => {
inputs.ref =
'\uFEFF1111111111222222222233333333334444444444555555555566666666667777'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.commit).toBeFalsy()
expect(settings.ref).toBe(
'1111111111222222222233333333334444444444555555555566666666667777'
)
})
it('treats a sha surrounded by ascii whitespace as a commit', async () => {
// ASCII whitespace can only come from the workflow author's YAML (git ref
// names cannot contain it), so trimming it and treating the value as a
// commit is safe.
inputs.ref = ' 1111111111222222222233333333334444444444 '
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.ref).toBeFalsy()
expect(settings.commit).toBe('1111111111222222222233333333334444444444')
})
it('sets workflow organization ID', async () => {
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.workflowOrganizationId).toBe(123456)
})
describe('unsafe PR checkout guard', () => {
const forkPayload = {
repository: {id: 100},
pull_request: {
head: {
sha: '1234567890123456789012345678901234567890',
repo: {id: 200, full_name: 'attacker/fork'}
},
merge_commit_sha: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
}
}
it('allows the default self-checkout on a fork pull_request_target', async () => {
const originalEvent = mockGithubContext.eventName
const originalPayload = mockGithubContext.payload
try {
mockGithubContext.eventName = 'pull_request_target'
mockGithubContext.payload = forkPayload
// Simulate a rebase/fast-forward merge where the base tip (event SHA)
// equals the PR head SHA. The default self-checkout must still succeed.
mockGithubContext.sha = '1234567890123456789012345678901234567890'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.commit).toBe('1234567890123456789012345678901234567890')
} finally {
mockGithubContext.eventName = originalEvent
mockGithubContext.payload = originalPayload
}
})
it('refuses an explicit fork repository on pull_request_target', async () => {
const originalEvent = mockGithubContext.eventName
const originalPayload = mockGithubContext.payload
try {
mockGithubContext.eventName = 'pull_request_target'
mockGithubContext.payload = forkPayload
inputs.repository = 'attacker/fork'
await expect(inputHelper.getInputs()).rejects.toThrow(
/Refusing to check out fork pull request code/
)
} finally {
mockGithubContext.eventName = originalEvent
mockGithubContext.payload = originalPayload
}
})
})
})
+57 -35
View File
@@ -1,8 +1,36 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals'
import * as assert from 'assert'
import * as core from '@actions/core'
import * as github from '@actions/github'
import * as refHelper from '../lib/ref-helper'
import {IGitCommandManager} from '../lib/git-command-manager'
// Mutable mock github context
const mockGithubContext: any = {
eventName: '',
payload: {},
repo: {owner: 'some-owner', repo: 'some-repo'},
ref: '',
sha: ''
}
// Mock @actions/core
const mockDebug = jest.fn()
jest.unstable_mockModule('@actions/core', () => ({
debug: mockDebug,
info: jest.fn(),
warning: jest.fn(),
error: jest.fn(),
setFailed: jest.fn()
}))
// Mock @actions/github
const mockGetOctokit = jest.fn()
jest.unstable_mockModule('@actions/github', () => ({
context: mockGithubContext,
getOctokit: mockGetOctokit
}))
// Dynamic imports after mocking
const refHelper = await import('../src/ref-helper.js')
type IGitCommandManager =
import('../src/git-command-manager.js').IGitCommandManager
const commit = '1234567890123456789012345678901234567890'
const sha256Commit =
@@ -12,6 +40,7 @@ let git: IGitCommandManager
describe('ref-helper tests', () => {
beforeEach(() => {
git = {} as unknown as IGitCommandManager
jest.clearAllMocks()
})
it('getCheckoutInfo requires git', async () => {
@@ -166,14 +195,12 @@ describe('ref-helper tests', () => {
})
it('getRefSpec sha + refs/tags/ with fetchTags', async () => {
// When fetchTags is true, only include tags wildcard (specific tag is redundant)
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit, true)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
})
it('getRefSpec sha + refs/heads/ with fetchTags', async () => {
// When fetchTags is true, include both the branch refspec and tags wildcard
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', commit, true)
expect(refSpec.length).toBe(2)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
@@ -194,7 +221,6 @@ describe('ref-helper tests', () => {
})
it('getRefSpec unqualified ref only with fetchTags', async () => {
// When fetchTags is true, skip specific tag pattern since wildcard covers all
const refSpec = refHelper.getRefSpec('my-ref', '', true)
expect(refSpec.length).toBe(2)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
@@ -222,14 +248,12 @@ describe('ref-helper tests', () => {
})
it('getRefSpec refs/tags/ only with fetchTags', async () => {
// When fetchTags is true, only include tags wildcard (specific tag is redundant)
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', '', true)
expect(refSpec.length).toBe(1)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
})
it('getRefSpec refs/heads/ only with fetchTags', async () => {
// When fetchTags is true, include both the branch refspec and tags wildcard
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '', true)
expect(refSpec.length).toBe(2)
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
@@ -248,9 +272,7 @@ describe('ref-helper tests', () => {
'1111111111222222222233333333334444444444555555555566666666667777'
const sha256Base =
'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffff0000'
let debugSpy: jest.SpyInstance
let getOctokitSpy: jest.SpyInstance
let repoGetSpy: jest.Mock
let repoGetSpy: jest.Mock<any>
let originalEventName: string
let originalPayload: unknown
let originalRef: string
@@ -261,10 +283,10 @@ describe('ref-helper tests', () => {
expectedBaseSha: string,
mergeCommit: string
): void {
;(github.context as any).eventName = 'pull_request'
github.context.ref = ref
github.context.sha = mergeCommit
;(github.context as any).payload = {
mockGithubContext.eventName = 'pull_request'
mockGithubContext.ref = ref
mockGithubContext.sha = mergeCommit
mockGithubContext.payload = {
action: 'synchronize',
after: expectedHeadSha,
number: 123,
@@ -280,18 +302,18 @@ describe('ref-helper tests', () => {
}
beforeEach(() => {
originalEventName = github.context.eventName
originalPayload = github.context.payload
originalRef = github.context.ref
originalSha = github.context.sha
originalEventName = mockGithubContext.eventName
originalPayload = mockGithubContext.payload
originalRef = mockGithubContext.ref
originalSha = mockGithubContext.sha
jest.spyOn(github.context, 'repo', 'get').mockReturnValue({
mockGithubContext.repo = {
owner: repositoryOwner,
repo: repositoryName
})
debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn())
}
repoGetSpy = jest.fn(async () => ({}))
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
mockGetOctokit.mockReturnValue({
rest: {
repos: {
get: repoGetSpy
@@ -301,11 +323,11 @@ describe('ref-helper tests', () => {
})
afterEach(() => {
;(github.context as any).eventName = originalEventName
;(github.context as any).payload = originalPayload
github.context.ref = originalRef
github.context.sha = originalSha
jest.restoreAllMocks()
mockGithubContext.eventName = originalEventName
mockGithubContext.payload = originalPayload
mockGithubContext.ref = originalRef
mockGithubContext.sha = originalSha
jest.clearAllMocks()
})
it('returns early for SHA-1 merge commit', async () => {
@@ -320,7 +342,7 @@ describe('ref-helper tests', () => {
commit
)
expect(getOctokitSpy).not.toHaveBeenCalled()
expect(mockGetOctokit).not.toHaveBeenCalled()
expect(repoGetSpy).not.toHaveBeenCalled()
})
@@ -338,7 +360,7 @@ describe('ref-helper tests', () => {
sha256Commit
)
expect(getOctokitSpy).toHaveBeenCalledWith(
expect(mockGetOctokit).toHaveBeenCalledWith(
'token',
expect.objectContaining({
userAgent: expect.stringContaining(
@@ -350,10 +372,10 @@ describe('ref-helper tests', () => {
owner: repositoryOwner,
repo: repositoryName
})
expect(debugSpy).toHaveBeenCalledWith(
expect(mockDebug).toHaveBeenCalledWith(
`Expected head sha ${sha256Head}; actual head sha ${actualHeadSha}`
)
expect(debugSpy).not.toHaveBeenCalledWith('Unexpected message format')
expect(mockDebug).not.toHaveBeenCalledWith('Unexpected message format')
})
it('does not match 50-char hex as a valid merge', async () => {
@@ -370,9 +392,9 @@ describe('ref-helper tests', () => {
commit
)
expect(getOctokitSpy).not.toHaveBeenCalled()
expect(mockGetOctokit).not.toHaveBeenCalled()
expect(repoGetSpy).not.toHaveBeenCalled()
expect(debugSpy).toHaveBeenCalledWith('Unexpected message format')
expect(mockDebug).toHaveBeenCalledWith('Unexpected message format')
})
})
})
+24 -9
View File
@@ -1,16 +1,32 @@
import * as core from '@actions/core'
import {RetryHelper} from '../lib/retry-helper'
import {
jest,
describe,
it,
expect,
beforeAll,
beforeEach,
afterAll
} from '@jest/globals'
let info: string[] = []
// Mock @actions/core before loading retry-helper
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn((message: string) => {
info.push(message)
}),
debug: jest.fn(),
warning: jest.fn(),
error: jest.fn()
}))
// Dynamic imports after mocking
const {RetryHelper} = await import('../src/retry-helper.js')
let info: string[]
let retryHelper: any
describe('retry-helper tests', () => {
beforeAll(() => {
// Mock @actions/core info()
jest.spyOn(core, 'info').mockImplementation((message: string) => {
info.push(message)
})
retryHelper = new RetryHelper(3, 0, 0)
})
@@ -20,7 +36,6 @@ describe('retry-helper tests', () => {
})
afterAll(() => {
// Restore
jest.restoreAllMocks()
})
+284
View File
@@ -0,0 +1,284 @@
import {
jest,
describe,
it,
expect,
beforeAll,
afterEach,
afterAll
} from '@jest/globals'
const BASE_REPO_ID = 100
const FORK_REPO_ID = 200
const PR_HEAD_SHA = '1111111111111111111111111111111111111111'
const PR_MERGE_SHA = '2222222222222222222222222222222222222222'
const SAFE_BASE_SHA = '3333333333333333333333333333333333333333'
const WORKFLOW_RUN_HEAD_COMMIT_SHA = '4444444444444444444444444444444444444444'
const BASE_QUALIFIED_REPO = 'some-owner/some-repo'
const FORK_QUALIFIED_REPO = 'another-repo/fork'
// Mutable mock context
const mockContext: any = {
eventName: '',
payload: {},
repo: {owner: 'some-owner', repo: 'some-repo'},
ref: '',
sha: ''
}
jest.unstable_mockModule('@actions/github', () => ({
context: mockContext
}))
// Dynamic imports after mocking
const {assertSafePrCheckout} =
await import('../src/unsafe-pr-checkout-helper.js')
const originalEventName = mockContext.eventName
const originalPayload = mockContext.payload
function setContext(eventName: string, payload: object): void {
mockContext.eventName = eventName
mockContext.payload = payload
}
function forkPullRequestTargetPayload(): object {
return {
repository: {id: BASE_REPO_ID},
pull_request: {
head: {
sha: PR_HEAD_SHA,
repo: {id: FORK_REPO_ID, full_name: FORK_QUALIFIED_REPO}
},
merge_commit_sha: PR_MERGE_SHA
}
}
}
function sameRepoPullRequestTargetPayload(): object {
return {
repository: {id: BASE_REPO_ID},
pull_request: {
head: {
sha: PR_HEAD_SHA,
repo: {id: BASE_REPO_ID, full_name: BASE_QUALIFIED_REPO}
},
merge_commit_sha: PR_MERGE_SHA
}
}
}
function forkWorkflowRunPayload(): object {
return {
repository: {id: BASE_REPO_ID},
workflow_run: {
event: 'pull_request',
head_commit: {id: WORKFLOW_RUN_HEAD_COMMIT_SHA},
head_repository: {id: FORK_REPO_ID, full_name: FORK_QUALIFIED_REPO}
}
}
}
describe('unsafe-pr-checkout-helper', () => {
beforeAll(() => {
mockContext.repo = {owner: 'some-owner', repo: 'some-repo'}
})
afterEach(() => {
mockContext.eventName = originalEventName
mockContext.payload = originalPayload
})
afterAll(() => {
mockContext.eventName = originalEventName
mockContext.payload = originalPayload
})
it('allows pull_request events untouched', () => {
setContext('pull_request', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: 'attacker/fork',
ref: 'refs/pull/1/merge',
commit: '',
allowUnsafePrCheckout: false
})
).not.toThrow()
})
it('allows pull_request_target default checkout (base branch)', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: 'refs/heads/main',
commit: SAFE_BASE_SHA,
allowUnsafePrCheckout: false
})
).not.toThrow()
})
it('allows same-repo pull_request_target checkout of PR head', () => {
setContext('pull_request_target', sameRepoPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: '',
commit: PR_HEAD_SHA,
allowUnsafePrCheckout: false
})
).not.toThrow()
})
it('refuses pull_request_target fork PR head SHA checkout', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: '',
commit: PR_HEAD_SHA,
allowUnsafePrCheckout: false
})
).toThrow(/Refusing to check out fork pull request code/)
})
it('refuses pull_request_target fork PR merge_commit_sha checkout', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: '',
commit: PR_MERGE_SHA,
allowUnsafePrCheckout: false
})
).toThrow(/allow-unsafe-pr-checkout/)
})
it('refuses pull_request_target fork PR ref pattern (head)', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: 'refs/pull/42/head',
commit: '',
allowUnsafePrCheckout: false
})
).toThrow()
})
it('refuses pull_request_target fork PR ref pattern (merge)', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: 'refs/pull/42/merge',
commit: '',
allowUnsafePrCheckout: false
})
).toThrow()
})
it('refuses pull_request_target when repository points at the fork', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: FORK_QUALIFIED_REPO,
ref: 'refs/heads/main',
commit: '',
allowUnsafePrCheckout: false
})
).toThrow()
})
it('allows pull_request_target checkout of an unrelated third-party repo', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: 'some-other/unrelated',
ref: 'refs/heads/main',
commit: '',
allowUnsafePrCheckout: false
})
).not.toThrow()
})
it('refuses pull_request_target ignoring repository case differences', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: FORK_QUALIFIED_REPO.toUpperCase(),
ref: '',
commit: '',
allowUnsafePrCheckout: false
})
).toThrow()
})
it('refuses pull_request_target ignoring commit SHA case differences', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: '',
commit: PR_HEAD_SHA.toUpperCase(),
allowUnsafePrCheckout: false
})
).toThrow()
})
it('allows pull_request_target fork PR checkout when opted in', () => {
setContext('pull_request_target', forkPullRequestTargetPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: 'refs/pull/42/merge',
commit: '',
allowUnsafePrCheckout: true
})
).not.toThrow()
})
it('refuses workflow_run fork PR head_commit.id checkout', () => {
setContext('workflow_run', forkWorkflowRunPayload())
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: '',
commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
allowUnsafePrCheckout: false
})
).toThrow()
})
it('refuses workflow_run with pull_request_target underlying event', () => {
const payload = forkWorkflowRunPayload() as {
workflow_run: {event: string}
}
payload.workflow_run.event = 'pull_request_target'
setContext('workflow_run', payload)
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: '',
commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
allowUnsafePrCheckout: false
})
).toThrow()
})
it('allows workflow_run same-repo PR (head_repository.id matches base)', () => {
const payload = forkWorkflowRunPayload() as {
workflow_run: {head_repository: {id: number}}
}
payload.workflow_run.head_repository.id = BASE_REPO_ID
setContext('workflow_run', payload)
expect(() =>
assertSafePrCheckout({
qualifiedRepository: BASE_QUALIFIED_REPO,
ref: '',
commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
allowUnsafePrCheckout: false
})
).not.toThrow()
})
})
+2 -1
View File
@@ -1,4 +1,5 @@
import * as urlHelper from '../src/url-helper'
import {jest, describe, it, expect, beforeEach, afterAll} from '@jest/globals'
import * as urlHelper from '../src/url-helper.js'
describe('getServerUrl tests', () => {
it('basics', async () => {
+9
View File
@@ -98,6 +98,15 @@ inputs:
github-server-url:
description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com
required: false
allow-unsafe-pr-checkout:
description: >
Required to check out fork pull request code from a workflow triggered by
`pull_request_target` or `workflow_run`. These workflows run with the
base repository's GITHUB_TOKEN, secrets, default-branch cache scope, and
runner access; fetching and executing a fork's code in that trusted
context commonly leads to "pwn request" vulnerabilities. Set to `true`
only after reviewing the risks at https://gh.io/securely-using-pull_request_target.
default: false
outputs:
ref:
description: 'The branch, tag or SHA that was checked out'
+28293 -24587
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+62
View File
@@ -0,0 +1,62 @@
import github from 'eslint-plugin-github'
import jest from 'eslint-plugin-jest'
const githubConfigs = github.getFlatConfigs()
export default [
{
ignores: ['**/node_modules/**', '**/lib/**', '**/dist/**']
},
githubConfigs.recommended,
...githubConfigs.typescript,
{
files: ['**/*.ts'],
languageOptions: {
parserOptions: {
project: './tsconfig.json'
}
},
rules: {
'eslint-comments/no-use': 'off',
'import/no-namespace': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-member-accessibility': [
'error',
{accessibility: 'no-public'}
],
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/await-thenable': 'error',
camelcase: 'off',
'@typescript-eslint/explicit-function-return-type': [
'error',
{allowExpressions: true}
],
'@typescript-eslint/no-array-constructor': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-for-in-array': 'error',
'@typescript-eslint/no-inferrable-types': 'error',
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-namespace': 'error',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-unnecessary-qualifier': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/no-useless-constructor': 'error',
'@typescript-eslint/prefer-for-of': 'warn',
'@typescript-eslint/prefer-function-type': 'warn',
'@typescript-eslint/prefer-includes': 'error',
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/require-array-sort-compare': 'error',
'@typescript-eslint/restrict-plus-operands': 'error',
'@typescript-eslint/unbound-method': 'error'
}
},
{
files: ['__test__/**/*.ts'],
...jest.configs['flat/recommended']
}
]
-12
View File
@@ -1,12 +0,0 @@
module.exports = {
clearMocks: true,
fakeTimers: {},
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}
+24
View File
@@ -0,0 +1,24 @@
export default {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
roots: ['<rootDir>'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
diagnostics: {
ignoreCodes: [151002]
}
}
]
},
extensionsToTreatAsEsm: ['.ts'],
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
verbose: true
}
+2376 -1393
View File
File diff suppressed because it is too large Load Diff
+22 -20
View File
@@ -1,14 +1,15 @@
{
"name": "checkout",
"version": "5.0.0",
"version": "7.0.1",
"description": "checkout action",
"type": "module",
"main": "lib/main.js",
"scripts": {
"build": "tsc && ncc build && node lib/misc/generate-docs.js",
"build": "tsc && ncc build src/main.ts -o dist && node lib/misc/generate-docs.js",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts",
"test": "jest",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"licensed-check": "src/misc/licensed-check.sh",
"licensed-generate": "src/misc/licensed-generate.sh"
},
@@ -27,29 +28,30 @@
"url": "https://github.com/actions/checkout/issues"
},
"homepage": "https://github.com/actions/checkout#readme",
"engines": {
"node": ">=24"
},
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0",
"@actions/io": "^1.1.3",
"@actions/tool-cache": "^2.0.1",
"uuid": "^9.0.1"
"@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0",
"@actions/github": "^9.1.1",
"@actions/io": "^3.0.2",
"@actions/tool-cache": "^4.0.0"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^24.1.0",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.9.0",
"@vercel/ncc": "^0.38.1",
"eslint": "^8.57.0",
"eslint-plugin-github": "^4.10.2",
"eslint-plugin-jest": "^28.8.2",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"@vercel/ncc": "^0.44.0",
"eslint": "^9.39.2",
"eslint-plugin-github": "^6.0.0",
"eslint-plugin-jest": "^29.12.1",
"jest": "^29.7.0",
"jest-circus": "^29.7.0",
"js-yaml": "^4.1.0",
"prettier": "^3.3.3",
"ts-jest": "^29.2.5",
"js-yaml": "^4.2.0",
"prettier": "^3.8.4",
"ts-jest": "^29.4.11",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
}
}
+9 -9
View File
@@ -5,12 +5,12 @@ import * as fs from 'fs'
import * as io from '@actions/io'
import * as os from 'os'
import * as path from 'path'
import * as regexpHelper from './regexp-helper'
import * as stateHelper from './state-helper'
import * as urlHelper from './url-helper'
import {v4 as uuid} from 'uuid'
import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings'
import * as regexpHelper from './regexp-helper.js'
import * as stateHelper from './state-helper.js'
import * as urlHelper from './url-helper.js'
import {randomUUID} from 'crypto'
import {IGitCommandManager} from './git-command-manager.js'
import {IGitSourceSettings} from './git-source-settings.js'
const IS_WINDOWS = process.platform === 'win32'
const SSH_COMMAND_KEY = 'core.sshCommand'
@@ -90,7 +90,7 @@ class GitAuthHelper {
// Create a temp home directory
const runnerTemp = process.env['RUNNER_TEMP'] || ''
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
const uniqueId = uuid()
const uniqueId = randomUUID()
this.temporaryHomePath = path.join(runnerTemp, uniqueId)
await fs.promises.mkdir(this.temporaryHomePath, {recursive: true})
@@ -255,7 +255,7 @@ class GitAuthHelper {
// Write key
const runnerTemp = process.env['RUNNER_TEMP'] || ''
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
const uniqueId = uuid()
const uniqueId = randomUUID()
this.sshKeyPath = path.join(runnerTemp, uniqueId)
stateHelper.setSshKeyPath(this.sshKeyPath)
await fs.promises.mkdir(runnerTemp, {recursive: true})
@@ -422,7 +422,7 @@ class GitAuthHelper {
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
// Create a unique filename for this checkout instance
const configFileName = `git-credentials-${uuid()}.config`
const configFileName = `git-credentials-${randomUUID()}.config`
this.credentialsConfigPath = path.join(runnerTemp, configFileName)
core.debug(`Credentials config path: ${this.credentialsConfigPath}`)
+6 -6
View File
@@ -1,13 +1,13 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import * as fs from 'fs'
import * as fshelper from './fs-helper'
import * as fshelper from './fs-helper.js'
import * as io from '@actions/io'
import * as path from 'path'
import * as refHelper from './ref-helper'
import * as regexpHelper from './regexp-helper'
import * as retryHelper from './retry-helper'
import {GitVersion} from './git-version'
import * as refHelper from './ref-helper.js'
import * as regexpHelper from './regexp-helper.js'
import * as retryHelper from './retry-helper.js'
import {GitVersion} from './git-version.js'
// Auth header not supported before 2.9
// Wire protocol v2 not supported before 2.18
@@ -510,7 +510,7 @@ class GitCommandManager {
} else {
args.push(globalConfig ? '--global' : '--local')
}
args.push('--unset', configKey, configValue)
args.push('--unset', configKey, regexpHelper.escape(configValue))
const output = await this.execGit(args, true)
return output.exitCode === 0
+2 -2
View File
@@ -1,10 +1,10 @@
import * as assert from 'assert'
import * as core from '@actions/core'
import * as fs from 'fs'
import * as fsHelper from './fs-helper'
import * as fsHelper from './fs-helper.js'
import * as io from '@actions/io'
import * as path from 'path'
import {IGitCommandManager} from './git-command-manager'
import {IGitCommandManager} from './git-command-manager.js'
export async function prepareExistingDirectory(
git: IGitCommandManager | undefined,
+10 -10
View File
@@ -1,19 +1,19 @@
import * as core from '@actions/core'
import * as fsHelper from './fs-helper'
import * as gitAuthHelper from './git-auth-helper'
import * as gitCommandManager from './git-command-manager'
import * as gitDirectoryHelper from './git-directory-helper'
import * as githubApiHelper from './github-api-helper'
import * as fsHelper from './fs-helper.js'
import * as gitAuthHelper from './git-auth-helper.js'
import * as gitCommandManager from './git-command-manager.js'
import * as gitDirectoryHelper from './git-directory-helper.js'
import * as githubApiHelper from './github-api-helper.js'
import * as io from '@actions/io'
import * as path from 'path'
import * as refHelper from './ref-helper'
import * as stateHelper from './state-helper'
import * as urlHelper from './url-helper'
import * as refHelper from './ref-helper.js'
import * as stateHelper from './state-helper.js'
import * as urlHelper from './url-helper.js'
import {
MinimumGitSparseCheckoutVersion,
IGitCommandManager
} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings'
} from './git-command-manager.js'
import {IGitSourceSettings} from './git-source-settings.js'
export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Repository URL
+6
View File
@@ -118,4 +118,10 @@ export interface IGitSourceSettings {
* User override on the GitHub Server/Host URL that hosts the repository to be cloned
*/
githubServerUrl: string | undefined
/**
* Opt-in to allow checking out fork pull request code from a workflow
* triggered by pull_request_target or workflow_run.
*/
allowUnsafePrCheckout: boolean
}
+4 -4
View File
@@ -4,10 +4,10 @@ import * as fs from 'fs'
import * as github from '@actions/github'
import * as io from '@actions/io'
import * as path from 'path'
import * as retryHelper from './retry-helper'
import * as retryHelper from './retry-helper.js'
import * as toolCache from '@actions/tool-cache'
import {v4 as uuid} from 'uuid'
import {getServerApiUrl} from './url-helper'
import {randomUUID} from 'crypto'
import {getServerApiUrl} from './url-helper.js'
const IS_WINDOWS = process.platform === 'win32'
@@ -39,7 +39,7 @@ export async function downloadRepository(
// Write archive to disk
core.info('Writing archive to disk')
const uniqueId = uuid()
const uniqueId = randomUUID()
const archivePath = IS_WINDOWS
? path.join(repositoryPath, `${uniqueId}.zip`)
: path.join(repositoryPath, `${uniqueId}.tar.gz`)
+43 -5
View File
@@ -1,9 +1,10 @@
import * as core from '@actions/core'
import * as fsHelper from './fs-helper'
import * as fsHelper from './fs-helper.js'
import * as github from '@actions/github'
import * as path from 'path'
import * as workflowContextHelper from './workflow-context-helper'
import {IGitSourceSettings} from './git-source-settings'
import * as unsafePrCheckoutHelper from './unsafe-pr-checkout-helper.js'
import * as workflowContextHelper from './workflow-context-helper.js'
import {IGitSourceSettings} from './git-source-settings.js'
export async function getInputs(): Promise<IGitSourceSettings> {
const result = {} as unknown as IGitSourceSettings
@@ -58,6 +59,23 @@ export async function getInputs(): Promise<IGitSourceSettings> {
// Source branch, source version
result.ref = core.getInput('ref')
// core.getInput()'s default trim strips a range of Unicode characters such as a
// leading BOM (U+FEFF) or NBSP (U+00A0). Those are valid in a git ref name, so
// a fork branch named "<BOM>" + 40 hex chars would trim down to a bare SHA and
// be silently reclassified as a commit, bypassing the unsafe fork PR checkout
// guard.
//
// The trim below strips only the ASCII whitespace characters which are all forbidden
// in a git branch name.
// \t U+0009 horizontal tab - ASCII control, forbidden in ref names
// \n U+000A line feed - ASCII control, forbidden in ref names
// \v U+000B vertical tab - ASCII control, forbidden in ref names
// \f U+000C form feed - ASCII control, forbidden in ref names
// \r U+000D carriage return - ASCII control, forbidden in ref names
// ' ' U+0020 space - forbidden in ref names
const asciiTrimmedRef = core
.getInput('ref', {trimWhitespace: false})
.replace(/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g, '')
if (!result.ref) {
if (isWorkflowRepository) {
result.ref = github.context.ref
@@ -71,8 +89,8 @@ export async function getInputs(): Promise<IGitSourceSettings> {
}
}
// SHA?
else if (result.ref.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) {
result.commit = result.ref
else if (asciiTrimmedRef.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) {
result.commit = asciiTrimmedRef
result.ref = ''
}
core.debug(`ref = '${result.ref}'`)
@@ -161,5 +179,25 @@ export async function getInputs(): Promise<IGitSourceSettings> {
result.githubServerUrl = core.getInput('github-server-url')
core.debug(`GitHub Host URL = ${result.githubServerUrl}`)
// Allow unsafe PR checkout (opt-in for pull_request_target / workflow_run fork PRs)
result.allowUnsafePrCheckout =
(core.getInput('allow-unsafe-pr-checkout') || 'false').toUpperCase() ===
'TRUE'
core.debug(`allow unsafe PR checkout = ${result.allowUnsafePrCheckout}`)
// The default self-checkout (this repository with no explicit ref) always
// resolves to the trusted ref/commit GitHub set for the triggering event, so
// the fork-checkout guard only needs to run when the caller customized the
// repository or ref.
const isDefaultCheckout = isWorkflowRepository && !core.getInput('ref')
if (!isDefaultCheckout) {
unsafePrCheckoutHelper.assertSafePrCheckout({
qualifiedRepository,
ref: result.ref,
commit: result.commit,
allowUnsafePrCheckout: result.allowUnsafePrCheckout
})
}
return result
}
+9 -9
View File
@@ -1,9 +1,11 @@
import * as core from '@actions/core'
import * as coreCommand from '@actions/core/lib/command'
import * as gitSourceProvider from './git-source-provider'
import * as inputHelper from './input-helper'
import * as gitSourceProvider from './git-source-provider.js'
import * as inputHelper from './input-helper.js'
import * as path from 'path'
import * as stateHelper from './state-helper'
import * as stateHelper from './state-helper.js'
import {fileURLToPath} from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
async function run(): Promise<void> {
try {
@@ -11,10 +13,8 @@ async function run(): Promise<void> {
try {
// Register problem matcher
coreCommand.issueCommand(
'add-matcher',
{},
path.join(__dirname, 'problem-matcher.json')
core.info(
`::add-matcher::${path.join(__dirname, 'problem-matcher.json')}`
)
// Get sources
@@ -22,7 +22,7 @@ async function run(): Promise<void> {
core.setOutput('ref', sourceSettings.ref)
} finally {
// Unregister problem matcher
coreCommand.issueCommand('remove-matcher', {owner: 'checkout-git'}, '')
core.info('::remove-matcher owner=checkout-git::')
}
} catch (error) {
core.setFailed(`${(error as any)?.message ?? error}`)
+4 -1
View File
@@ -2,6 +2,9 @@ import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as yaml from 'js-yaml'
import {fileURLToPath} from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
//
// SUMMARY
@@ -120,7 +123,7 @@ function updateUsage(
}
updateUsage(
'actions/checkout@v6',
'actions/checkout@v7',
path.join(__dirname, '..', '..', 'action.yml'),
path.join(__dirname, '..', '..', 'README.md')
)
+3 -3
View File
@@ -1,7 +1,7 @@
import {IGitCommandManager} from './git-command-manager'
import {IGitCommandManager} from './git-command-manager.js'
import * as core from '@actions/core'
import * as github from '@actions/github'
import {getServerApiUrl, isGhes} from './url-helper'
import {getServerApiUrl, isGhes} from './url-helper.js'
export const tagsRefSpec = '+refs/tags/*:refs/tags/*'
@@ -292,7 +292,7 @@ export async function checkCommitInfo(
}
}
function fromPayload(path: string): any {
export function fromPayload(path: string): any {
return select(github.context.payload, path)
}
+88
View File
@@ -0,0 +1,88 @@
import * as github from '@actions/github'
import {fromPayload} from './ref-helper.js'
const PR_REF_PATTERN = /^refs\/pull\/[0-9]+\/(?:head|merge)$/
export interface IUnsafePrCheckoutInput {
qualifiedRepository: string
ref: string
commit: string | undefined
allowUnsafePrCheckout: boolean
}
export function assertSafePrCheckout(input: IUnsafePrCheckoutInput): void {
if (input.allowUnsafePrCheckout) {
return
}
const eventName = github.context.eventName
if (eventName !== 'pull_request_target' && eventName !== 'workflow_run') {
return
}
const baseRepoId = fromPayload('repository.id')
if (typeof baseRepoId !== 'number') {
return
}
let prHeadRepoId: unknown
let prHeadRepoFullName: unknown
const prShas: string[] = []
if (eventName === 'pull_request_target') {
prHeadRepoId = fromPayload('pull_request.head.repo.id')
prHeadRepoFullName = fromPayload('pull_request.head.repo.full_name')
pushIfSha(prShas, fromPayload('pull_request.head.sha'))
pushIfSha(prShas, fromPayload('pull_request.merge_commit_sha'))
} else {
const wrEvent = fromPayload('workflow_run.event')
if (typeof wrEvent !== 'string' || !wrEvent.startsWith('pull_request')) {
return
}
prHeadRepoId = fromPayload('workflow_run.head_repository.id')
prHeadRepoFullName = fromPayload('workflow_run.head_repository.full_name')
pushIfSha(prShas, fromPayload('workflow_run.head_commit.id'))
// For `pull_request_target`-triggered workflow_run, `head_sha` is the base
// default branch SHA (not the PR head)
if (wrEvent !== 'pull_request_target') {
pushIfSha(prShas, fromPayload('workflow_run.head_sha'))
}
}
// (A) Fork PR?
if (typeof prHeadRepoId !== 'number' || prHeadRepoId === baseRepoId) {
return
}
// (B) We cannot check for all fork PR refs so check to see
// if the resolved input points to the fork PR sha we have in the payload
const repositoryMatchesPrHead =
typeof prHeadRepoFullName === 'string' &&
input.qualifiedRepository.toLowerCase() === prHeadRepoFullName.toLowerCase()
const refMatchesPullPattern = PR_REF_PATTERN.test(input.ref)
const commitMatchesPrHeadSha =
!!input.commit && prShas.includes(input.commit.toLowerCase())
if (
!repositoryMatchesPrHead &&
!refMatchesPullPattern &&
!commitMatchesPrHeadSha
) {
return
}
throw new Error(
`Refusing to check out fork pull request code from a '${eventName}' workflow. ` +
`This workflow runs with the base repository's GITHUB_TOKEN, secrets, default-branch ` +
`cache scope, and runner access. Fetching and executing a fork's code in that trusted ` +
`context commonly leads to "pwn request" vulnerabilities. To opt in, review the risks ` +
`at https://gh.io/securely-using-pull_request_target and set 'allow-unsafe-pr-checkout: true' ` +
`on the actions/checkout step.`
)
}
function pushIfSha(target: string[], value: unknown): void {
if (typeof value === 'string' && value.length > 0) {
target.push(value.toLowerCase())
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import * as assert from 'assert'
import {URL} from 'url'
import {IGitSourceSettings} from './git-source-settings'
import {IGitSourceSettings} from './git-source-settings.js'
export function getFetchUrl(settings: IGitSourceSettings): string {
assert.ok(
+5 -9
View File
@@ -1,17 +1,13 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"es6"
],
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./lib",
"rootDir": "./src",
"declaration": true,
"strict": true,
"noImplicitAny": false,
"esModuleInterop": true,
"skipLibCheck": true
"esModuleInterop": true
},
"exclude": ["__test__", "lib", "node_modules"]
"exclude": ["__test__", "lib", "node_modules", "jest.config.ts"]
}