Compare commits
96 Commits
Author | SHA1 | Date | |
---|---|---|---|
2541b1294d | |||
0ffe6f9c55 | |||
dcd71f6466 | |||
add3486cc3 | |||
5126516654 | |||
d50f8ea767 | |||
2d1c1198e7 | |||
a12a3943b4 | |||
8f9e05e482 | |||
230611dbd0 | |||
ec3a7ce113 | |||
fd47087372 | |||
3d677ac575 | |||
826ba42d6c | |||
eb8a193c1d | |||
c49af7ca1f | |||
1e204e9a92 | |||
0299a0d2b6 | |||
be0f448456 | |||
56c00a7b1f | |||
85e47d1a2b | |||
3fc17f8645 | |||
e3bc06d986 | |||
442567ba57 | |||
7f00b66d06 | |||
eccf386318 | |||
2bd2911be9 | |||
afe4af09a7 | |||
25a956c84d | |||
5a4ac9002d | |||
c952173edf | |||
a81bbbf829 | |||
21dc310f19 | |||
be6c44d969 | |||
dac8cc78a1 | |||
2036a08e25 | |||
592cf69a22 | |||
a4b69b4886 | |||
1433f62caa | |||
61b9e3751b | |||
28c7f3d2b5 | |||
fb6f360df2 | |||
b4483adec3 | |||
00a3be8934 | |||
453ee27fca | |||
65865e15a1 | |||
aabbfeb2ce | |||
e52d022eb5 | |||
2ff2fbdea4 | |||
df86c829eb | |||
97b30c411c | |||
86f86b36ef | |||
7523e23789 | |||
ac455590d1 | |||
94c2de77cc | |||
01aecccf73 | |||
85b1f35505 | |||
574281d34c | |||
fbb30c60ab | |||
58070a9fc3 | |||
9a3a9ade82 | |||
b2e6b7ed13 | |||
80602fafba | |||
b4626ce19c | |||
422dc45671 | |||
204620207c | |||
f219062370 | |||
096e927750 | |||
f858c22e96 | |||
77904fd431 | |||
06218e4404 | |||
61fd8fd0c7 | |||
f95f2a3856 | |||
f90c7b395d | |||
090d9c9dfd | |||
db41740e12 | |||
bc50a995b8 | |||
dfd70d4a2d | |||
ae525b2262 | |||
f466b96953 | |||
c85684db76 | |||
299dd5064e | |||
722adc63f1 | |||
3537747199 | |||
a6747255bd | |||
c170eefc26 | |||
a572f640b0 | |||
cab31617d8 | |||
5881116d18 | |||
7990b10a0c | |||
01a434328a | |||
4817b449b0 | |||
689bf84be4 | |||
cc70598ce8 | |||
8461dbfed3 | |||
e347bba93b |
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
dist/
|
||||
lib/
|
||||
node_modules/
|
53
.eslintrc.json
Normal file
53
.eslintrc.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
.licenses/** -diff linguist-generated=true
|
51
.github/workflows/check-dist.yml
vendored
Normal file
51
.github/workflows/check-dist.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
# `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@v3
|
||||
|
||||
- name: Set Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.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@v2
|
||||
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
58
.github/workflows/codeql-analysis.yml
vendored
Normal file
58
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
# 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@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
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@v1
|
14
.github/workflows/licensed.yml
vendored
Normal file
14
.github/workflows/licensed.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: Licensed
|
||||
|
||||
on:
|
||||
push: {branches: main}
|
||||
pull_request: {branches: main}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check licenses
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: npm ci
|
||||
- run: npm run licensed-check
|
242
.github/workflows/test.yml
vendored
242
.github/workflows/test.yml
vendored
@ -1,19 +1,245 @@
|
||||
name: "test-local"
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- 'releases/*'
|
||||
- main
|
||||
- releases/*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
- uses: actions/checkout@v3
|
||||
- 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:
|
||||
os: [windows-latest, ubuntu-latest, macOS-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: ./
|
||||
# Clone this repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Basic checkout
|
||||
- name: Checkout basic
|
||||
uses: ./
|
||||
with:
|
||||
ref: master
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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: alpine/git:latest
|
||||
options: --dns 127.0.0.1
|
||||
services:
|
||||
squid-proxy:
|
||||
image: datadog/squid:latest
|
||||
ports:
|
||||
- 3128:3128
|
||||
env:
|
||||
https_proxy: http://squid-proxy:3128
|
||||
steps:
|
||||
# Clone this repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 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@v3
|
||||
|
||||
# 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@v3
|
||||
with:
|
||||
path: v3
|
||||
|
||||
# Basic checkout using git
|
||||
- name: Checkout basic
|
||||
uses: ./v3
|
||||
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 v3
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: v3
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
__test__/_temp
|
||||
_temp/
|
||||
lib/
|
||||
node_modules/
|
14
.licensed.yml
Normal file
14
.licensed.yml
Normal file
@ -0,0 +1,14 @@
|
||||
sources:
|
||||
npm: true
|
||||
|
||||
allowed:
|
||||
- apache-2.0
|
||||
- bsd-2-clause
|
||||
- bsd-3-clause
|
||||
- isc
|
||||
- mit
|
||||
- cc0-1.0
|
||||
- unlicense
|
||||
|
||||
reviewed:
|
||||
npm:
|
BIN
.licenses/npm/@actions/core.dep.yml
generated
Normal file
BIN
.licenses/npm/@actions/core.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@actions/exec.dep.yml
generated
Normal file
BIN
.licenses/npm/@actions/exec.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@actions/github.dep.yml
generated
Normal file
BIN
.licenses/npm/@actions/github.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@actions/http-client.dep.yml
generated
Normal file
BIN
.licenses/npm/@actions/http-client.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@actions/io.dep.yml
generated
Normal file
BIN
.licenses/npm/@actions/io.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@actions/tool-cache.dep.yml
generated
Normal file
BIN
.licenses/npm/@actions/tool-cache.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/auth-token.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/auth-token.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/endpoint.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/endpoint.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/graphql.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/graphql.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/plugin-paginate-rest.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/plugin-paginate-rest.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/plugin-request-log.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/plugin-request-log.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/plugin-rest-endpoint-methods.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/plugin-rest-endpoint-methods.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/request-error-1.2.1.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/request-error-1.2.1.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/request-error-2.0.0.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/request-error-2.0.0.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/request.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/request.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/rest.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/rest.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@octokit/types.dep.yml
generated
Normal file
BIN
.licenses/npm/@octokit/types.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/@types/node.dep.yml
generated
Normal file
BIN
.licenses/npm/@types/node.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/atob-lite.dep.yml
generated
Normal file
BIN
.licenses/npm/atob-lite.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/before-after-hook.dep.yml
generated
Normal file
BIN
.licenses/npm/before-after-hook.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/btoa-lite.dep.yml
generated
Normal file
BIN
.licenses/npm/btoa-lite.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/call-bind.dep.yml
generated
Normal file
BIN
.licenses/npm/call-bind.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/cross-spawn.dep.yml
generated
Normal file
BIN
.licenses/npm/cross-spawn.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/deprecation.dep.yml
generated
Normal file
BIN
.licenses/npm/deprecation.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/end-of-stream.dep.yml
generated
Normal file
BIN
.licenses/npm/end-of-stream.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/execa.dep.yml
generated
Normal file
BIN
.licenses/npm/execa.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/function-bind.dep.yml
generated
Normal file
BIN
.licenses/npm/function-bind.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/get-intrinsic.dep.yml
generated
Normal file
BIN
.licenses/npm/get-intrinsic.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/get-stream.dep.yml
generated
Normal file
BIN
.licenses/npm/get-stream.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/has-symbols.dep.yml
generated
Normal file
BIN
.licenses/npm/has-symbols.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/has.dep.yml
generated
Normal file
BIN
.licenses/npm/has.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/is-plain-object.dep.yml
generated
Normal file
BIN
.licenses/npm/is-plain-object.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/is-stream.dep.yml
generated
Normal file
BIN
.licenses/npm/is-stream.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/isexe.dep.yml
generated
Normal file
BIN
.licenses/npm/isexe.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/isobject.dep.yml
generated
Normal file
BIN
.licenses/npm/isobject.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/lodash.get.dep.yml
generated
Normal file
BIN
.licenses/npm/lodash.get.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/lodash.set.dep.yml
generated
Normal file
BIN
.licenses/npm/lodash.set.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/lodash.uniq.dep.yml
generated
Normal file
BIN
.licenses/npm/lodash.uniq.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/macos-release.dep.yml
generated
Normal file
BIN
.licenses/npm/macos-release.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/nice-try.dep.yml
generated
Normal file
BIN
.licenses/npm/nice-try.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/node-fetch.dep.yml
generated
Normal file
BIN
.licenses/npm/node-fetch.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/npm-run-path.dep.yml
generated
Normal file
BIN
.licenses/npm/npm-run-path.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/object-inspect.dep.yml
generated
Normal file
BIN
.licenses/npm/object-inspect.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/octokit-pagination-methods.dep.yml
generated
Normal file
BIN
.licenses/npm/octokit-pagination-methods.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/once.dep.yml
generated
Normal file
BIN
.licenses/npm/once.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/os-name.dep.yml
generated
Normal file
BIN
.licenses/npm/os-name.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/p-finally.dep.yml
generated
Normal file
BIN
.licenses/npm/p-finally.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/path-key.dep.yml
generated
Normal file
BIN
.licenses/npm/path-key.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/pump.dep.yml
generated
Normal file
BIN
.licenses/npm/pump.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/qs.dep.yml
generated
Normal file
BIN
.licenses/npm/qs.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/semver-5.7.1.dep.yml
generated
Normal file
BIN
.licenses/npm/semver-5.7.1.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/semver-6.3.0.dep.yml
generated
Normal file
BIN
.licenses/npm/semver-6.3.0.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/shebang-command.dep.yml
generated
Normal file
BIN
.licenses/npm/shebang-command.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/shebang-regex.dep.yml
generated
Normal file
BIN
.licenses/npm/shebang-regex.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/side-channel.dep.yml
generated
Normal file
BIN
.licenses/npm/side-channel.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/signal-exit.dep.yml
generated
Normal file
BIN
.licenses/npm/signal-exit.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/strip-eof.dep.yml
generated
Normal file
BIN
.licenses/npm/strip-eof.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/tr46.dep.yml
generated
Normal file
BIN
.licenses/npm/tr46.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/tunnel.dep.yml
generated
Normal file
BIN
.licenses/npm/tunnel.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/typed-rest-client.dep.yml
generated
Normal file
BIN
.licenses/npm/typed-rest-client.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/underscore.dep.yml
generated
Normal file
BIN
.licenses/npm/underscore.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/universal-user-agent-4.0.1.dep.yml
generated
Normal file
BIN
.licenses/npm/universal-user-agent-4.0.1.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/universal-user-agent-5.0.0.dep.yml
generated
Normal file
BIN
.licenses/npm/universal-user-agent-5.0.0.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/uuid.dep.yml
generated
Normal file
BIN
.licenses/npm/uuid.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/webidl-conversions.dep.yml
generated
Normal file
BIN
.licenses/npm/webidl-conversions.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/whatwg-url.dep.yml
generated
Normal file
BIN
.licenses/npm/whatwg-url.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/which.dep.yml
generated
Normal file
BIN
.licenses/npm/which.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/windows-release.dep.yml
generated
Normal file
BIN
.licenses/npm/windows-release.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/wrappy.dep.yml
generated
Normal file
BIN
.licenses/npm/wrappy.dep.yml
generated
Normal file
Binary file not shown.
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@ -0,0 +1,3 @@
|
||||
dist/
|
||||
lib/
|
||||
node_modules/
|
11
.prettierrc.json
Normal file
11
.prettierrc.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid",
|
||||
"parser": "typescript"
|
||||
}
|
71
CHANGELOG.md
71
CHANGELOG.md
@ -1,13 +1,68 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased Changes
|
||||
- N/A
|
||||
## v3.0.2
|
||||
- [Add input `set-safe-directory`](https://github.com/actions/checkout/pull/770)
|
||||
|
||||
## v1.2.0
|
||||
- Reverted the breaking behavior change in v1.1.0 that broke custom authentication flows
|
||||
## v3.0.1
|
||||
- [Fixed an issue where checkout failed to run in container jobs due to the new git setting `safe.directory`](https://github.com/actions/checkout/pull/762)
|
||||
- [Bumped various npm package versions](https://github.com/actions/checkout/pull/744)
|
||||
|
||||
## v1.1.0 (Not reccomended for use, this functionality will be ported to the 2.0 update)
|
||||
- Persist `with.token` or `${{ github.token }}` into checkout repository's git config as `http.https://github.com/.extraheader=AUTHORIZATION: basic ***` to better support scripting git
|
||||
## v3.0.0
|
||||
|
||||
## v1.0.0
|
||||
- Initial Release of the checkout action
|
||||
- [Update to node 16](https://github.com/actions/checkout/pull/689)
|
||||
|
||||
## v2.3.1
|
||||
|
||||
- [Fix default branch resolution for .wiki and when using SSH](https://github.com/actions/checkout/pull/284)
|
||||
|
||||
## v2.3.0
|
||||
|
||||
- [Fallback to the default branch](https://github.com/actions/checkout/pull/278)
|
||||
|
||||
## v2.2.0
|
||||
|
||||
- [Fetch all history for all tags and branches when fetch-depth=0](https://github.com/actions/checkout/pull/258)
|
||||
|
||||
## v2.1.1
|
||||
|
||||
- Changes to support GHES ([here](https://github.com/actions/checkout/pull/236) and [here](https://github.com/actions/checkout/pull/248))
|
||||
|
||||
## v2.1.0
|
||||
|
||||
- [Group output](https://github.com/actions/checkout/pull/191)
|
||||
- [Changes to support GHES alpha release](https://github.com/actions/checkout/pull/199)
|
||||
- [Persist core.sshCommand for submodules](https://github.com/actions/checkout/pull/184)
|
||||
- [Add support ssh](https://github.com/actions/checkout/pull/163)
|
||||
- [Convert submodule SSH URL to HTTPS, when not using SSH](https://github.com/actions/checkout/pull/179)
|
||||
- [Add submodule support](https://github.com/actions/checkout/pull/157)
|
||||
- [Follow proxy settings](https://github.com/actions/checkout/pull/144)
|
||||
- [Fix ref for pr closed event when a pr is merged](https://github.com/actions/checkout/pull/141)
|
||||
- [Fix issue checking detached when git less than 2.22](https://github.com/actions/checkout/pull/128)
|
||||
|
||||
## v2.0.0
|
||||
|
||||
- [Do not pass cred on command line](https://github.com/actions/checkout/pull/108)
|
||||
- [Add input persist-credentials](https://github.com/actions/checkout/pull/107)
|
||||
- [Fallback to REST API to download repo](https://github.com/actions/checkout/pull/104)
|
||||
|
||||
## v2 (beta)
|
||||
|
||||
- Improved fetch performance
|
||||
- The default behavior now fetches only the SHA being checked-out
|
||||
- Script authenticated git commands
|
||||
- Persists `with.token` in the local git config
|
||||
- Enables your scripts to run authenticated git commands
|
||||
- Post-job cleanup removes the token
|
||||
- Coming soon: Opt out by setting `with.persist-credentials` to `false`
|
||||
- Creates a local branch
|
||||
- No longer detached HEAD when checking out a branch
|
||||
- A local branch is created with the corresponding upstream branch set
|
||||
- Improved layout
|
||||
- `with.path` is always relative to `github.workspace`
|
||||
- Aligns better with container actions, where `github.workspace` gets mapped in
|
||||
- Removed input `submodules`
|
||||
|
||||
|
||||
## v1
|
||||
|
||||
Refer [here](https://github.com/actions/checkout/blob/v1/CHANGELOG.md) for the V1 changelog
|
||||
|
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@ -0,0 +1 @@
|
||||
* @actions/actions-runtime
|
246
README.md
246
README.md
@ -2,60 +2,228 @@
|
||||
<a href="https://github.com/actions/checkout"><img alt="GitHub Actions status" src="https://github.com/actions/checkout/workflows/test-local/badge.svg"></a>
|
||||
</p>
|
||||
|
||||
# Checkout
|
||||
# Checkout V3
|
||||
|
||||
This action checks out your repository to `$GITHUB_WORKSPACE`, so that your workflow can access the contents of your repository.
|
||||
This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it.
|
||||
|
||||
By default, this is equivalent to running `git fetch` and `git checkout $GITHUB_SHA`, so that you'll always have your repo contents at the version that triggered the workflow.
|
||||
See [here](https://help.github.com/en/articles/events-that-trigger-workflows) to learn what `$GITHUB_SHA` is for different kinds of events.
|
||||
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://help.github.com/en/articles/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.
|
||||
|
||||
# What's new
|
||||
|
||||
- Updated to the node16 runtime by default
|
||||
- This requires a minimum [Actions Runner](https://github.com/actions/runner/releases/tag/v2.285.0) version of v2.285.0 to run, which is by default available in GHES 3.4 or later.
|
||||
|
||||
# Usage
|
||||
|
||||
See [action.yml](action.yml)
|
||||
<!-- start usage -->
|
||||
```yaml
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# Repository name with owner. For example, actions/checkout
|
||||
# Default: ${{ github.repository }}
|
||||
repository: ''
|
||||
|
||||
Basic:
|
||||
# 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: ''
|
||||
|
||||
# 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: ''
|
||||
|
||||
# Number of commits to fetch. 0 indicates all history for all branches and tags.
|
||||
# Default: 1
|
||||
fetch-depth: ''
|
||||
|
||||
# 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: ''
|
||||
```
|
||||
<!-- end usage -->
|
||||
|
||||
# Scenarios
|
||||
|
||||
- [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)
|
||||
|
||||
## Fetch all history for all tags and branches
|
||||
|
||||
```yaml
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
```
|
||||
|
||||
## Checkout a different branch
|
||||
|
||||
```yaml
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: my-branch
|
||||
```
|
||||
|
||||
## Checkout HEAD^
|
||||
|
||||
```yaml
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- run: git checkout HEAD^
|
||||
```
|
||||
|
||||
## Checkout multiple repos (side by side)
|
||||
|
||||
```yaml
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: main
|
||||
|
||||
- name: Checkout tools repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: my-org/my-tools
|
||||
path: my-tools
|
||||
```
|
||||
|
||||
## Checkout multiple repos (nested)
|
||||
|
||||
```yaml
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout tools repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: my-org/my-tools
|
||||
path: my-tools
|
||||
```
|
||||
|
||||
## Checkout multiple repos (private)
|
||||
|
||||
```yaml
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: main
|
||||
|
||||
- name: Checkout private tools
|
||||
uses: actions/checkout@v3
|
||||
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@v3
|
||||
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@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10.x
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
- uses: actions/checkout@v3
|
||||
```
|
||||
|
||||
By default, the branch or tag ref that triggered the workflow will be checked out. If you wish to check out a different branch, a different repository or use different token to checkout, specify that using `with.ref`, `with.repository` and `with.token`.
|
||||
## Push a commit using the built-in token
|
||||
|
||||
## Checkout different branch from the workflow repository
|
||||
```yaml
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
ref: some-branch
|
||||
on: push
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
date > generated.txt
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git add .
|
||||
git commit -m "generated"
|
||||
git push
|
||||
```
|
||||
|
||||
## Checkout different private repository
|
||||
```yaml
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
repository: myAccount/myRepository
|
||||
ref: refs/heads/master
|
||||
token: ${{ secrets.GitHub_PAT }} # `GitHub_PAT` is a secret contains your PAT.
|
||||
```
|
||||
> - `${{ github.token }}` is scoped to the current repository, so if you want to checkout another 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 private submodules
|
||||
```yaml
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true # 'recursive' 'true' or 'false'
|
||||
token: ${{ secrets.GitHub_PAT }} # `GitHub_PAT` is a secret contains your PAT.
|
||||
```
|
||||
> - Private submodules must be configured via `https` not `ssh`.
|
||||
> - `${{ github.token }}` only has permission to the workflow triggering repository. If the repository contains any submodules that come from private repositories, you will need to add your [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) as secret and use the secret in `with.token` to make the `checkout` action work.
|
||||
|
||||
For more details, see [Contexts and expression syntax for GitHub Actions](https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions) and [Creating and using encrypted secrets](https://help.github.com/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
|
||||
|
||||
# License
|
||||
|
||||
The scripts and documentation in this project are released under the [MIT License](LICENSE)
|
||||
|
810
__test__/git-auth-helper.test.ts
Normal file
810
__test__/git-auth-helper.test.ts
Normal file
@ -0,0 +1,810 @@
|
||||
import * as core from '@actions/core'
|
||||
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'
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper')
|
||||
const originalRunnerTemp = process.env['RUNNER_TEMP']
|
||||
const originalHome = process.env['HOME']
|
||||
let workspace: string
|
||||
let localGitConfigPath: string
|
||||
let globalGitConfigPath: string
|
||||
let runnerTemp: string
|
||||
let tempHomedir: string
|
||||
let git: IGitCommandManager & {env: {[key: string]: string}}
|
||||
let settings: IGitSourceSettings
|
||||
let sshPath: string
|
||||
|
||||
describe('git-auth-helper tests', () => {
|
||||
beforeAll(async () => {
|
||||
// SSH
|
||||
sshPath = await io.which('ssh')
|
||||
|
||||
// Clear test workspace
|
||||
await io.rmRF(testWorkspace)
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Unregister mocks
|
||||
jest.restoreAllMocks()
|
||||
|
||||
// Restore HOME
|
||||
if (originalHome) {
|
||||
process.env['HOME'] = originalHome
|
||||
} else {
|
||||
delete process.env['HOME']
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
// Restore RUNNER_TEMP
|
||||
delete process.env['RUNNER_TEMP']
|
||||
if (originalRunnerTemp) {
|
||||
process.env['RUNNER_TEMP'] = originalRunnerTemp
|
||||
}
|
||||
})
|
||||
|
||||
const configureAuth_configuresAuthHeader =
|
||||
'configureAuth configures auth header'
|
||||
it(configureAuth_configuresAuthHeader, async () => {
|
||||
// Arrange
|
||||
await setup(configureAuth_configuresAuthHeader)
|
||||
expect(settings.authToken).toBeTruthy() // sanity check
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert config
|
||||
const configContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${settings.authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse =
|
||||
'configureAuth configures auth header even when persist credentials false'
|
||||
it(
|
||||
configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse,
|
||||
async () => {
|
||||
// Arrange
|
||||
await setup(
|
||||
configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse
|
||||
)
|
||||
expect(settings.authToken).toBeTruthy() // sanity check
|
||||
settings.persistCredentials = false
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert config
|
||||
const configContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
}
|
||||
)
|
||||
|
||||
const configureAuth_copiesUserKnownHosts =
|
||||
'configureAuth copies user known hosts'
|
||||
it(configureAuth_copiesUserKnownHosts, async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${configureAuth_copiesUserKnownHosts}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arange
|
||||
await setup(configureAuth_copiesUserKnownHosts)
|
||||
expect(settings.sshKey).toBeTruthy() // sanity check
|
||||
|
||||
// Mock fs.promises.readFile
|
||||
const realReadFile = fs.promises.readFile
|
||||
jest.spyOn(fs.promises, 'readFile').mockImplementation(
|
||||
async (file: any, options: any): Promise<Buffer> => {
|
||||
const userKnownHostsPath = path.join(
|
||||
os.homedir(),
|
||||
'.ssh',
|
||||
'known_hosts'
|
||||
)
|
||||
if (file === userKnownHostsPath) {
|
||||
return Buffer.from('some-domain.com ssh-rsa ABCDEF')
|
||||
}
|
||||
|
||||
return await realReadFile(file, options)
|
||||
}
|
||||
)
|
||||
|
||||
// Act
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert known hosts
|
||||
const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
|
||||
const actualSshKnownHostsContent = (
|
||||
await fs.promises.readFile(actualSshKnownHostsPath)
|
||||
).toString()
|
||||
expect(actualSshKnownHostsContent).toMatch(
|
||||
/some-domain\.com ssh-rsa ABCDEF/
|
||||
)
|
||||
expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
|
||||
})
|
||||
|
||||
const configureAuth_registersBasicCredentialAsSecret =
|
||||
'configureAuth registers basic credential as secret'
|
||||
it(configureAuth_registersBasicCredentialAsSecret, async () => {
|
||||
// Arrange
|
||||
await setup(configureAuth_registersBasicCredentialAsSecret)
|
||||
expect(settings.authToken).toBeTruthy() // sanity check
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert secret
|
||||
const setSecretSpy = core.setSecret as jest.Mock<any, any>
|
||||
expect(setSecretSpy).toHaveBeenCalledTimes(1)
|
||||
const expectedSecret = Buffer.from(
|
||||
`x-access-token:${settings.authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret)
|
||||
})
|
||||
|
||||
const setsSshCommandEnvVarWhenPersistCredentialsFalse =
|
||||
'sets SSH command env var when persist-credentials false'
|
||||
it(setsSshCommandEnvVarWhenPersistCredentialsFalse, async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${setsSshCommandEnvVarWhenPersistCredentialsFalse}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arrange
|
||||
await setup(setsSshCommandEnvVarWhenPersistCredentialsFalse)
|
||||
settings.persistCredentials = false
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert git env var
|
||||
const actualKeyPath = await getActualSshKeyPath()
|
||||
const actualKnownHostsPath = await getActualSshKnownHostsPath()
|
||||
const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
|
||||
actualKeyPath
|
||||
)}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
|
||||
actualKnownHostsPath
|
||||
)}"`
|
||||
expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
|
||||
'GIT_SSH_COMMAND',
|
||||
expectedSshCommand
|
||||
)
|
||||
|
||||
// Asserty git config
|
||||
const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(x => x)
|
||||
expect(gitConfigLines).toHaveLength(1)
|
||||
expect(gitConfigLines[0]).toMatch(/^http\./)
|
||||
})
|
||||
|
||||
const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
|
||||
'sets SSH command when persist-credentials true'
|
||||
it(configureAuth_setsSshCommandWhenPersistCredentialsTrue, async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${configureAuth_setsSshCommandWhenPersistCredentialsTrue}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arrange
|
||||
await setup(configureAuth_setsSshCommandWhenPersistCredentialsTrue)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert git env var
|
||||
const actualKeyPath = await getActualSshKeyPath()
|
||||
const actualKnownHostsPath = await getActualSshKnownHostsPath()
|
||||
const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
|
||||
actualKeyPath
|
||||
)}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
|
||||
actualKnownHostsPath
|
||||
)}"`
|
||||
expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
|
||||
'GIT_SSH_COMMAND',
|
||||
expectedSshCommand
|
||||
)
|
||||
|
||||
// Asserty git config
|
||||
expect(git.config).toHaveBeenCalledWith(
|
||||
'core.sshCommand',
|
||||
expectedSshCommand
|
||||
)
|
||||
})
|
||||
|
||||
const configureAuth_writesExplicitKnownHosts = 'writes explicit known hosts'
|
||||
it(configureAuth_writesExplicitKnownHosts, async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${configureAuth_writesExplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arrange
|
||||
await setup(configureAuth_writesExplicitKnownHosts)
|
||||
expect(settings.sshKey).toBeTruthy() // sanity check
|
||||
settings.sshKnownHosts = 'my-custom-host.com ssh-rsa ABC123'
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert known hosts
|
||||
const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
|
||||
const actualSshKnownHostsContent = (
|
||||
await fs.promises.readFile(actualSshKnownHostsPath)
|
||||
).toString()
|
||||
expect(actualSshKnownHostsContent).toMatch(
|
||||
/my-custom-host\.com ssh-rsa ABC123/
|
||||
)
|
||||
expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
|
||||
})
|
||||
|
||||
const configureAuth_writesSshKeyAndImplicitKnownHosts =
|
||||
'writes SSH key and implicit known hosts'
|
||||
it(configureAuth_writesSshKeyAndImplicitKnownHosts, async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${configureAuth_writesSshKeyAndImplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arrange
|
||||
await setup(configureAuth_writesSshKeyAndImplicitKnownHosts)
|
||||
expect(settings.sshKey).toBeTruthy() // sanity check
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert SSH key
|
||||
const actualSshKeyPath = await getActualSshKeyPath()
|
||||
expect(actualSshKeyPath).toBeTruthy()
|
||||
const actualSshKeyContent = (
|
||||
await fs.promises.readFile(actualSshKeyPath)
|
||||
).toString()
|
||||
expect(actualSshKeyContent).toBe(settings.sshKey + '\n')
|
||||
if (!isWindows) {
|
||||
// Assert read/write for user, not group or others.
|
||||
// Otherwise SSH client will error.
|
||||
expect((await fs.promises.stat(actualSshKeyPath)).mode & 0o777).toBe(
|
||||
0o600
|
||||
)
|
||||
}
|
||||
|
||||
// Assert known hosts
|
||||
const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
|
||||
const actualSshKnownHostsContent = (
|
||||
await fs.promises.readFile(actualSshKnownHostsPath)
|
||||
).toString()
|
||||
expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
|
||||
})
|
||||
|
||||
const configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet =
|
||||
'configureGlobalAuth configures URL insteadOf when SSH key not set'
|
||||
it(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet, async () => {
|
||||
// Arrange
|
||||
await setup(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet)
|
||||
settings.sshKey = ''
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
await authHelper.configureGlobalAuth()
|
||||
|
||||
// Assert temporary global config
|
||||
expect(git.env['HOME']).toBeTruthy()
|
||||
const configContent = (
|
||||
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
|
||||
).toString()
|
||||
expect(
|
||||
configContent.indexOf(`url.https://github.com/.insteadOf git@github.com`)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
const configureGlobalAuth_copiesGlobalGitConfig =
|
||||
'configureGlobalAuth copies global git config'
|
||||
it(configureGlobalAuth_copiesGlobalGitConfig, async () => {
|
||||
// Arrange
|
||||
await setup(configureGlobalAuth_copiesGlobalGitConfig)
|
||||
await fs.promises.writeFile(globalGitConfigPath, 'value-from-global-config')
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
await authHelper.configureGlobalAuth()
|
||||
|
||||
// Assert original global config not altered
|
||||
let configContent = (
|
||||
await fs.promises.readFile(globalGitConfigPath)
|
||||
).toString()
|
||||
expect(configContent).toBe('value-from-global-config')
|
||||
|
||||
// Assert temporary global config
|
||||
expect(git.env['HOME']).toBeTruthy()
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${settings.authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
configContent = (
|
||||
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
|
||||
).toString()
|
||||
expect(
|
||||
configContent.indexOf('value-from-global-config')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist =
|
||||
'configureGlobalAuth creates new git config when global does not exist'
|
||||
it(
|
||||
configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist,
|
||||
async () => {
|
||||
// Arrange
|
||||
await setup(
|
||||
configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist
|
||||
)
|
||||
await io.rmRF(globalGitConfigPath)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
await authHelper.configureGlobalAuth()
|
||||
|
||||
// Assert original global config not recreated
|
||||
try {
|
||||
await fs.promises.stat(globalGitConfigPath)
|
||||
throw new Error(
|
||||
`Did not expect file to exist: '${globalGitConfigPath}'`
|
||||
)
|
||||
} catch (err) {
|
||||
if ((err as any)?.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Assert temporary global config
|
||||
expect(git.env['HOME']).toBeTruthy()
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${settings.authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
const configContent = (
|
||||
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
|
||||
).toString()
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
}
|
||||
)
|
||||
|
||||
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet =
|
||||
'configureSubmoduleAuth configures submodules when persist credentials false and SSH key not set'
|
||||
it(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet,
|
||||
async () => {
|
||||
// Arrange
|
||||
await setup(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet
|
||||
)
|
||||
settings.persistCredentials = false
|
||||
settings.sshKey = ''
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
|
||||
mockSubmoduleForeach.mockClear() // reset calls
|
||||
|
||||
// Act
|
||||
await authHelper.configureSubmoduleAuth()
|
||||
|
||||
// Assert
|
||||
expect(mockSubmoduleForeach).toBeCalledTimes(1)
|
||||
expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
|
||||
/unset-all.*insteadOf/
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet =
|
||||
'configureSubmoduleAuth configures submodules when persist credentials false and SSH key set'
|
||||
it(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet,
|
||||
async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arrange
|
||||
await setup(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet
|
||||
)
|
||||
settings.persistCredentials = false
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
|
||||
mockSubmoduleForeach.mockClear() // reset calls
|
||||
|
||||
// Act
|
||||
await authHelper.configureSubmoduleAuth()
|
||||
|
||||
// Assert
|
||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(1)
|
||||
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
||||
/unset-all.*insteadOf/
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet =
|
||||
'configureSubmoduleAuth configures submodules when persist credentials true and SSH key not set'
|
||||
it(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet,
|
||||
async () => {
|
||||
// Arrange
|
||||
await setup(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet
|
||||
)
|
||||
settings.sshKey = ''
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
|
||||
mockSubmoduleForeach.mockClear() // reset calls
|
||||
|
||||
// Act
|
||||
await authHelper.configureSubmoduleAuth()
|
||||
|
||||
// Assert
|
||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4)
|
||||
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
||||
/unset-all.*insteadOf/
|
||||
)
|
||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
|
||||
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
|
||||
/url.*insteadOf.*git@github.com:/
|
||||
)
|
||||
expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch(
|
||||
/url.*insteadOf.*org-123456@github.com:/
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet =
|
||||
'configureSubmoduleAuth configures submodules when persist credentials true and SSH key set'
|
||||
it(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet,
|
||||
async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arrange
|
||||
await setup(
|
||||
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet
|
||||
)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
|
||||
mockSubmoduleForeach.mockClear() // reset calls
|
||||
|
||||
// Act
|
||||
await authHelper.configureSubmoduleAuth()
|
||||
|
||||
// Assert
|
||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
|
||||
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
||||
/unset-all.*insteadOf/
|
||||
)
|
||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
|
||||
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
|
||||
}
|
||||
)
|
||||
|
||||
const removeAuth_removesSshCommand = 'removeAuth removes SSH command'
|
||||
it(removeAuth_removesSshCommand, async () => {
|
||||
if (!sshPath) {
|
||||
process.stdout.write(
|
||||
`Skipped test "${removeAuth_removesSshCommand}". Executable 'ssh' not found in the PATH.\n`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Arrange
|
||||
await setup(removeAuth_removesSshCommand)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
let gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('core.sshCommand')).toBeGreaterThanOrEqual(
|
||||
0
|
||||
) // sanity check
|
||||
const actualKeyPath = await getActualSshKeyPath()
|
||||
expect(actualKeyPath).toBeTruthy()
|
||||
await fs.promises.stat(actualKeyPath)
|
||||
const actualKnownHostsPath = await getActualSshKnownHostsPath()
|
||||
expect(actualKnownHostsPath).toBeTruthy()
|
||||
await fs.promises.stat(actualKnownHostsPath)
|
||||
|
||||
// Act
|
||||
await authHelper.removeAuth()
|
||||
|
||||
// Assert git config
|
||||
gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('core.sshCommand')).toBeLessThan(0)
|
||||
|
||||
// Assert SSH key file
|
||||
try {
|
||||
await fs.promises.stat(actualKeyPath)
|
||||
throw new Error('SSH key should have been deleted')
|
||||
} catch (err) {
|
||||
if ((err as any)?.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Assert known hosts file
|
||||
try {
|
||||
await fs.promises.stat(actualKnownHostsPath)
|
||||
throw new Error('SSH known hosts should have been deleted')
|
||||
} catch (err) {
|
||||
if ((err as any)?.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const removeAuth_removesToken = 'removeAuth removes token'
|
||||
it(removeAuth_removesToken, async () => {
|
||||
// Arrange
|
||||
await setup(removeAuth_removesToken)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
let gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
|
||||
|
||||
// Act
|
||||
await authHelper.removeAuth()
|
||||
|
||||
// Assert git config
|
||||
gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
|
||||
})
|
||||
|
||||
const removeGlobalConfig_removesOverride =
|
||||
'removeGlobalConfig removes override'
|
||||
it(removeGlobalConfig_removesOverride, async () => {
|
||||
// Arrange
|
||||
await setup(removeGlobalConfig_removesOverride)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
await authHelper.configureGlobalAuth()
|
||||
const homeOverride = git.env['HOME'] // Sanity check
|
||||
expect(homeOverride).toBeTruthy()
|
||||
await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig'))
|
||||
|
||||
// Act
|
||||
await authHelper.removeGlobalConfig()
|
||||
|
||||
// Assert
|
||||
expect(git.env['HOME']).toBeUndefined()
|
||||
try {
|
||||
await fs.promises.stat(homeOverride)
|
||||
throw new Error(`Should have been deleted '${homeOverride}'`)
|
||||
} catch (err) {
|
||||
if ((err as any)?.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
async function setup(testName: string): Promise<void> {
|
||||
testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-')
|
||||
|
||||
// Directories
|
||||
workspace = path.join(testWorkspace, testName, 'workspace')
|
||||
runnerTemp = path.join(testWorkspace, testName, 'runner-temp')
|
||||
tempHomedir = path.join(testWorkspace, testName, 'home-dir')
|
||||
await fs.promises.mkdir(workspace, {recursive: true})
|
||||
await fs.promises.mkdir(runnerTemp, {recursive: true})
|
||||
await fs.promises.mkdir(tempHomedir, {recursive: true})
|
||||
process.env['RUNNER_TEMP'] = runnerTemp
|
||||
process.env['HOME'] = tempHomedir
|
||||
|
||||
// Create git config
|
||||
globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
|
||||
await fs.promises.writeFile(globalGitConfigPath, '')
|
||||
localGitConfigPath = path.join(workspace, '.git', 'config')
|
||||
await fs.promises.mkdir(path.dirname(localGitConfigPath), {recursive: true})
|
||||
await fs.promises.writeFile(localGitConfigPath, '')
|
||||
|
||||
git = {
|
||||
branchDelete: jest.fn(),
|
||||
branchExists: jest.fn(),
|
||||
branchList: jest.fn(),
|
||||
checkout: jest.fn(),
|
||||
checkoutDetach: jest.fn(),
|
||||
config: jest.fn(
|
||||
async (key: string, value: string, globalConfig?: boolean) => {
|
||||
const configPath = globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath
|
||||
await fs.promises.appendFile(configPath, `\n${key} ${value}`)
|
||||
}
|
||||
),
|
||||
configExists: jest.fn(
|
||||
async (key: string, globalConfig?: boolean): Promise<boolean> => {
|
||||
const configPath = globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath
|
||||
const content = await fs.promises.readFile(configPath)
|
||||
const lines = content
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(x => x)
|
||||
return lines.some(x => x.startsWith(key))
|
||||
}
|
||||
),
|
||||
env: {},
|
||||
fetch: jest.fn(),
|
||||
getDefaultBranch: jest.fn(),
|
||||
getWorkingDirectory: jest.fn(() => workspace),
|
||||
init: jest.fn(),
|
||||
isDetached: jest.fn(),
|
||||
lfsFetch: jest.fn(),
|
||||
lfsInstall: jest.fn(),
|
||||
log1: jest.fn(),
|
||||
remoteAdd: jest.fn(),
|
||||
removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
|
||||
revParse: jest.fn(),
|
||||
setEnvironmentVariable: jest.fn((name: string, value: string) => {
|
||||
git.env[name] = value
|
||||
}),
|
||||
shaExists: jest.fn(),
|
||||
submoduleForeach: jest.fn(async () => {
|
||||
return ''
|
||||
}),
|
||||
submoduleSync: jest.fn(),
|
||||
submoduleUpdate: jest.fn(),
|
||||
tagExists: jest.fn(),
|
||||
tryClean: jest.fn(),
|
||||
tryConfigUnset: jest.fn(
|
||||
async (key: string, globalConfig?: boolean): Promise<boolean> => {
|
||||
const configPath = globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath
|
||||
let content = await fs.promises.readFile(configPath)
|
||||
let lines = content
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(x => x)
|
||||
.filter(x => !x.startsWith(key))
|
||||
await fs.promises.writeFile(configPath, lines.join('\n'))
|
||||
return true
|
||||
}
|
||||
),
|
||||
tryDisableAutomaticGarbageCollection: jest.fn(),
|
||||
tryGetFetchUrl: jest.fn(),
|
||||
tryReset: jest.fn()
|
||||
}
|
||||
|
||||
settings = {
|
||||
authToken: 'some auth token',
|
||||
clean: true,
|
||||
commit: '',
|
||||
fetchDepth: 1,
|
||||
lfs: false,
|
||||
submodules: false,
|
||||
nestedSubmodules: false,
|
||||
persistCredentials: true,
|
||||
ref: 'refs/heads/main',
|
||||
repositoryName: 'my-repo',
|
||||
repositoryOwner: 'my-org',
|
||||
repositoryPath: '',
|
||||
sshKey: sshPath ? 'some ssh private key' : '',
|
||||
sshKnownHosts: '',
|
||||
sshStrict: true,
|
||||
workflowOrganizationId: 123456,
|
||||
setSafeDirectory: true
|
||||
}
|
||||
}
|
||||
|
||||
async function getActualSshKeyPath(): Promise<string> {
|
||||
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
||||
.sort()
|
||||
.map(x => path.join(runnerTemp, x))
|
||||
if (actualTempFiles.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
expect(actualTempFiles).toHaveLength(2)
|
||||
expect(actualTempFiles[0].endsWith('_known_hosts')).toBeFalsy()
|
||||
return actualTempFiles[0]
|
||||
}
|
||||
|
||||
async function getActualSshKnownHostsPath(): Promise<string> {
|
||||
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
||||
.sort()
|
||||
.map(x => path.join(runnerTemp, x))
|
||||
if (actualTempFiles.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
expect(actualTempFiles).toHaveLength(2)
|
||||
expect(actualTempFiles[1].endsWith('_known_hosts')).toBeTruthy()
|
||||
expect(actualTempFiles[1].startsWith(actualTempFiles[0])).toBeTruthy()
|
||||
return actualTempFiles[1]
|
||||
}
|
441
__test__/git-directory-helper.test.ts
Normal file
441
__test__/git-directory-helper.test.ts
Normal file
@ -0,0 +1,441 @@
|
||||
import * as core from '@actions/core'
|
||||
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'
|
||||
|
||||
const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper')
|
||||
let repositoryPath: string
|
||||
let repositoryUrl: string
|
||||
let clean: boolean
|
||||
let ref: string
|
||||
let git: IGitCommandManager
|
||||
|
||||
describe('git-directory-helper tests', () => {
|
||||
beforeAll(async () => {
|
||||
// Clear test workspace
|
||||
await io.rmRF(testWorkspace)
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Unregister mocks
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
const cleansWhenCleanTrue = 'cleans when clean true'
|
||||
it(cleansWhenCleanTrue, async () => {
|
||||
// Arrange
|
||||
await setup(cleansWhenCleanTrue)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.tryClean).toHaveBeenCalled()
|
||||
expect(git.tryReset).toHaveBeenCalled()
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const checkoutDetachWhenNotDetached = 'checkout detach when not detached'
|
||||
it(checkoutDetachWhenNotDetached, async () => {
|
||||
// Arrange
|
||||
await setup(checkoutDetachWhenNotDetached)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.checkoutDetach).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const doesNotCheckoutDetachWhenNotAlreadyDetached =
|
||||
'does not checkout detach when already detached'
|
||||
it(doesNotCheckoutDetachWhenNotAlreadyDetached, async () => {
|
||||
// Arrange
|
||||
await setup(doesNotCheckoutDetachWhenNotAlreadyDetached)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
const mockIsDetached = git.isDetached as jest.Mock<any, any>
|
||||
mockIsDetached.mockImplementation(async () => {
|
||||
return true
|
||||
})
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.checkoutDetach).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const doesNotCleanWhenCleanFalse = 'does not clean when clean false'
|
||||
it(doesNotCleanWhenCleanFalse, async () => {
|
||||
// Arrange
|
||||
await setup(doesNotCleanWhenCleanFalse)
|
||||
clean = false
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.isDetached).toHaveBeenCalled()
|
||||
expect(git.branchList).toHaveBeenCalled()
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
expect(git.tryClean).not.toHaveBeenCalled()
|
||||
expect(git.tryReset).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const removesContentsWhenCleanFails = 'removes contents when clean fails'
|
||||
it(removesContentsWhenCleanFails, async () => {
|
||||
// Arrange
|
||||
await setup(removesContentsWhenCleanFails)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
let mockTryClean = git.tryClean as jest.Mock<any, any>
|
||||
mockTryClean.mockImplementation(async () => {
|
||||
return false
|
||||
})
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files).toHaveLength(0)
|
||||
expect(git.tryClean).toHaveBeenCalled()
|
||||
expect(core.warning).toHaveBeenCalled()
|
||||
expect(git.tryReset).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const removesContentsWhenDifferentRepositoryUrl =
|
||||
'removes contents when different repository url'
|
||||
it(removesContentsWhenDifferentRepositoryUrl, async () => {
|
||||
// Arrange
|
||||
await setup(removesContentsWhenDifferentRepositoryUrl)
|
||||
clean = false
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
const differentRepositoryUrl =
|
||||
'https://github.com/my-different-org/my-different-repo'
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
differentRepositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files).toHaveLength(0)
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
expect(git.isDetached).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const removesContentsWhenNoGitDirectory =
|
||||
'removes contents when no git directory'
|
||||
it(removesContentsWhenNoGitDirectory, async () => {
|
||||
// Arrange
|
||||
await setup(removesContentsWhenNoGitDirectory)
|
||||
clean = false
|
||||
await io.rmRF(path.join(repositoryPath, '.git'))
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files).toHaveLength(0)
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
expect(git.isDetached).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const removesContentsWhenResetFails = 'removes contents when reset fails'
|
||||
it(removesContentsWhenResetFails, async () => {
|
||||
// Arrange
|
||||
await setup(removesContentsWhenResetFails)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
let mockTryReset = git.tryReset as jest.Mock<any, any>
|
||||
mockTryReset.mockImplementation(async () => {
|
||||
return false
|
||||
})
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files).toHaveLength(0)
|
||||
expect(git.tryClean).toHaveBeenCalled()
|
||||
expect(git.tryReset).toHaveBeenCalled()
|
||||
expect(core.warning).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const removesContentsWhenUndefinedGitCommandManager =
|
||||
'removes contents when undefined git command manager'
|
||||
it(removesContentsWhenUndefinedGitCommandManager, async () => {
|
||||
// Arrange
|
||||
await setup(removesContentsWhenUndefinedGitCommandManager)
|
||||
clean = false
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
undefined,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files).toHaveLength(0)
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const removesLocalBranches = 'removes local branches'
|
||||
it(removesLocalBranches, async () => {
|
||||
// Arrange
|
||||
await setup(removesLocalBranches)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
const mockBranchList = git.branchList as jest.Mock<any, any>
|
||||
mockBranchList.mockImplementation(async (remote: boolean) => {
|
||||
return remote ? [] : ['local-branch-1', 'local-branch-2']
|
||||
})
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-1')
|
||||
expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-2')
|
||||
})
|
||||
|
||||
const removesLockFiles = 'removes lock files'
|
||||
it(removesLockFiles, async () => {
|
||||
// Arrange
|
||||
await setup(removesLockFiles)
|
||||
clean = false
|
||||
await fs.promises.writeFile(
|
||||
path.join(repositoryPath, '.git', 'index.lock'),
|
||||
''
|
||||
)
|
||||
await fs.promises.writeFile(
|
||||
path.join(repositoryPath, '.git', 'shallow.lock'),
|
||||
''
|
||||
)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
let files = await fs.promises.readdir(path.join(repositoryPath, '.git'))
|
||||
expect(files).toHaveLength(0)
|
||||
files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.isDetached).toHaveBeenCalled()
|
||||
expect(git.branchList).toHaveBeenCalled()
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
expect(git.tryClean).not.toHaveBeenCalled()
|
||||
expect(git.tryReset).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const removesAncestorRemoteBranch = 'removes ancestor remote branch'
|
||||
it(removesAncestorRemoteBranch, async () => {
|
||||
// Arrange
|
||||
await setup(removesAncestorRemoteBranch)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
const mockBranchList = git.branchList as jest.Mock<any, any>
|
||||
mockBranchList.mockImplementation(async (remote: boolean) => {
|
||||
return remote ? ['origin/remote-branch-1', 'origin/remote-branch-2'] : []
|
||||
})
|
||||
ref = 'remote-branch-1/conflict'
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.branchDelete).toHaveBeenCalledTimes(1)
|
||||
expect(git.branchDelete).toHaveBeenCalledWith(
|
||||
true,
|
||||
'origin/remote-branch-1'
|
||||
)
|
||||
})
|
||||
|
||||
const removesDescendantRemoteBranches = 'removes descendant remote branch'
|
||||
it(removesDescendantRemoteBranches, async () => {
|
||||
// Arrange
|
||||
await setup(removesDescendantRemoteBranches)
|
||||
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
|
||||
const mockBranchList = git.branchList as jest.Mock<any, any>
|
||||
mockBranchList.mockImplementation(async (remote: boolean) => {
|
||||
return remote
|
||||
? ['origin/remote-branch-1/conflict', 'origin/remote-branch-2']
|
||||
: []
|
||||
})
|
||||
ref = 'remote-branch-1'
|
||||
|
||||
// Act
|
||||
await gitDirectoryHelper.prepareExistingDirectory(
|
||||
git,
|
||||
repositoryPath,
|
||||
repositoryUrl,
|
||||
clean,
|
||||
ref
|
||||
)
|
||||
|
||||
// Assert
|
||||
const files = await fs.promises.readdir(repositoryPath)
|
||||
expect(files.sort()).toEqual(['.git', 'my-file'])
|
||||
expect(git.branchDelete).toHaveBeenCalledTimes(1)
|
||||
expect(git.branchDelete).toHaveBeenCalledWith(
|
||||
true,
|
||||
'origin/remote-branch-1/conflict'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
async function setup(testName: string): Promise<void> {
|
||||
testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-')
|
||||
|
||||
// Repository directory
|
||||
repositoryPath = path.join(testWorkspace, testName)
|
||||
await fs.promises.mkdir(path.join(repositoryPath, '.git'), {recursive: true})
|
||||
|
||||
// Repository URL
|
||||
repositoryUrl = 'https://github.com/my-org/my-repo'
|
||||
|
||||
// Clean
|
||||
clean = true
|
||||
|
||||
// Ref
|
||||
ref = ''
|
||||
|
||||
// Git command manager
|
||||
git = {
|
||||
branchDelete: jest.fn(),
|
||||
branchExists: jest.fn(),
|
||||
branchList: jest.fn(async () => {
|
||||
return []
|
||||
}),
|
||||
checkout: jest.fn(),
|
||||
checkoutDetach: jest.fn(),
|
||||
config: jest.fn(),
|
||||
configExists: jest.fn(),
|
||||
fetch: jest.fn(),
|
||||
getDefaultBranch: jest.fn(),
|
||||
getWorkingDirectory: jest.fn(() => repositoryPath),
|
||||
init: jest.fn(),
|
||||
isDetached: jest.fn(),
|
||||
lfsFetch: jest.fn(),
|
||||
lfsInstall: jest.fn(),
|
||||
log1: jest.fn(),
|
||||
remoteAdd: jest.fn(),
|
||||
removeEnvironmentVariable: jest.fn(),
|
||||
revParse: jest.fn(),
|
||||
setEnvironmentVariable: jest.fn(),
|
||||
shaExists: jest.fn(),
|
||||
submoduleForeach: jest.fn(),
|
||||
submoduleSync: jest.fn(),
|
||||
submoduleUpdate: jest.fn(),
|
||||
tagExists: jest.fn(),
|
||||
tryClean: jest.fn(async () => {
|
||||
return true
|
||||
}),
|
||||
tryConfigUnset: jest.fn(),
|
||||
tryDisableAutomaticGarbageCollection: jest.fn(),
|
||||
tryGetFetchUrl: jest.fn(async () => {
|
||||
// Sanity check - this function shouldn't be called when the .git directory doesn't exist
|
||||
await fs.promises.stat(path.join(repositoryPath, '.git'))
|
||||
return repositoryUrl
|
||||
}),
|
||||
tryReset: jest.fn(async () => {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
45
__test__/git-version.test.ts
Normal file
45
__test__/git-version.test.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {GitVersion} from '../lib/git-version'
|
||||
|
||||
describe('git-version tests', () => {
|
||||
it('basics', async () => {
|
||||
let version = new GitVersion('')
|
||||
expect(version.isValid()).toBeFalsy()
|
||||
|
||||
version = new GitVersion('asdf')
|
||||
expect(version.isValid()).toBeFalsy()
|
||||
|
||||
version = new GitVersion('1.2')
|
||||
expect(version.isValid()).toBeTruthy()
|
||||
expect(version.toString()).toBe('1.2')
|
||||
|
||||
version = new GitVersion('1.2.3')
|
||||
expect(version.isValid()).toBeTruthy()
|
||||
expect(version.toString()).toBe('1.2.3')
|
||||
})
|
||||
|
||||
it('check minimum', async () => {
|
||||
let version = new GitVersion('4.5')
|
||||
expect(version.checkMinimum(new GitVersion('3.6'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('3.6.7'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.4'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.5'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.5.0'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.6'))).toBeFalsy()
|
||||
expect(version.checkMinimum(new GitVersion('4.6.0'))).toBeFalsy()
|
||||
expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy()
|
||||
expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy()
|
||||
|
||||
version = new GitVersion('4.5.6')
|
||||
expect(version.checkMinimum(new GitVersion('3.6'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('3.6.7'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.4'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.5'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.5.5'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.5.6'))).toBeTruthy()
|
||||
expect(version.checkMinimum(new GitVersion('4.5.7'))).toBeFalsy()
|
||||
expect(version.checkMinimum(new GitVersion('4.6'))).toBeFalsy()
|
||||
expect(version.checkMinimum(new GitVersion('4.6.0'))).toBeFalsy()
|
||||
expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy()
|
||||
expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy()
|
||||
})
|
||||
})
|
142
__test__/input-helper.test.ts
Normal file
142
__test__/input-helper.test.ts
Normal file
@ -0,0 +1,142 @@
|
||||
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 * 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')
|
||||
|
||||
// Inputs for mock @actions/core
|
||||
let inputs = {} as any
|
||||
|
||||
// Shallow clone original @actions/github context
|
||||
let originalContext = {...github.context}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset inputs
|
||||
inputs = {}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
// Restore GitHub workspace
|
||||
delete process.env['GITHUB_WORKSPACE']
|
||||
if (originalGitHubWorkspace) {
|
||||
process.env['GITHUB_WORKSPACE'] = originalGitHubWorkspace
|
||||
}
|
||||
|
||||
// Restore @actions/github context
|
||||
github.context.ref = originalContext.ref
|
||||
github.context.sha = originalContext.sha
|
||||
|
||||
// Restore
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('sets defaults', async () => {
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings).toBeTruthy()
|
||||
expect(settings.authToken).toBeFalsy()
|
||||
expect(settings.clean).toBe(true)
|
||||
expect(settings.commit).toBeTruthy()
|
||||
expect(settings.commit).toBe('1234567890123456789012345678901234567890')
|
||||
expect(settings.fetchDepth).toBe(1)
|
||||
expect(settings.lfs).toBe(false)
|
||||
expect(settings.ref).toBe('refs/heads/some-ref')
|
||||
expect(settings.repositoryName).toBe('some-repo')
|
||||
expect(settings.repositoryOwner).toBe('some-owner')
|
||||
expect(settings.repositoryPath).toBe(gitHubWorkspace)
|
||||
expect(settings.setSafeDirectory).toBe(true)
|
||||
})
|
||||
|
||||
it('qualifies ref', async () => {
|
||||
let originalRef = github.context.ref
|
||||
try {
|
||||
github.context.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
|
||||
}
|
||||
})
|
||||
|
||||
it('requires qualified repo', async () => {
|
||||
inputs.repository = 'some-unqualified-repo'
|
||||
try {
|
||||
await inputHelper.getInputs()
|
||||
throw 'should not reach here'
|
||||
} catch (err) {
|
||||
expect(`(${(err as any).message}`).toMatch(
|
||||
"Invalid repository 'some-unqualified-repo'"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('roots path', async () => {
|
||||
inputs.path = 'some-directory/some-subdirectory'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.repositoryPath).toBe(
|
||||
path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory')
|
||||
)
|
||||
})
|
||||
|
||||
it('sets ref to empty when explicit sha', async () => {
|
||||
inputs.ref = '1111111111222222222233333333334444444444'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.ref).toBeFalsy()
|
||||
expect(settings.commit).toBe('1111111111222222222233333333334444444444')
|
||||
})
|
||||
|
||||
it('sets sha to empty when explicit ref', async () => {
|
||||
inputs.ref = 'refs/heads/some-other-ref'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.ref).toBe('refs/heads/some-other-ref')
|
||||
expect(settings.commit).toBeFalsy()
|
||||
})
|
||||
|
||||
it('sets workflow organization ID', async () => {
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.workflowOrganizationId).toBe(123456)
|
||||
})
|
||||
})
|
10
__test__/modify-work-tree.sh
Executable file
10
__test__/modify-work-tree.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "./basic/basic-file.txt" ]; then
|
||||
echo "Expected basic file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo hello >> ./basic/basic-file.txt
|
||||
echo hello >> ./basic/new-file.txt
|
||||
git -C ./basic status
|
6
__test__/override-git-version.cmd
Executable file
6
__test__/override-git-version.cmd
Executable file
@ -0,0 +1,6 @@
|
||||
|
||||
mkdir override-git-version
|
||||
cd override-git-version
|
||||
echo @echo override git version 1.2.3 > git.cmd
|
||||
echo "%CD%" >> $GITHUB_PATH
|
||||
cd ..
|
9
__test__/override-git-version.sh
Executable file
9
__test__/override-git-version.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir override-git-version
|
||||
cd override-git-version
|
||||
echo "#!/bin/sh" > git
|
||||
echo "echo override git version 1.2.3" >> git
|
||||
chmod +x git
|
||||
echo "$(pwd)" >> $GITHUB_PATH
|
||||
cd ..
|
170
__test__/ref-helper.test.ts
Normal file
170
__test__/ref-helper.test.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import * as assert from 'assert'
|
||||
import * as refHelper from '../lib/ref-helper'
|
||||
import {IGitCommandManager} from '../lib/git-command-manager'
|
||||
|
||||
const commit = '1234567890123456789012345678901234567890'
|
||||
let git: IGitCommandManager
|
||||
|
||||
describe('ref-helper tests', () => {
|
||||
beforeEach(() => {
|
||||
git = ({} as unknown) as IGitCommandManager
|
||||
})
|
||||
|
||||
it('getCheckoutInfo requires git', async () => {
|
||||
const git = (null as unknown) as IGitCommandManager
|
||||
try {
|
||||
await refHelper.getCheckoutInfo(git, 'refs/heads/my/branch', commit)
|
||||
throw new Error('Should not reach here')
|
||||
} catch (err) {
|
||||
expect((err as any)?.message).toBe('Arg git cannot be empty')
|
||||
}
|
||||
})
|
||||
|
||||
it('getCheckoutInfo requires ref or commit', async () => {
|
||||
try {
|
||||
await refHelper.getCheckoutInfo(git, '', '')
|
||||
throw new Error('Should not reach here')
|
||||
} catch (err) {
|
||||
expect((err as any)?.message).toBe(
|
||||
'Args ref and commit cannot both be empty'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('getCheckoutInfo sha only', async () => {
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(git, '', commit)
|
||||
expect(checkoutInfo.ref).toBe(commit)
|
||||
expect(checkoutInfo.startPoint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('getCheckoutInfo refs/heads/', async () => {
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(
|
||||
git,
|
||||
'refs/heads/my/branch',
|
||||
commit
|
||||
)
|
||||
expect(checkoutInfo.ref).toBe('my/branch')
|
||||
expect(checkoutInfo.startPoint).toBe('refs/remotes/origin/my/branch')
|
||||
})
|
||||
|
||||
it('getCheckoutInfo refs/pull/', async () => {
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(
|
||||
git,
|
||||
'refs/pull/123/merge',
|
||||
commit
|
||||
)
|
||||
expect(checkoutInfo.ref).toBe('refs/remotes/pull/123/merge')
|
||||
expect(checkoutInfo.startPoint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('getCheckoutInfo refs/tags/', async () => {
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(
|
||||
git,
|
||||
'refs/tags/my-tag',
|
||||
commit
|
||||
)
|
||||
expect(checkoutInfo.ref).toBe('refs/tags/my-tag')
|
||||
expect(checkoutInfo.startPoint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('getCheckoutInfo unqualified branch only', async () => {
|
||||
git.branchExists = jest.fn(async (remote: boolean, pattern: string) => {
|
||||
return true
|
||||
})
|
||||
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(git, 'my/branch', '')
|
||||
|
||||
expect(checkoutInfo.ref).toBe('my/branch')
|
||||
expect(checkoutInfo.startPoint).toBe('refs/remotes/origin/my/branch')
|
||||
})
|
||||
|
||||
it('getCheckoutInfo unqualified tag only', async () => {
|
||||
git.branchExists = jest.fn(async (remote: boolean, pattern: string) => {
|
||||
return false
|
||||
})
|
||||
git.tagExists = jest.fn(async (pattern: string) => {
|
||||
return true
|
||||
})
|
||||
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(git, 'my-tag', '')
|
||||
|
||||
expect(checkoutInfo.ref).toBe('refs/tags/my-tag')
|
||||
expect(checkoutInfo.startPoint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('getCheckoutInfo unqualified ref only, not a branch or tag', async () => {
|
||||
git.branchExists = jest.fn(async (remote: boolean, pattern: string) => {
|
||||
return false
|
||||
})
|
||||
git.tagExists = jest.fn(async (pattern: string) => {
|
||||
return false
|
||||
})
|
||||
|
||||
try {
|
||||
await refHelper.getCheckoutInfo(git, 'my-ref', '')
|
||||
throw new Error('Should not reach here')
|
||||
} catch (err) {
|
||||
expect((err as any)?.message).toBe(
|
||||
"A branch or tag with the name 'my-ref' could not be found"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('getRefSpec requires ref or commit', async () => {
|
||||
assert.throws(
|
||||
() => refHelper.getRefSpec('', ''),
|
||||
/Args ref and commit cannot both be empty/
|
||||
)
|
||||
})
|
||||
|
||||
it('getRefSpec sha + refs/heads/', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', commit)
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe(`+${commit}:refs/remotes/origin/my/branch`)
|
||||
})
|
||||
|
||||
it('getRefSpec sha + refs/pull/', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/pull/123/merge', commit)
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe(`+${commit}:refs/remotes/pull/123/merge`)
|
||||
})
|
||||
|
||||
it('getRefSpec sha + refs/tags/', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit)
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe(`+${commit}:refs/tags/my-tag`)
|
||||
})
|
||||
|
||||
it('getRefSpec sha only', async () => {
|
||||
const refSpec = refHelper.getRefSpec('', commit)
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe(commit)
|
||||
})
|
||||
|
||||
it('getRefSpec unqualified ref only', async () => {
|
||||
const refSpec = refHelper.getRefSpec('my-ref', '')
|
||||
expect(refSpec.length).toBe(2)
|
||||
expect(refSpec[0]).toBe('+refs/heads/my-ref*:refs/remotes/origin/my-ref*')
|
||||
expect(refSpec[1]).toBe('+refs/tags/my-ref*:refs/tags/my-ref*')
|
||||
})
|
||||
|
||||
it('getRefSpec refs/heads/ only', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '')
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe(
|
||||
'+refs/heads/my/branch:refs/remotes/origin/my/branch'
|
||||
)
|
||||
})
|
||||
|
||||
it('getRefSpec refs/pull/ only', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/pull/123/merge', '')
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe('+refs/pull/123/merge:refs/remotes/pull/123/merge')
|
||||
})
|
||||
|
||||
it('getRefSpec refs/tags/ only', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', '')
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe('+refs/tags/my-tag:refs/tags/my-tag')
|
||||
})
|
||||
})
|
87
__test__/retry-helper.test.ts
Normal file
87
__test__/retry-helper.test.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import * as core from '@actions/core'
|
||||
import {RetryHelper} from '../lib/retry-helper'
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset info
|
||||
info = []
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
// Restore
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('first attempt succeeds', async () => {
|
||||
const actual = await retryHelper.execute(async () => {
|
||||
return 'some result'
|
||||
})
|
||||
expect(actual).toBe('some result')
|
||||
expect(info).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('second attempt succeeds', async () => {
|
||||
let attempts = 0
|
||||
const actual = await retryHelper.execute(() => {
|
||||
if (++attempts == 1) {
|
||||
throw new Error('some error')
|
||||
}
|
||||
|
||||
return Promise.resolve('some result')
|
||||
})
|
||||
expect(attempts).toBe(2)
|
||||
expect(actual).toBe('some result')
|
||||
expect(info).toHaveLength(2)
|
||||
expect(info[0]).toBe('some error')
|
||||
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
|
||||
})
|
||||
|
||||
it('third attempt succeeds', async () => {
|
||||
let attempts = 0
|
||||
const actual = await retryHelper.execute(() => {
|
||||
if (++attempts < 3) {
|
||||
throw new Error(`some error ${attempts}`)
|
||||
}
|
||||
|
||||
return Promise.resolve('some result')
|
||||
})
|
||||
expect(attempts).toBe(3)
|
||||
expect(actual).toBe('some result')
|
||||
expect(info).toHaveLength(4)
|
||||
expect(info[0]).toBe('some error 1')
|
||||
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
|
||||
expect(info[2]).toBe('some error 2')
|
||||
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
|
||||
})
|
||||
|
||||
it('all attempts fail succeeds', async () => {
|
||||
let attempts = 0
|
||||
let error: Error = (null as unknown) as Error
|
||||
try {
|
||||
await retryHelper.execute(() => {
|
||||
throw new Error(`some error ${++attempts}`)
|
||||
})
|
||||
} catch (err) {
|
||||
error = err as Error
|
||||
}
|
||||
expect(error.message).toBe('some error 3')
|
||||
expect(attempts).toBe(3)
|
||||
expect(info).toHaveLength(4)
|
||||
expect(info[0]).toBe('some error 1')
|
||||
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
|
||||
expect(info[2]).toBe('some error 2')
|
||||
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
|
||||
})
|
||||
})
|
24
__test__/verify-basic.sh
Executable file
24
__test__/verify-basic.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ ! -f "./basic/basic-file.txt" ]; then
|
||||
echo "Expected basic file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$1" = "--archive" ]; then
|
||||
# Verify no .git folder
|
||||
if [ -d "./basic/.git" ]; then
|
||||
echo "Did not expect ./basic/.git folder to exist"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Verify .git folder
|
||||
if [ ! -d "./basic/.git" ]; then
|
||||
echo "Expected ./basic/.git folder to exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify auth token
|
||||
cd basic
|
||||
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
|
||||
fi
|
13
__test__/verify-clean.sh
Executable file
13
__test__/verify-clean.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$(git -C ./basic status --porcelain)" != "" ]]; then
|
||||
echo ----------------------------------------
|
||||
echo git status
|
||||
echo ----------------------------------------
|
||||
git status
|
||||
echo ----------------------------------------
|
||||
echo git diff
|
||||
echo ----------------------------------------
|
||||
git diff
|
||||
exit 1
|
||||
fi
|
11
__test__/verify-lfs.sh
Executable file
11
__test__/verify-lfs.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "./lfs/regular-file.txt" ]; then
|
||||
echo "Expected regular file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "./lfs/lfs-file.bin" ]; then
|
||||
echo "Expected lfs file does not exist"
|
||||
exit 1
|
||||
fi
|
17
__test__/verify-no-unstaged-changes.sh
Executable file
17
__test__/verify-no-unstaged-changes.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$(git status --porcelain)" != "" ]]; then
|
||||
echo ----------------------------------------
|
||||
echo git status
|
||||
echo ----------------------------------------
|
||||
git status
|
||||
echo ----------------------------------------
|
||||
echo git diff
|
||||
echo ----------------------------------------
|
||||
git diff
|
||||
echo ----------------------------------------
|
||||
echo Troubleshooting
|
||||
echo ----------------------------------------
|
||||
echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run format && npm run build"
|
||||
exit 1
|
||||
fi
|
11
__test__/verify-side-by-side.sh
Executable file
11
__test__/verify-side-by-side.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "./side-by-side-1/side-by-side-test-file-1.txt" ]; then
|
||||
echo "Expected file 1 does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "./side-by-side-2/side-by-side-test-file-2.txt" ]; then
|
||||
echo "Expected file 2 does not exist"
|
||||
exit 1
|
||||
fi
|
11
__test__/verify-submodules-false.sh
Executable file
11
__test__/verify-submodules-false.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "./submodules-false/regular-file.txt" ]; then
|
||||
echo "Expected regular file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "./submodules-false/submodule-level-1/submodule-file.txt" ]; then
|
||||
echo "Unexpected submodule file exists"
|
||||
exit 1
|
||||
fi
|
26
__test__/verify-submodules-recursive.sh
Executable file
26
__test__/verify-submodules-recursive.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "./submodules-recursive/regular-file.txt" ]; then
|
||||
echo "Expected regular file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "./submodules-recursive/submodule-level-1/submodule-file.txt" ]; then
|
||||
echo "Expected submodule file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "./submodules-recursive/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then
|
||||
echo "Expected nested submodule file does not exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing persisted credential"
|
||||
pushd ./submodules-recursive/submodule-level-1/submodule-level-2
|
||||
git config --local --name-only --get-regexp http.+extraheader && git fetch
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Failed to validate persisted credential"
|
||||
popd
|
||||
exit 1
|
||||
fi
|
||||
popd
|
26
__test__/verify-submodules-true.sh
Executable file
26
__test__/verify-submodules-true.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "./submodules-true/regular-file.txt" ]; then
|
||||
echo "Expected regular file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "./submodules-true/submodule-level-1/submodule-file.txt" ]; then
|
||||
echo "Expected submodule file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "./submodules-true/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then
|
||||
echo "Unexpected nested submodule file exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing persisted credential"
|
||||
pushd ./submodules-true/submodule-level-1
|
||||
git config --local --name-only --get-regexp http.+extraheader && git fetch
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Failed to validate persisted credential"
|
||||
popd
|
||||
exit 1
|
||||
fi
|
||||
popd
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user