Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6747255bd | ||
|
|
c170eefc26 | ||
|
|
a572f640b0 | ||
|
|
cab31617d8 | ||
|
|
5881116d18 | ||
|
|
7990b10a0c | ||
|
|
01a434328a | ||
|
|
4817b449b0 | ||
|
|
689bf84be4 | ||
|
|
cc70598ce8 | ||
|
|
8461dbfed3 | ||
|
|
e347bba93b | ||
|
|
50fbc622fc | ||
|
|
e8bd1dffb6 | ||
|
|
0b496e91ec | ||
|
|
f6ce2afa70 | ||
|
|
94d077c249 | ||
|
|
0963d3b35f | ||
|
|
a14471d838 | ||
|
|
7f0669ca1f | ||
|
|
cacfc4155d | ||
|
|
6e6328ef28 | ||
|
|
53bed0742e | ||
|
|
b4b537b06a | ||
|
|
af513c7a01 | ||
|
|
bf4af63534 | ||
|
|
3df79e0276 | ||
|
|
44679f67d2 |
No files matched your search
+1
-2
@@ -1,4 +1,3 @@
|
||||
lib/
|
||||
dist/
|
||||
lib/
|
||||
node_modules/
|
||||
coverage/
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"plugins": ["jest", "@typescript-eslint"],
|
||||
"extends": ["plugin:github/es6"],
|
||||
"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",
|
||||
"@typescript-eslint/ban-ts-ignore": "error",
|
||||
"camelcase": "off",
|
||||
"@typescript-eslint/camelcase": "error",
|
||||
"@typescript-eslint/class-name-casing": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
|
||||
"@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-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-object-literal-type-assertion": "error",
|
||||
"@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-interface": "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,3 +0,0 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
dist/** -diff linguist-generated=true
|
||||
@@ -1,82 +0,0 @@
|
||||
env:
|
||||
node: true
|
||||
es6: true
|
||||
jest: true
|
||||
|
||||
globals:
|
||||
Atomics: readonly
|
||||
SharedArrayBuffer: readonly
|
||||
|
||||
ignorePatterns:
|
||||
- '!.*'
|
||||
- '**/node_modules/.*'
|
||||
- '**/dist/.*'
|
||||
- '**/coverage/.*'
|
||||
- '*.json'
|
||||
|
||||
parser: '@typescript-eslint/parser'
|
||||
|
||||
parserOptions:
|
||||
ecmaVersion: 2023
|
||||
sourceType: module
|
||||
project:
|
||||
- './.gitea/linters/tsconfig.json'
|
||||
- './tsconfig.json'
|
||||
|
||||
plugins:
|
||||
- jest
|
||||
- '@typescript-eslint'
|
||||
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:@typescript-eslint/eslint-recommended
|
||||
- plugin:@typescript-eslint/recommended
|
||||
- plugin:jest/recommended
|
||||
|
||||
rules:
|
||||
{
|
||||
'camelcase': 'off',
|
||||
'eslint-comments/no-use': 'off',
|
||||
'eslint-comments/no-unused-disable': 'off',
|
||||
'i18n-text/no-en': 'off',
|
||||
'import/no-namespace': 'off',
|
||||
'no-console': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'prettier/prettier': 'error',
|
||||
'semi': 'off',
|
||||
'@typescript-eslint/array-type': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/consistent-type-assertions': 'error',
|
||||
'@typescript-eslint/explicit-member-accessibility':
|
||||
['error', { 'accessibility': 'no-public' }],
|
||||
'@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-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-require-imports': 'error',
|
||||
'@typescript-eslint/no-unnecessary-qualifier': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/no-unused-vars': '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',
|
||||
'@typescript-eslint/semi': ['error', 'never'],
|
||||
'@typescript-eslint/space-before-function-paren': 'off',
|
||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||
'@typescript-eslint/unbound-method': 'error'
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# Unordered list style
|
||||
MD004:
|
||||
style: dash
|
||||
|
||||
# Ordered list item prefix
|
||||
MD029:
|
||||
style: one
|
||||
@@ -1,10 +0,0 @@
|
||||
rules:
|
||||
document-end: disable
|
||||
document-start:
|
||||
level: warning
|
||||
present: false
|
||||
line-length:
|
||||
level: warning
|
||||
max: 80
|
||||
allow-non-breakable-words: true
|
||||
allow-non-breakable-inline-mappings: true
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["../../tests/**/*", "../../src/**/*"],
|
||||
"exclude": ["../../dist", "../../node_modules", "../../coverage", "*.json"]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
name: "test-local"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'releases/*'
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@${{ gitea.ref_name }}
|
||||
with:
|
||||
token: ${{ secrets.GITEA_TOKEN }}
|
||||
- run: git ls-remote --tags origin
|
||||
@@ -0,0 +1,102 @@
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- releases/*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1 # todo: switch to v2
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run format-check
|
||||
- run: npm run lint
|
||||
- run: npm run pack
|
||||
- run: npm run gendocs
|
||||
- run: npm test
|
||||
- name: Verify no unstaged changes
|
||||
run: __test__/verify-no-unstaged-changes.sh
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
runs-on: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
|
||||
steps:
|
||||
# Clone this repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2-beta
|
||||
|
||||
# Basic checkout
|
||||
- name: Basic checkout
|
||||
uses: ./
|
||||
with:
|
||||
ref: test-data/v2/basic
|
||||
path: basic
|
||||
- name: Verify basic
|
||||
shell: bash
|
||||
run: __test__/verify-basic.sh
|
||||
|
||||
# Clean
|
||||
- name: Modify work tree
|
||||
shell: bash
|
||||
run: __test__/modify-work-tree.sh
|
||||
- name: Clean checkout
|
||||
uses: ./
|
||||
with:
|
||||
ref: test-data/v2/basic
|
||||
path: basic
|
||||
- name: Verify clean
|
||||
shell: bash
|
||||
run: __test__/verify-clean.sh
|
||||
|
||||
# Side by side
|
||||
- name: Side by side checkout 1
|
||||
uses: ./
|
||||
with:
|
||||
ref: test-data/v2/side-by-side-1
|
||||
path: side-by-side-1
|
||||
- name: Side by side checkout 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: LFS checkout
|
||||
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
|
||||
|
||||
test-job-container:
|
||||
runs-on: ubuntu-latest
|
||||
container: alpine:latest
|
||||
steps:
|
||||
# Clone this repo
|
||||
# todo: after v2-beta contains the latest changes, switch this to "uses: actions/checkout@v2-beta"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@a572f640b07e96fc5837b3adfa0e5a2ddd8dae21
|
||||
|
||||
# Basic checkout
|
||||
- name: Basic checkout
|
||||
uses: ./
|
||||
with:
|
||||
ref: test-data/v2/basic
|
||||
path: basic
|
||||
- name: Verify basic
|
||||
run: __test__/verify-basic.sh --archive
|
||||
+2
-103
@@ -1,103 +1,2 @@
|
||||
# Dependency directory
|
||||
node_modules
|
||||
|
||||
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# OS metadata
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Ignore built ts files
|
||||
__tests__/runner/*
|
||||
|
||||
# IDE files
|
||||
.idea
|
||||
.vscode
|
||||
*.code-workspace
|
||||
lib/
|
||||
node_modules/
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
dist/
|
||||
node_modules/
|
||||
coverage/
|
||||
lib/
|
||||
node_modules/
|
||||
+10
-15
@@ -1,16 +1,11 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": true,
|
||||
"arrowParens": "avoid",
|
||||
"proseWrap": "always",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid",
|
||||
"parser": "typescript"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## 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 +0,0 @@
|
||||
* @actions/maintainers
|
||||
@@ -1,6 +1,7 @@
|
||||
MIT License
|
||||
|
||||
Copyright TeamTekkit
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 GitHub, Inc. and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -9,13 +10,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,61 +1,105 @@
|
||||
<p align="center">
|
||||
<a href="https://git.teamtekk.it/actions/checkout"><img alt="GitHub Actions status" src="https://git.teamtekk.it/actions/checkout/workflows/test-local/badge.svg"></a>
|
||||
<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 V2 beta
|
||||
|
||||
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.git.teamtekk.it/en/articles/events-that-trigger-workflows) to learn what `$GITHUB_SHA` is for different kinds of events.
|
||||
By default, the repository that triggered the workflow is checked-out, for the ref/SHA that triggered the event.
|
||||
|
||||
Refer [here](https://help.github.com/en/articles/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events.
|
||||
|
||||
# What's new
|
||||
|
||||
- Improved fetch performance
|
||||
- The default behavior now fetches only the commit being checked-out
|
||||
- Script authenticated git commands
|
||||
- Persists the input `token` in the local git config
|
||||
- Enables your scripts to run authenticated git commands
|
||||
- Post-job cleanup removes the token
|
||||
- Opt out by setting the input `persist-credentials: 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
|
||||
- The input `path` is always relative to $GITHUB_WORKSPACE
|
||||
- Aligns better with container actions, where $GITHUB_WORKSPACE gets mapped in
|
||||
- Fallback to REST API download
|
||||
- When Git 2.18 or higher is not in the PATH, the REST API will be used to download the files
|
||||
- Removed input `submodules`
|
||||
|
||||
Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous versions.
|
||||
|
||||
# Usage
|
||||
|
||||
See [action.yml](action.yml)
|
||||
|
||||
Basic:
|
||||
|
||||
<!-- start usage -->
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v2-beta
|
||||
with:
|
||||
node-version: 10.x
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
# Repository name with owner. For example, actions/checkout
|
||||
# Default: ${{ github.repository }}
|
||||
repository: ''
|
||||
|
||||
# The branch, tag or SHA to checkout. When checking out the repository that
|
||||
# triggered a workflow, this defaults to the reference or SHA for that event.
|
||||
# Otherwise, defaults to `master`.
|
||||
ref: ''
|
||||
|
||||
# Auth token used to fetch the repository. The token is stored in the local git
|
||||
# config, which enables your scripts to run authenticated git commands. The
|
||||
# post-job step removes the token from the git config.
|
||||
# Default: ${{ github.token }}
|
||||
token: ''
|
||||
|
||||
# Whether to persist the token in the 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.
|
||||
# Default: 1
|
||||
fetch-depth: ''
|
||||
|
||||
# Whether to download Git-LFS files
|
||||
# Default: false
|
||||
lfs: ''
|
||||
```
|
||||
<!-- end usage -->
|
||||
|
||||
By default, the branch or tag ref that triggered the workflow will be checked out, `${{ github.token }}` will be used for any Git server authentication. 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`:
|
||||
## Checkout a different branch
|
||||
|
||||
Checkout different branch from the workflow repository:
|
||||
```yaml
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2-beta
|
||||
with:
|
||||
ref: some-branch
|
||||
```
|
||||
|
||||
Checkout different private repository:
|
||||
## Checkout a different, private repository
|
||||
|
||||
```yaml
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2-beta
|
||||
with:
|
||||
repository: myAccount/myRepository
|
||||
ref: refs/heads/release
|
||||
token: ${{ secrets.GitHub_PAT }} // `GitHub_PAT` is a secret contains your PAT.
|
||||
ref: refs/heads/master
|
||||
token: ${{ secrets.GitHub_PAT }} # `GitHub_PAT` is a secret that 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 the HEAD commit of a PR, rather than the merge commit
|
||||
|
||||
Checkout private submodules:
|
||||
```yaml
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2-beta
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GitHub_PAT }} // `GitHub_PAT` is a secret contains your PAT.
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
```
|
||||
> - `with.token` will be used as `Basic` authentication header for https requests talk to https://git.teamtekk.it from `git(.exe)`, ensure those private submodules are configured via `https` not `ssh`.
|
||||
> - `${{ github.token }}` only has permission to the workflow triggering repository. If the repository contains any submodules that comes from private repository, you will have to add your PAT as secret and use the secret in `with.token` to make `checkout` action work.
|
||||
|
||||
For more details, see [Contexts and expression syntax for GitHub Actions](https://help.git.teamtekk.it/en/articles/contexts-and-expression-syntax-for-github-actions) and [Creating and using secrets (encrypted variables)](https://help.git.teamtekk.it/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables)
|
||||
# License
|
||||
|
||||
# Changelog
|
||||
|
||||
## v1.1.0 (unreleased)
|
||||
- Persist `with.token` or `${{ github.token }}` into checkout repository's git config as `http.https://git.teamtekk.it/.extraheader=AUTHORIZATION: basic ***` to better support scripting git
|
||||
The scripts and documentation in this project are released under the [MIT License](LICENSE)
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,120 @@
|
||||
import * as assert from 'assert'
|
||||
import * as path from 'path'
|
||||
import {ISourceSettings} from '../lib/git-source-provider'
|
||||
|
||||
const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE']
|
||||
const gitHubWorkspace = path.resolve('/checkout-tests/workspace')
|
||||
|
||||
// Late bind
|
||||
let inputHelper: any
|
||||
|
||||
// Mock @actions/core
|
||||
let inputs = {} as any
|
||||
const mockCore = jest.genMockFromModule('@actions/core') as any
|
||||
mockCore.getInput = (name: string) => {
|
||||
return inputs[name]
|
||||
}
|
||||
|
||||
// Mock @actions/github
|
||||
const mockGitHub = jest.genMockFromModule('@actions/github') as any
|
||||
mockGitHub.context = {
|
||||
repo: {
|
||||
owner: 'some-owner',
|
||||
repo: 'some-repo'
|
||||
},
|
||||
ref: 'refs/heads/some-ref',
|
||||
sha: '1234567890123456789012345678901234567890'
|
||||
}
|
||||
|
||||
// Mock ./fs-helper
|
||||
const mockFSHelper = jest.genMockFromModule('../lib/fs-helper') as any
|
||||
mockFSHelper.directoryExistsSync = (path: string) => path == gitHubWorkspace
|
||||
|
||||
describe('input-helper tests', () => {
|
||||
beforeAll(() => {
|
||||
// GitHub workspace
|
||||
process.env['GITHUB_WORKSPACE'] = gitHubWorkspace
|
||||
|
||||
// Mocks
|
||||
jest.setMock('@actions/core', mockCore)
|
||||
jest.setMock('@actions/github', mockGitHub)
|
||||
jest.setMock('../lib/fs-helper', mockFSHelper)
|
||||
|
||||
// Now import
|
||||
inputHelper = require('../lib/input-helper')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset inputs
|
||||
inputs = {}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
// Reset GitHub workspace
|
||||
delete process.env['GITHUB_WORKSPACE']
|
||||
if (originalGitHubWorkspace) {
|
||||
process.env['GITHUB_WORKSPACE'] = originalGitHubWorkspace
|
||||
}
|
||||
|
||||
// Reset modules
|
||||
jest.resetModules()
|
||||
})
|
||||
|
||||
it('sets defaults', () => {
|
||||
const settings: ISourceSettings = 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)
|
||||
})
|
||||
|
||||
it('requires qualified repo', () => {
|
||||
inputs.repository = 'some-unqualified-repo'
|
||||
assert.throws(() => {
|
||||
inputHelper.getInputs()
|
||||
}, /Invalid repository 'some-unqualified-repo'/)
|
||||
})
|
||||
|
||||
it('roots path', () => {
|
||||
inputs.path = 'some-directory/some-subdirectory'
|
||||
const settings: ISourceSettings = inputHelper.getInputs()
|
||||
expect(settings.repositoryPath).toBe(
|
||||
path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory')
|
||||
)
|
||||
})
|
||||
|
||||
it('sets correct default ref/sha for other repo', () => {
|
||||
inputs.repository = 'some-owner/some-other-repo'
|
||||
const settings: ISourceSettings = inputHelper.getInputs()
|
||||
expect(settings.ref).toBe('refs/heads/master')
|
||||
expect(settings.commit).toBeFalsy()
|
||||
})
|
||||
|
||||
it('sets ref to empty when explicit sha', () => {
|
||||
inputs.ref = '1111111111222222222233333333334444444444'
|
||||
const settings: ISourceSettings = inputHelper.getInputs()
|
||||
expect(settings.ref).toBeFalsy()
|
||||
expect(settings.commit).toBe('1111111111222222222233333333334444444444')
|
||||
})
|
||||
|
||||
it('sets sha to empty when explicit ref', () => {
|
||||
inputs.ref = 'refs/heads/some-other-ref'
|
||||
const settings: ISourceSettings = inputHelper.getInputs()
|
||||
expect(settings.ref).toBe('refs/heads/some-other-ref')
|
||||
expect(settings.commit).toBeFalsy()
|
||||
})
|
||||
|
||||
it('gives good error message for submodules input', () => {
|
||||
inputs.submodules = 'true'
|
||||
assert.throws(() => {
|
||||
inputHelper.getInputs()
|
||||
}, /The input 'submodules' is not supported/)
|
||||
})
|
||||
})
|
||||
Executable
+10
@@ -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
|
||||
@@ -0,0 +1,168 @@
|
||||
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.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.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.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')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
const mockCore = jest.genMockFromModule('@actions/core') as any
|
||||
mockCore.info = (message: string) => {
|
||||
info.push(message)
|
||||
}
|
||||
let info: string[]
|
||||
let retryHelper: any
|
||||
|
||||
describe('retry-helper tests', () => {
|
||||
beforeAll(() => {
|
||||
// Mocks
|
||||
jest.setMock('@actions/core', mockCore)
|
||||
|
||||
// Now import
|
||||
const retryHelperModule = require('../lib/retry-helper')
|
||||
retryHelper = new retryHelperModule.RetryHelper(3, 0, 0)
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset info
|
||||
info = []
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
// Reset modules
|
||||
jest.resetModules()
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
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/)
|
||||
})
|
||||
})
|
||||
Executable
+24
@@ -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/master:refs/remotes/origin/master
|
||||
fi
|
||||
Executable
+13
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+17
@@ -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 all"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+11
@@ -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
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "./submodules-not-checked-out/regular-file.txt" ]; then
|
||||
echo "Expected regular file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "./submodules-not-checked-out/submodule-level-1/submodule-file.txt" ]; then
|
||||
echo "Unexpected submodule file exists"
|
||||
exit 1
|
||||
fi
|
||||
+26
-23
@@ -1,32 +1,35 @@
|
||||
name: 'Checkout'
|
||||
description: 'Checkout a Git repository.'
|
||||
description: 'Checkout a Git repository at a particular version'
|
||||
inputs:
|
||||
repository:
|
||||
description: 'Repository name'
|
||||
default: ${{ gitea.repository }}
|
||||
description: 'Repository name with owner. For example, actions/checkout'
|
||||
default: ${{ github.repository }}
|
||||
ref:
|
||||
description: 'Ref to checkout (SHA, branch, tag)'
|
||||
default: ${{ gitea.ref }}
|
||||
description: >
|
||||
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, defaults to `master`.
|
||||
token:
|
||||
description: 'Access token for clone repository'
|
||||
default: ${{ gitea.token }}
|
||||
clean:
|
||||
description: 'If true, execute `execute git clean -ffdx && git reset --hard HEAD` before fetching'
|
||||
description: >
|
||||
Auth token used to fetch the repository. The token is stored in the local
|
||||
git config, which enables your scripts to run authenticated git commands.
|
||||
The post-job step removes the token from the git config.
|
||||
default: ${{ github.token }}
|
||||
persist-credentials:
|
||||
description: 'Whether to persist the token in the git config'
|
||||
default: true
|
||||
submodules:
|
||||
description: 'Whether to include submodules: false to exclude submodules, true to include only one level of submodules, or recursive to recursively clone submodules; defaults to false'
|
||||
default: 'false'
|
||||
lfs:
|
||||
description: 'Whether to download Git-LFS files; defaults to false'
|
||||
default: false
|
||||
fetch-depth:
|
||||
description: 'The depth of commits to ask Git to fetch; defaults to no limit'
|
||||
default: '0'
|
||||
path:
|
||||
description: 'Optional path to check out source code'
|
||||
default: ${{ gitea.workspace }}
|
||||
server-url:
|
||||
default: ${{ gitea.server_url }}
|
||||
description: 'Relative path under $GITHUB_WORKSPACE to place the repository'
|
||||
clean:
|
||||
description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching'
|
||||
default: true
|
||||
fetch-depth:
|
||||
description: 'Number of commits to fetch. 0 indicates all history.'
|
||||
default: 1
|
||||
lfs:
|
||||
description: 'Whether to download Git-LFS files'
|
||||
default: false
|
||||
runs:
|
||||
using: node20
|
||||
using: node12
|
||||
main: dist/index.js
|
||||
post: dist/index.js
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="116" height="20" role="img" aria-label="Coverage: 22.72%"><title>Coverage: 22.72%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#e05d44"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">22.72%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">22.72%</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
Vendored
+15842
-26224
File diff suppressed because it is too large.
Load diff
Vendored
-120
@@ -1,120 +0,0 @@
|
||||
@actions/core
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@actions/http-client
|
||||
MIT
|
||||
Actions Http Client for Node.js
|
||||
|
||||
Copyright (c) GitHub, Inc.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
@fastify/busboy
|
||||
MIT
|
||||
Copyright Brian White. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
|
||||
tunnel
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Koichi Kobayashi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
undici
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) Matteo Collina and Undici contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
uuid
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "checkout-git",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(fatal|error): (.*)$",
|
||||
"message": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
testRunner: 'jest-circus/runner',
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
Generated
+7053
File diff suppressed because it is too large.
Load diff
+39
-74
@@ -1,90 +1,55 @@
|
||||
{
|
||||
"name": "checkout",
|
||||
"description": "GitHub Actions TypeScript template",
|
||||
"version": "0.0.0",
|
||||
"author": "actions",
|
||||
"private": true,
|
||||
"homepage": "https://git.teamtekk.it/actions/checkout",
|
||||
"version": "2.0.0",
|
||||
"description": "checkout action",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"pack": "ncc build",
|
||||
"gendocs": "node lib/misc/generate-docs.js",
|
||||
"test": "jest",
|
||||
"all": "npm run build && npm run format && npm run lint && npm run pack && npm run gendocs && npm test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.teamtekk.it/actions/checkout.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://git.teamtekk.it/actions/checkout/issues"
|
||||
"url": "git+https://github.com/actions/checkout.git"
|
||||
},
|
||||
"keywords": [
|
||||
"github",
|
||||
"actions",
|
||||
"node",
|
||||
"checkout"
|
||||
],
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle": "npm run format:write && npm run package",
|
||||
"ci-test": "jest",
|
||||
"coverage": "make-coverage-badge --output-path ./badges/coverage.svg",
|
||||
"format:write": "prettier --write **/*.ts",
|
||||
"format:check": "prettier --check **/*.ts",
|
||||
"lint": "npx eslint . -c ./.gitea/linters/.eslintrc.yml",
|
||||
"package": "ncc build src/index.ts --license licenses.txt",
|
||||
"package:watch": "npm run package -- --watch",
|
||||
"test": "jest",
|
||||
"all": "npm run format:write && npm run lint && npm run test && npm run coverage && npm run package"
|
||||
},
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
"verbose": true,
|
||||
"clearMocks": true,
|
||||
"testEnvironment": "node",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"ts"
|
||||
],
|
||||
"testMatch": [
|
||||
"**/*.test.ts"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/dist/"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"coverageReporters": [
|
||||
"json-summary",
|
||||
"text",
|
||||
"lcov"
|
||||
],
|
||||
"collectCoverage": true,
|
||||
"collectCoverageFrom": [
|
||||
"./src/**"
|
||||
]
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/checkout/issues"
|
||||
},
|
||||
"homepage": "https://github.com/actions/checkout#readme",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1"
|
||||
"@actions/core": "^1.1.3",
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/github": "^2.0.0",
|
||||
"@actions/io": "^1.0.1",
|
||||
"@actions/tool-cache": "^1.1.2",
|
||||
"uuid": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.11.10",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
||||
"@typescript-eslint/parser": "^6.19.1",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-github": "^4.10.1",
|
||||
"eslint-plugin-jest": "^27.6.3",
|
||||
"eslint-plugin-jsonc": "^2.13.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"make-coverage-badge": "^1.2.0",
|
||||
"prettier": "^3.2.4",
|
||||
"prettier-eslint": "^16.3.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.3.3"
|
||||
"@types/jest": "^24.0.23",
|
||||
"@types/node": "^12.7.12",
|
||||
"@types/uuid": "^3.4.6",
|
||||
"@typescript-eslint/parser": "^2.8.0",
|
||||
"@zeit/ncc": "^0.20.5",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-github": "^2.0.0",
|
||||
"eslint-plugin-jest": "^22.21.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest-circus": "^24.9.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"prettier": "^1.19.1",
|
||||
"ts-jest": "^24.2.0",
|
||||
"typescript": "^3.6.4"
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# About:
|
||||
#
|
||||
# This is a helper script to tag and push a new release. GitHub Actions use
|
||||
# release tags to allow users to select a specific version of the action to use.
|
||||
#
|
||||
# See: https://github.com/actions/typescript-action#publishing-a-new-release
|
||||
#
|
||||
# This script will do the following:
|
||||
#
|
||||
# 1. Get the latest release tag
|
||||
# 2. Prompt the user for a new release tag
|
||||
# 3. Tag the new release
|
||||
# 4. Push the new tag to the remote
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# script/release
|
||||
|
||||
# Terminal colors
|
||||
OFF='\033[0m'
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
|
||||
# Get the latest release tag
|
||||
latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
|
||||
|
||||
if [[ -z "$latest_tag" ]]; then
|
||||
# There are no existing release tags
|
||||
echo -e "No tags found (yet) - Continue to create and push your first tag"
|
||||
latest_tag="[unknown]"
|
||||
fi
|
||||
|
||||
# Display the latest release tag
|
||||
echo -e "The latest release tag is: ${BLUE}${latest_tag}${OFF}"
|
||||
|
||||
# Prompt the user for the new release tag
|
||||
read -r -p 'Enter a new release tag (vX.X.X format): ' new_tag
|
||||
|
||||
# Validate the new release tag
|
||||
tag_regex='v[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
if echo "$new_tag" | grep -q -E "$tag_regex"; then
|
||||
echo -e "Tag: ${BLUE}$new_tag${OFF} is valid"
|
||||
else
|
||||
# Release tag is not `vX.X.X` format
|
||||
echo -e "Tag: ${BLUE}$new_tag${OFF} is ${RED}not valid${OFF} (must be in vX.X.X format)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Tag the new release
|
||||
git tag -a "$new_tag" -m "$new_tag Release"
|
||||
echo -e "${GREEN}Tagged: $new_tag${OFF}"
|
||||
|
||||
# Push the new tag to the remote
|
||||
git push --tags
|
||||
echo -e "${GREEN}Release tag pushed to remote${OFF}"
|
||||
echo -e "${GREEN}Done!${OFF}"
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
const Base64 = {
|
||||
_keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
|
||||
|
||||
encode: function (input: string): string {
|
||||
let output = "";
|
||||
let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
|
||||
input = Base64._utf8_encode(input);
|
||||
|
||||
while (i < input.length) {
|
||||
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
|
||||
output = output +
|
||||
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
|
||||
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
|
||||
}
|
||||
return output;
|
||||
},
|
||||
|
||||
decode: function (input: string): string {
|
||||
let output = "";
|
||||
let chr1, chr2, chr3;
|
||||
let enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
while (i < input.length) {
|
||||
|
||||
enc1 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc3 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc4 = this._keyStr.indexOf(input.charAt(i++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
output = output + String.fromCharCode(chr1);
|
||||
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
}
|
||||
|
||||
output = Base64._utf8_decode(output);
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
_utf8_encode: function (string: string): string {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
let utftext = "";
|
||||
|
||||
for (let n = 0; n < string.length; n++) {
|
||||
|
||||
let c = string.charCodeAt(n);
|
||||
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
}
|
||||
else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return utftext;
|
||||
},
|
||||
|
||||
_utf8_decode: function (utftext: string): string {
|
||||
let string = "";
|
||||
let i = 0;
|
||||
let c = 0;
|
||||
let c1 = 0;
|
||||
let c2 = 0;
|
||||
let c3 = 0;
|
||||
|
||||
while (i < utftext.length) {
|
||||
|
||||
c = utftext.charCodeAt(i);
|
||||
|
||||
if (c < 128) {
|
||||
string += String.fromCharCode(c);
|
||||
i++;
|
||||
}
|
||||
else if ((c > 191) && (c < 224)) {
|
||||
c2 = utftext.charCodeAt(i + 1);
|
||||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
c2 = utftext.charCodeAt(i + 1);
|
||||
c3 = utftext.charCodeAt(i + 2);
|
||||
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
};
|
||||
|
||||
export default Base64;
|
||||
@@ -0,0 +1,77 @@
|
||||
import * as fs from 'fs'
|
||||
|
||||
export function directoryExistsSync(path: string, required?: boolean): boolean {
|
||||
if (!path) {
|
||||
throw new Error("Arg 'path' must not be empty")
|
||||
}
|
||||
|
||||
let stats: fs.Stats
|
||||
try {
|
||||
stats = fs.statSync(path)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
if (!required) {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(`Directory '${path}' does not exist`)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Encountered an error when checking whether path '${path}' exists: ${error.message}`
|
||||
)
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return true
|
||||
} else if (!required) {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(`Directory '${path}' does not exist`)
|
||||
}
|
||||
|
||||
export function existsSync(path: string): boolean {
|
||||
if (!path) {
|
||||
throw new Error("Arg 'path' must not be empty")
|
||||
}
|
||||
|
||||
try {
|
||||
fs.statSync(path)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Encountered an error when checking whether path '${path}' exists: ${error.message}`
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function fileExistsSync(path: string): boolean {
|
||||
if (!path) {
|
||||
throw new Error("Arg 'path' must not be empty")
|
||||
}
|
||||
|
||||
let stats: fs.Stats
|
||||
try {
|
||||
stats = fs.statSync(path)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Encountered an error when checking whether path '${path}' exists: ${error.message}`
|
||||
)
|
||||
}
|
||||
|
||||
if (!stats.isDirectory()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as exec from '@actions/exec'
|
||||
import * as fshelper from './fs-helper'
|
||||
import * as io from '@actions/io'
|
||||
import * as path from 'path'
|
||||
import * as retryHelper from './retry-helper'
|
||||
import {GitVersion} from './git-version'
|
||||
|
||||
// Auth header not supported before 2.9
|
||||
// Wire protocol v2 not supported before 2.18
|
||||
export const MinimumGitVersion = new GitVersion('2.18')
|
||||
|
||||
export interface IGitCommandManager {
|
||||
branchDelete(remote: boolean, branch: string): Promise<void>
|
||||
branchExists(remote: boolean, pattern: string): Promise<boolean>
|
||||
branchList(remote: boolean): Promise<string[]>
|
||||
checkout(ref: string, startPoint: string): Promise<void>
|
||||
checkoutDetach(): Promise<void>
|
||||
config(configKey: string, configValue: string): Promise<void>
|
||||
configExists(configKey: string): Promise<boolean>
|
||||
fetch(fetchDepth: number, refSpec: string[]): Promise<void>
|
||||
getWorkingDirectory(): string
|
||||
init(): Promise<void>
|
||||
isDetached(): Promise<boolean>
|
||||
lfsFetch(ref: string): Promise<void>
|
||||
lfsInstall(): Promise<void>
|
||||
log1(): Promise<void>
|
||||
remoteAdd(remoteName: string, remoteUrl: string): Promise<void>
|
||||
tagExists(pattern: string): Promise<boolean>
|
||||
tryClean(): Promise<boolean>
|
||||
tryConfigUnset(configKey: string): Promise<boolean>
|
||||
tryDisableAutomaticGarbageCollection(): Promise<boolean>
|
||||
tryGetFetchUrl(): Promise<string>
|
||||
tryReset(): Promise<boolean>
|
||||
}
|
||||
|
||||
export async function CreateCommandManager(
|
||||
workingDirectory: string,
|
||||
lfs: boolean
|
||||
): Promise<IGitCommandManager> {
|
||||
return await GitCommandManager.createCommandManager(workingDirectory, lfs)
|
||||
}
|
||||
|
||||
class GitCommandManager {
|
||||
private gitEnv = {
|
||||
GIT_TERMINAL_PROMPT: '0', // Disable git prompt
|
||||
GCM_INTERACTIVE: 'Never' // Disable prompting for git credential manager
|
||||
}
|
||||
private gitPath = ''
|
||||
private lfs = false
|
||||
private workingDirectory = ''
|
||||
|
||||
// Private constructor; use createCommandManager()
|
||||
private constructor() {}
|
||||
|
||||
async branchDelete(remote: boolean, branch: string): Promise<void> {
|
||||
const args = ['branch', '--delete', '--force']
|
||||
if (remote) {
|
||||
args.push('--remote')
|
||||
}
|
||||
args.push(branch)
|
||||
|
||||
await this.execGit(args)
|
||||
}
|
||||
|
||||
async branchExists(remote: boolean, pattern: string): Promise<boolean> {
|
||||
const args = ['branch', '--list']
|
||||
if (remote) {
|
||||
args.push('--remote')
|
||||
}
|
||||
args.push(pattern)
|
||||
|
||||
const output = await this.execGit(args)
|
||||
return !!output.stdout.trim()
|
||||
}
|
||||
|
||||
async branchList(remote: boolean): Promise<string[]> {
|
||||
const result: string[] = []
|
||||
|
||||
// Note, this implementation uses "rev-parse --symbolic" because the output from
|
||||
// "branch --list" is more difficult when in a detached HEAD state.
|
||||
|
||||
const args = ['rev-parse', '--symbolic']
|
||||
if (remote) {
|
||||
args.push('--remotes=origin')
|
||||
} else {
|
||||
args.push('--branches')
|
||||
}
|
||||
|
||||
const output = await this.execGit(args)
|
||||
|
||||
for (let branch of output.stdout.trim().split('\n')) {
|
||||
branch = branch.trim()
|
||||
if (branch) {
|
||||
result.push(branch)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async checkout(ref: string, startPoint: string): Promise<void> {
|
||||
const args = ['checkout', '--progress', '--force']
|
||||
if (startPoint) {
|
||||
args.push('-B', ref, startPoint)
|
||||
} else {
|
||||
args.push(ref)
|
||||
}
|
||||
|
||||
await this.execGit(args)
|
||||
}
|
||||
|
||||
async checkoutDetach(): Promise<void> {
|
||||
const args = ['checkout', '--detach']
|
||||
await this.execGit(args)
|
||||
}
|
||||
|
||||
async config(configKey: string, configValue: string): Promise<void> {
|
||||
await this.execGit(['config', '--local', configKey, configValue])
|
||||
}
|
||||
|
||||
async configExists(configKey: string): Promise<boolean> {
|
||||
const pattern = configKey.replace(/[^a-zA-Z0-9_]/g, x => {
|
||||
return `\\${x}`
|
||||
})
|
||||
const output = await this.execGit(
|
||||
['config', '--local', '--name-only', '--get-regexp', pattern],
|
||||
true
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async fetch(fetchDepth: number, refSpec: string[]): Promise<void> {
|
||||
const args = [
|
||||
'-c',
|
||||
'protocol.version=2',
|
||||
'fetch',
|
||||
'--no-tags',
|
||||
'--prune',
|
||||
'--progress',
|
||||
'--no-recurse-submodules'
|
||||
]
|
||||
if (fetchDepth > 0) {
|
||||
args.push(`--depth=${fetchDepth}`)
|
||||
} else if (
|
||||
fshelper.fileExistsSync(
|
||||
path.join(this.workingDirectory, '.git', 'shallow')
|
||||
)
|
||||
) {
|
||||
args.push('--unshallow')
|
||||
}
|
||||
|
||||
args.push('origin')
|
||||
for (const arg of refSpec) {
|
||||
args.push(arg)
|
||||
}
|
||||
|
||||
const that = this
|
||||
await retryHelper.execute(async () => {
|
||||
await that.execGit(args)
|
||||
})
|
||||
}
|
||||
|
||||
getWorkingDirectory(): string {
|
||||
return this.workingDirectory
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
await this.execGit(['init', this.workingDirectory])
|
||||
}
|
||||
|
||||
async isDetached(): Promise<boolean> {
|
||||
// Note, this implementation uses "branch --show-current" because
|
||||
// "rev-parse --symbolic-full-name HEAD" can fail on a new repo
|
||||
// with nothing checked out.
|
||||
|
||||
const output = await this.execGit(['branch', '--show-current'])
|
||||
return output.stdout.trim() === ''
|
||||
}
|
||||
|
||||
async lfsFetch(ref: string): Promise<void> {
|
||||
const args = ['lfs', 'fetch', 'origin', ref]
|
||||
|
||||
const that = this
|
||||
await retryHelper.execute(async () => {
|
||||
await that.execGit(args)
|
||||
})
|
||||
}
|
||||
|
||||
async lfsInstall(): Promise<void> {
|
||||
await this.execGit(['lfs', 'install', '--local'])
|
||||
}
|
||||
|
||||
async log1(): Promise<void> {
|
||||
await this.execGit(['log', '-1'])
|
||||
}
|
||||
|
||||
async remoteAdd(remoteName: string, remoteUrl: string): Promise<void> {
|
||||
await this.execGit(['remote', 'add', remoteName, remoteUrl])
|
||||
}
|
||||
|
||||
async tagExists(pattern: string): Promise<boolean> {
|
||||
const output = await this.execGit(['tag', '--list', pattern])
|
||||
return !!output.stdout.trim()
|
||||
}
|
||||
|
||||
async tryClean(): Promise<boolean> {
|
||||
const output = await this.execGit(['clean', '-ffdx'], true)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async tryConfigUnset(configKey: string): Promise<boolean> {
|
||||
const output = await this.execGit(
|
||||
['config', '--local', '--unset-all', configKey],
|
||||
true
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async tryDisableAutomaticGarbageCollection(): Promise<boolean> {
|
||||
const output = await this.execGit(
|
||||
['config', '--local', 'gc.auto', '0'],
|
||||
true
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async tryGetFetchUrl(): Promise<string> {
|
||||
const output = await this.execGit(
|
||||
['config', '--local', '--get', 'remote.origin.url'],
|
||||
true
|
||||
)
|
||||
|
||||
if (output.exitCode !== 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const stdout = output.stdout.trim()
|
||||
if (stdout.includes('\n')) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return stdout
|
||||
}
|
||||
|
||||
async tryReset(): Promise<boolean> {
|
||||
const output = await this.execGit(['reset', '--hard', 'HEAD'], true)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
static async createCommandManager(
|
||||
workingDirectory: string,
|
||||
lfs: boolean
|
||||
): Promise<GitCommandManager> {
|
||||
const result = new GitCommandManager()
|
||||
await result.initializeCommandManager(workingDirectory, lfs)
|
||||
return result
|
||||
}
|
||||
|
||||
private async execGit(
|
||||
args: string[],
|
||||
allowAllExitCodes = false
|
||||
): Promise<GitOutput> {
|
||||
fshelper.directoryExistsSync(this.workingDirectory, true)
|
||||
|
||||
const result = new GitOutput()
|
||||
|
||||
const env = {}
|
||||
for (const key of Object.keys(process.env)) {
|
||||
env[key] = process.env[key]
|
||||
}
|
||||
for (const key of Object.keys(this.gitEnv)) {
|
||||
env[key] = this.gitEnv[key]
|
||||
}
|
||||
|
||||
const stdout: string[] = []
|
||||
|
||||
const options = {
|
||||
cwd: this.workingDirectory,
|
||||
env,
|
||||
ignoreReturnCode: allowAllExitCodes,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
stdout.push(data.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.exitCode = await exec.exec(`"${this.gitPath}"`, args, options)
|
||||
result.stdout = stdout.join('')
|
||||
return result
|
||||
}
|
||||
|
||||
private async initializeCommandManager(
|
||||
workingDirectory: string,
|
||||
lfs: boolean
|
||||
): Promise<void> {
|
||||
this.workingDirectory = workingDirectory
|
||||
|
||||
// Git-lfs will try to pull down assets if any of the local/user/system setting exist.
|
||||
// If the user didn't enable `LFS` in their pipeline definition, disable LFS fetch/checkout.
|
||||
this.lfs = lfs
|
||||
if (!this.lfs) {
|
||||
this.gitEnv['GIT_LFS_SKIP_SMUDGE'] = '1'
|
||||
}
|
||||
|
||||
this.gitPath = await io.which('git', true)
|
||||
|
||||
// Git version
|
||||
core.debug('Getting git version')
|
||||
let gitVersion = new GitVersion()
|
||||
let gitOutput = await this.execGit(['version'])
|
||||
let stdout = gitOutput.stdout.trim()
|
||||
if (!stdout.includes('\n')) {
|
||||
const match = stdout.match(/\d+\.\d+(\.\d+)?/)
|
||||
if (match) {
|
||||
gitVersion = new GitVersion(match[0])
|
||||
}
|
||||
}
|
||||
if (!gitVersion.isValid()) {
|
||||
throw new Error('Unable to determine git version')
|
||||
}
|
||||
|
||||
// Minimum git version
|
||||
if (!gitVersion.checkMinimum(MinimumGitVersion)) {
|
||||
throw new Error(
|
||||
`Minimum required git version is ${MinimumGitVersion}. Your git ('${this.gitPath}') is ${gitVersion}`
|
||||
)
|
||||
}
|
||||
|
||||
if (this.lfs) {
|
||||
// Git-lfs version
|
||||
core.debug('Getting git-lfs version')
|
||||
let gitLfsVersion = new GitVersion()
|
||||
const gitLfsPath = await io.which('git-lfs', true)
|
||||
gitOutput = await this.execGit(['lfs', 'version'])
|
||||
stdout = gitOutput.stdout.trim()
|
||||
if (!stdout.includes('\n')) {
|
||||
const match = stdout.match(/\d+\.\d+(\.\d+)?/)
|
||||
if (match) {
|
||||
gitLfsVersion = new GitVersion(match[0])
|
||||
}
|
||||
}
|
||||
if (!gitLfsVersion.isValid()) {
|
||||
throw new Error('Unable to determine git-lfs version')
|
||||
}
|
||||
|
||||
// Minimum git-lfs version
|
||||
// Note:
|
||||
// - Auth header not supported before 2.1
|
||||
const minimumGitLfsVersion = new GitVersion('2.1')
|
||||
if (!gitLfsVersion.checkMinimum(minimumGitLfsVersion)) {
|
||||
throw new Error(
|
||||
`Minimum required git-lfs version is ${minimumGitLfsVersion}. Your git-lfs ('${gitLfsPath}') is ${gitLfsVersion}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the user agent
|
||||
const gitHttpUserAgent = `git/${gitVersion} (github-actions-checkout)`
|
||||
core.debug(`Set git useragent to: ${gitHttpUserAgent}`)
|
||||
this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent
|
||||
}
|
||||
}
|
||||
|
||||
class GitOutput {
|
||||
stdout = ''
|
||||
exitCode = 0
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as fsHelper from './fs-helper'
|
||||
import * as gitCommandManager from './git-command-manager'
|
||||
import * as githubApiHelper from './github-api-helper'
|
||||
import * as io from '@actions/io'
|
||||
import * as path from 'path'
|
||||
import * as refHelper from './ref-helper'
|
||||
import * as stateHelper from './state-helper'
|
||||
import {IGitCommandManager} from './git-command-manager'
|
||||
|
||||
const authConfigKey = `http.https://github.com/.extraheader`
|
||||
|
||||
export interface ISourceSettings {
|
||||
repositoryPath: string
|
||||
repositoryOwner: string
|
||||
repositoryName: string
|
||||
ref: string
|
||||
commit: string
|
||||
clean: boolean
|
||||
fetchDepth: number
|
||||
lfs: boolean
|
||||
authToken: string
|
||||
persistCredentials: boolean
|
||||
}
|
||||
|
||||
export async function getSource(settings: ISourceSettings): Promise<void> {
|
||||
// Repository URL
|
||||
core.info(
|
||||
`Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`
|
||||
)
|
||||
const repositoryUrl = `https://github.com/${encodeURIComponent(
|
||||
settings.repositoryOwner
|
||||
)}/${encodeURIComponent(settings.repositoryName)}`
|
||||
|
||||
// Remove conflicting file path
|
||||
if (fsHelper.fileExistsSync(settings.repositoryPath)) {
|
||||
await io.rmRF(settings.repositoryPath)
|
||||
}
|
||||
|
||||
// Create directory
|
||||
let isExisting = true
|
||||
if (!fsHelper.directoryExistsSync(settings.repositoryPath)) {
|
||||
isExisting = false
|
||||
await io.mkdirP(settings.repositoryPath)
|
||||
}
|
||||
|
||||
// Git command manager
|
||||
const git = await getGitCommandManager(settings)
|
||||
|
||||
// Prepare existing directory, otherwise recreate
|
||||
if (isExisting) {
|
||||
await prepareExistingDirectory(
|
||||
git,
|
||||
settings.repositoryPath,
|
||||
repositoryUrl,
|
||||
settings.clean
|
||||
)
|
||||
}
|
||||
|
||||
if (!git) {
|
||||
// Downloading using REST API
|
||||
core.info(`The repository will be downloaded using the GitHub REST API`)
|
||||
core.info(
|
||||
`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`
|
||||
)
|
||||
await githubApiHelper.downloadRepository(
|
||||
settings.authToken,
|
||||
settings.repositoryOwner,
|
||||
settings.repositoryName,
|
||||
settings.ref,
|
||||
settings.commit,
|
||||
settings.repositoryPath
|
||||
)
|
||||
} else {
|
||||
// Save state for POST action
|
||||
stateHelper.setRepositoryPath(settings.repositoryPath)
|
||||
|
||||
// Initialize the repository
|
||||
if (
|
||||
!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))
|
||||
) {
|
||||
await git.init()
|
||||
await git.remoteAdd('origin', repositoryUrl)
|
||||
}
|
||||
|
||||
// Disable automatic garbage collection
|
||||
if (!(await git.tryDisableAutomaticGarbageCollection())) {
|
||||
core.warning(
|
||||
`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`
|
||||
)
|
||||
}
|
||||
|
||||
// Remove possible previous extraheader
|
||||
await removeGitConfig(git, authConfigKey)
|
||||
|
||||
try {
|
||||
// Config auth token
|
||||
await configureAuthToken(git, settings.authToken)
|
||||
|
||||
// LFS install
|
||||
if (settings.lfs) {
|
||||
await git.lfsInstall()
|
||||
}
|
||||
|
||||
// Fetch
|
||||
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
|
||||
await git.fetch(settings.fetchDepth, refSpec)
|
||||
|
||||
// Checkout info
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(
|
||||
git,
|
||||
settings.ref,
|
||||
settings.commit
|
||||
)
|
||||
|
||||
// LFS fetch
|
||||
// Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
|
||||
// Explicit lfs fetch will fetch lfs objects in parallel.
|
||||
if (settings.lfs) {
|
||||
await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref)
|
||||
}
|
||||
|
||||
// Checkout
|
||||
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
|
||||
|
||||
// Dump some info about the checked out commit
|
||||
await git.log1()
|
||||
} finally {
|
||||
if (!settings.persistCredentials) {
|
||||
await removeGitConfig(git, authConfigKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanup(repositoryPath: string): Promise<void> {
|
||||
// Repo exists?
|
||||
if (!fsHelper.fileExistsSync(path.join(repositoryPath, '.git', 'config'))) {
|
||||
return
|
||||
}
|
||||
fsHelper.directoryExistsSync(repositoryPath, true)
|
||||
|
||||
// Remove the config key
|
||||
const git = await gitCommandManager.CreateCommandManager(
|
||||
repositoryPath,
|
||||
false
|
||||
)
|
||||
await removeGitConfig(git, authConfigKey)
|
||||
}
|
||||
|
||||
async function getGitCommandManager(
|
||||
settings: ISourceSettings
|
||||
): Promise<IGitCommandManager> {
|
||||
core.info(`Working directory is '${settings.repositoryPath}'`)
|
||||
let git = (null as unknown) as IGitCommandManager
|
||||
try {
|
||||
return await gitCommandManager.CreateCommandManager(
|
||||
settings.repositoryPath,
|
||||
settings.lfs
|
||||
)
|
||||
} catch (err) {
|
||||
// Git is required for LFS
|
||||
if (settings.lfs) {
|
||||
throw err
|
||||
}
|
||||
|
||||
// Otherwise fallback to REST API
|
||||
return (null as unknown) as IGitCommandManager
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareExistingDirectory(
|
||||
git: IGitCommandManager,
|
||||
repositoryPath: string,
|
||||
repositoryUrl: string,
|
||||
clean: boolean
|
||||
): Promise<void> {
|
||||
let remove = false
|
||||
|
||||
// Check whether using git or REST API
|
||||
if (!git) {
|
||||
remove = true
|
||||
}
|
||||
// Fetch URL does not match
|
||||
else if (
|
||||
!fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) ||
|
||||
repositoryUrl !== (await git.tryGetFetchUrl())
|
||||
) {
|
||||
remove = true
|
||||
} else {
|
||||
// Delete any index.lock and shallow.lock left by a previously canceled run or crashed git process
|
||||
const lockPaths = [
|
||||
path.join(repositoryPath, '.git', 'index.lock'),
|
||||
path.join(repositoryPath, '.git', 'shallow.lock')
|
||||
]
|
||||
for (const lockPath of lockPaths) {
|
||||
try {
|
||||
await io.rmRF(lockPath)
|
||||
} catch (error) {
|
||||
core.debug(`Unable to delete '${lockPath}'. ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Checkout detached HEAD
|
||||
if (!(await git.isDetached())) {
|
||||
await git.checkoutDetach()
|
||||
}
|
||||
|
||||
// Remove all refs/heads/*
|
||||
let branches = await git.branchList(false)
|
||||
for (const branch of branches) {
|
||||
await git.branchDelete(false, branch)
|
||||
}
|
||||
|
||||
// Remove all refs/remotes/origin/* to avoid conflicts
|
||||
branches = await git.branchList(true)
|
||||
for (const branch of branches) {
|
||||
await git.branchDelete(true, branch)
|
||||
}
|
||||
|
||||
// Clean
|
||||
if (clean) {
|
||||
if (!(await git.tryClean())) {
|
||||
core.debug(
|
||||
`The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`
|
||||
)
|
||||
remove = true
|
||||
} else if (!(await git.tryReset())) {
|
||||
remove = true
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
core.warning(
|
||||
`Unable to clean or reset the repository. The repository will be recreated instead.`
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
core.warning(
|
||||
`Unable to prepare the existing repository. The repository will be recreated instead.`
|
||||
)
|
||||
remove = true
|
||||
}
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
// Delete the contents of the directory. Don't delete the directory itself
|
||||
// since it might be the current working directory.
|
||||
core.info(`Deleting the contents of '${repositoryPath}'`)
|
||||
for (const file of await fs.promises.readdir(repositoryPath)) {
|
||||
await io.rmRF(path.join(repositoryPath, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function configureAuthToken(
|
||||
git: IGitCommandManager,
|
||||
authToken: string
|
||||
): Promise<void> {
|
||||
// Configure a placeholder value. This approach avoids the credential being captured
|
||||
// by process creation audit events, which are commonly logged. For more information,
|
||||
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||
const placeholder = `AUTHORIZATION: basic ***`
|
||||
await git.config(authConfigKey, placeholder)
|
||||
|
||||
// Determine the basic credential value
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
core.setSecret(basicCredential)
|
||||
|
||||
// Replace the value in the config file
|
||||
const configPath = path.join(git.getWorkingDirectory(), '.git', 'config')
|
||||
let content = (await fs.promises.readFile(configPath)).toString()
|
||||
const placeholderIndex = content.indexOf(placeholder)
|
||||
if (
|
||||
placeholderIndex < 0 ||
|
||||
placeholderIndex != content.lastIndexOf(placeholder)
|
||||
) {
|
||||
throw new Error('Unable to replace auth placeholder in .git/config')
|
||||
}
|
||||
content = content.replace(
|
||||
placeholder,
|
||||
`AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
await fs.promises.writeFile(configPath, content)
|
||||
}
|
||||
|
||||
async function removeGitConfig(
|
||||
git: IGitCommandManager,
|
||||
configKey: string
|
||||
): Promise<void> {
|
||||
if (
|
||||
(await git.configExists(configKey)) &&
|
||||
!(await git.tryConfigUnset(configKey))
|
||||
) {
|
||||
// Load the config contents
|
||||
core.warning(`Failed to remove '${configKey}' from the git config`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
export class GitVersion {
|
||||
private readonly major: number = NaN
|
||||
private readonly minor: number = NaN
|
||||
private readonly patch: number = NaN
|
||||
|
||||
/**
|
||||
* Used for comparing the version of git and git-lfs against the minimum required version
|
||||
* @param version the version string, e.g. 1.2 or 1.2.3
|
||||
*/
|
||||
constructor(version?: string) {
|
||||
if (version) {
|
||||
const match = version.match(/^(\d+)\.(\d+)(\.(\d+))?$/)
|
||||
if (match) {
|
||||
this.major = Number(match[1])
|
||||
this.minor = Number(match[2])
|
||||
if (match[4]) {
|
||||
this.patch = Number(match[4])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the instance against a minimum required version
|
||||
* @param minimum Minimum version
|
||||
*/
|
||||
checkMinimum(minimum: GitVersion): boolean {
|
||||
if (!minimum.isValid()) {
|
||||
throw new Error('Arg minimum is not a valid version')
|
||||
}
|
||||
|
||||
// Major is insufficient
|
||||
if (this.major < minimum.major) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Major is equal
|
||||
if (this.major === minimum.major) {
|
||||
// Minor is insufficient
|
||||
if (this.minor < minimum.minor) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Minor is equal
|
||||
if (this.minor === minimum.minor) {
|
||||
// Patch is insufficient
|
||||
if (this.patch && this.patch < (minimum.patch || 0)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the instance was constructed from a valid version string
|
||||
*/
|
||||
isValid(): boolean {
|
||||
return !isNaN(this.major)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version as a string, e.g. 1.2 or 1.2.3
|
||||
*/
|
||||
toString(): string {
|
||||
let result = ''
|
||||
if (this.isValid()) {
|
||||
result = `${this.major}.${this.minor}`
|
||||
if (!isNaN(this.patch)) {
|
||||
result += `.${this.patch}`
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import * as assert from 'assert'
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as github from '@actions/github'
|
||||
import * as io from '@actions/io'
|
||||
import * as path from 'path'
|
||||
import * as retryHelper from './retry-helper'
|
||||
import * as toolCache from '@actions/tool-cache'
|
||||
import {default as uuid} from 'uuid/v4'
|
||||
import {ReposGetArchiveLinkParams} from '@octokit/rest'
|
||||
|
||||
const IS_WINDOWS = process.platform === 'win32'
|
||||
|
||||
export async function downloadRepository(
|
||||
authToken: string,
|
||||
owner: string,
|
||||
repo: string,
|
||||
ref: string,
|
||||
commit: string,
|
||||
repositoryPath: string
|
||||
): Promise<void> {
|
||||
// Download the archive
|
||||
let archiveData = await retryHelper.execute(async () => {
|
||||
core.info('Downloading the archive')
|
||||
return await downloadArchive(authToken, owner, repo, ref, commit)
|
||||
})
|
||||
|
||||
// Write archive to disk
|
||||
core.info('Writing archive to disk')
|
||||
const uniqueId = uuid()
|
||||
const archivePath = path.join(repositoryPath, `${uniqueId}.tar.gz`)
|
||||
await fs.promises.writeFile(archivePath, archiveData)
|
||||
archiveData = Buffer.from('') // Free memory
|
||||
|
||||
// Extract archive
|
||||
core.info('Extracting the archive')
|
||||
const extractPath = path.join(repositoryPath, uniqueId)
|
||||
await io.mkdirP(extractPath)
|
||||
if (IS_WINDOWS) {
|
||||
await toolCache.extractZip(archivePath, extractPath)
|
||||
} else {
|
||||
await toolCache.extractTar(archivePath, extractPath)
|
||||
}
|
||||
io.rmRF(archivePath)
|
||||
|
||||
// Determine the path of the repository content. The archive contains
|
||||
// a top-level folder and the repository content is inside.
|
||||
const archiveFileNames = await fs.promises.readdir(extractPath)
|
||||
assert.ok(
|
||||
archiveFileNames.length == 1,
|
||||
'Expected exactly one directory inside archive'
|
||||
)
|
||||
const archiveVersion = archiveFileNames[0] // The top-level folder name includes the short SHA
|
||||
core.info(`Resolved version ${archiveVersion}`)
|
||||
const tempRepositoryPath = path.join(extractPath, archiveVersion)
|
||||
|
||||
// Move the files
|
||||
for (const fileName of await fs.promises.readdir(tempRepositoryPath)) {
|
||||
const sourcePath = path.join(tempRepositoryPath, fileName)
|
||||
const targetPath = path.join(repositoryPath, fileName)
|
||||
if (IS_WINDOWS) {
|
||||
await io.cp(sourcePath, targetPath, {recursive: true}) // Copy on Windows (Windows Defender may have a lock)
|
||||
} else {
|
||||
await io.mv(sourcePath, targetPath)
|
||||
}
|
||||
}
|
||||
io.rmRF(extractPath)
|
||||
}
|
||||
|
||||
async function downloadArchive(
|
||||
authToken: string,
|
||||
owner: string,
|
||||
repo: string,
|
||||
ref: string,
|
||||
commit: string
|
||||
): Promise<Buffer> {
|
||||
const octokit = new github.GitHub(authToken)
|
||||
const params: ReposGetArchiveLinkParams = {
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
archive_format: IS_WINDOWS ? 'zipball' : 'tarball',
|
||||
ref: commit || ref
|
||||
}
|
||||
const response = await octokit.repos.getArchiveLink(params)
|
||||
if (response.status != 200) {
|
||||
throw new Error(
|
||||
`Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}`
|
||||
)
|
||||
}
|
||||
|
||||
return Buffer.from(response.data) // response.data is ArrayBuffer
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { run } from './main'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
run()
|
||||
@@ -0,0 +1,108 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as fsHelper from './fs-helper'
|
||||
import * as github from '@actions/github'
|
||||
import * as path from 'path'
|
||||
import {ISourceSettings} from './git-source-provider'
|
||||
|
||||
export function getInputs(): ISourceSettings {
|
||||
const result = ({} as unknown) as ISourceSettings
|
||||
|
||||
// GitHub workspace
|
||||
let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
|
||||
if (!githubWorkspacePath) {
|
||||
throw new Error('GITHUB_WORKSPACE not defined')
|
||||
}
|
||||
githubWorkspacePath = path.resolve(githubWorkspacePath)
|
||||
core.debug(`GITHUB_WORKSPACE = '${githubWorkspacePath}'`)
|
||||
fsHelper.directoryExistsSync(githubWorkspacePath, true)
|
||||
|
||||
// Qualified repository
|
||||
const qualifiedRepository =
|
||||
core.getInput('repository') ||
|
||||
`${github.context.repo.owner}/${github.context.repo.repo}`
|
||||
core.debug(`qualified repository = '${qualifiedRepository}'`)
|
||||
const splitRepository = qualifiedRepository.split('/')
|
||||
if (
|
||||
splitRepository.length !== 2 ||
|
||||
!splitRepository[0] ||
|
||||
!splitRepository[1]
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid repository '${qualifiedRepository}'. Expected format {owner}/{repo}.`
|
||||
)
|
||||
}
|
||||
result.repositoryOwner = splitRepository[0]
|
||||
result.repositoryName = splitRepository[1]
|
||||
|
||||
// Repository path
|
||||
result.repositoryPath = core.getInput('path') || '.'
|
||||
result.repositoryPath = path.resolve(
|
||||
githubWorkspacePath,
|
||||
result.repositoryPath
|
||||
)
|
||||
if (
|
||||
!(result.repositoryPath + path.sep).startsWith(
|
||||
githubWorkspacePath + path.sep
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`Repository path '${result.repositoryPath}' is not under '${githubWorkspacePath}'`
|
||||
)
|
||||
}
|
||||
|
||||
// Workflow repository?
|
||||
const isWorkflowRepository =
|
||||
qualifiedRepository.toUpperCase() ===
|
||||
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase()
|
||||
|
||||
// Source branch, source version
|
||||
result.ref = core.getInput('ref')
|
||||
if (!result.ref) {
|
||||
if (isWorkflowRepository) {
|
||||
result.ref = github.context.ref
|
||||
result.commit = github.context.sha
|
||||
}
|
||||
|
||||
if (!result.ref && !result.commit) {
|
||||
result.ref = 'refs/heads/master'
|
||||
}
|
||||
}
|
||||
// SHA?
|
||||
else if (result.ref.match(/^[0-9a-fA-F]{40}$/)) {
|
||||
result.commit = result.ref
|
||||
result.ref = ''
|
||||
}
|
||||
core.debug(`ref = '${result.ref}'`)
|
||||
core.debug(`commit = '${result.commit}'`)
|
||||
|
||||
// Clean
|
||||
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'
|
||||
core.debug(`clean = ${result.clean}`)
|
||||
|
||||
// Submodules
|
||||
if (core.getInput('submodules')) {
|
||||
throw new Error(
|
||||
"The input 'submodules' is not supported in actions/checkout@v2"
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch depth
|
||||
result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1'))
|
||||
if (isNaN(result.fetchDepth) || result.fetchDepth < 0) {
|
||||
result.fetchDepth = 0
|
||||
}
|
||||
core.debug(`fetch depth = ${result.fetchDepth}`)
|
||||
|
||||
// LFS
|
||||
result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'
|
||||
core.debug(`lfs = ${result.lfs}`)
|
||||
|
||||
// Auth token
|
||||
result.authToken = core.getInput('token')
|
||||
|
||||
// Persist credentials
|
||||
result.persistCredentials =
|
||||
(core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
|
||||
|
||||
return result
|
||||
}
|
||||
+40
-76
@@ -1,82 +1,46 @@
|
||||
import core from '@actions/core';
|
||||
import { exec } from '@actions/exec';
|
||||
import { URL } from 'node:url';
|
||||
import Base64 from './base64'
|
||||
import { execSync } from 'child_process';
|
||||
import * as core from '@actions/core'
|
||||
import * as coreCommand from '@actions/core/lib/command'
|
||||
import * as gitSourceProvider from './git-source-provider'
|
||||
import * as inputHelper from './input-helper'
|
||||
import * as path from 'path'
|
||||
import * as stateHelper from './state-helper'
|
||||
|
||||
type CLIOutput = {
|
||||
out: string,
|
||||
err: string
|
||||
};
|
||||
|
||||
async function cli(command: string): Promise<CLIOutput> {
|
||||
let out: CLIOutput = {
|
||||
out: '',
|
||||
err: ''
|
||||
};
|
||||
const options = { listeners: { stdout: (data: Buffer) => { out.out += data.toString(); }, stderr: (data: Buffer) => { out.err += data.toString(); }}};
|
||||
await exec('git', ['rev-parse', '--short', 'HEAD'], options);
|
||||
return out
|
||||
}
|
||||
|
||||
async function constructRepoURL(server: string, repository: string, ref: string, token: string): Promise<string> {
|
||||
core.info('Helloooo2');
|
||||
const purl = new URL(server);
|
||||
core.info('Helloooo3');
|
||||
|
||||
let consurl: string = purl.protocol;
|
||||
core.info('Helloooo4');
|
||||
/*
|
||||
if (purl.password) {
|
||||
consurl += `:${purl.password}@`;
|
||||
core.info('URL came with token already!');
|
||||
} else {
|
||||
consurl += `:${Base64.encode(token)}@`
|
||||
core.info('Sad, I had to tag on the token manually');
|
||||
}
|
||||
*/
|
||||
core.info('Helloooo5');
|
||||
consurl += purl.hostname;
|
||||
core.info('Helloooo6');
|
||||
consurl += `/${repository}`
|
||||
core.info('Helloooo7');
|
||||
|
||||
return consurl;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function for the action.
|
||||
* @returns {Promise<void>} Resolves when the action is complete.
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
core.info('Helloooo0');
|
||||
const repository: string = core.getInput('repository');
|
||||
const ref: string = core.getInput('ref');
|
||||
const token: string = core.getInput('token');
|
||||
const clean: boolean = core.getBooleanInput('clean');
|
||||
const submodules: string = core.getInput('submodules');
|
||||
const lfs: boolean = core.getBooleanInput('lfs');
|
||||
const fetchdepth: string = core.getInput('fetch-depth');
|
||||
const path: string = core.getInput('path');
|
||||
const serverurl: string = core.getInput('server-url');
|
||||
const sourceSettings = inputHelper.getInputs()
|
||||
|
||||
core.info('Helloooo1');
|
||||
const url = await constructRepoURL(serverurl, repository, ref, token);
|
||||
try {
|
||||
// Register problem matcher
|
||||
coreCommand.issueCommand(
|
||||
'add-matcher',
|
||||
{},
|
||||
path.join(__dirname, 'problem-matcher.json')
|
||||
)
|
||||
|
||||
core.info(`URL: ${url}`);
|
||||
console.log('Hest');
|
||||
console.log(`URL: ${url}`);
|
||||
|
||||
// const output = execSync(`git clone ${repository}`, { encoding: 'utf-8' }); // the default is 'buffer'
|
||||
// core.debug(`Output was: ${output}\n`);
|
||||
|
||||
// Set outputs for other workflow steps to use
|
||||
// core.setOutput('time', new Date().toTimeString())
|
||||
} catch (error) {
|
||||
// Fail the workflow run if an error occurs
|
||||
if (error instanceof Error) {
|
||||
core.setFailed(error.message);
|
||||
// Get sources
|
||||
await gitSourceProvider.getSource(sourceSettings)
|
||||
} finally {
|
||||
// Unregister problem matcher
|
||||
coreCommand.issueCommand('remove-matcher', {owner: 'checkout-git'}, '')
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanup(): Promise<void> {
|
||||
try {
|
||||
await gitSourceProvider.cleanup(stateHelper.RepositoryPath)
|
||||
} catch (error) {
|
||||
core.warning(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
if (!stateHelper.IsPost) {
|
||||
run()
|
||||
}
|
||||
// Post
|
||||
else {
|
||||
cleanup()
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
import * as yaml from 'js-yaml'
|
||||
|
||||
//
|
||||
// SUMMARY
|
||||
//
|
||||
// This script rebuilds the usage section in the README.md to be consistent with the action.yml
|
||||
|
||||
function updateUsage(
|
||||
actionReference: string,
|
||||
actionYamlPath: string = 'action.yml',
|
||||
readmePath: string = 'README.md',
|
||||
startToken: string = '<!-- start usage -->',
|
||||
endToken: string = '<!-- end usage -->'
|
||||
): void {
|
||||
if (!actionReference) {
|
||||
throw new Error('Parameter actionReference must not be empty')
|
||||
}
|
||||
|
||||
// Load the action.yml
|
||||
const actionYaml = yaml.safeLoad(fs.readFileSync(actionYamlPath).toString())
|
||||
|
||||
// Load the README
|
||||
const originalReadme = fs.readFileSync(readmePath).toString()
|
||||
|
||||
// Find the start token
|
||||
const startTokenIndex = originalReadme.indexOf(startToken)
|
||||
if (startTokenIndex < 0) {
|
||||
throw new Error(`Start token '${startToken}' not found`)
|
||||
}
|
||||
|
||||
// Find the end token
|
||||
const endTokenIndex = originalReadme.indexOf(endToken)
|
||||
if (endTokenIndex < 0) {
|
||||
throw new Error(`End token '${endToken}' not found`)
|
||||
} else if (endTokenIndex < startTokenIndex) {
|
||||
throw new Error('Start token must appear before end token')
|
||||
}
|
||||
|
||||
// Build the new README
|
||||
const newReadme: string[] = []
|
||||
|
||||
// Append the beginning
|
||||
newReadme.push(originalReadme.substr(0, startTokenIndex + startToken.length))
|
||||
|
||||
// Build the new usage section
|
||||
newReadme.push('```yaml', `- uses: ${actionReference}`, ' with:')
|
||||
const inputs = actionYaml.inputs
|
||||
let firstInput = true
|
||||
for (const key of Object.keys(inputs)) {
|
||||
const input = inputs[key]
|
||||
|
||||
// Line break between inputs
|
||||
if (!firstInput) {
|
||||
newReadme.push('')
|
||||
}
|
||||
|
||||
// Constrain the width of the description
|
||||
const width = 80
|
||||
let description = input.description as string
|
||||
while (description) {
|
||||
// Longer than width? Find a space to break apart
|
||||
let segment: string = description
|
||||
if (description.length > width) {
|
||||
segment = description.substr(0, width + 1)
|
||||
while (!segment.endsWith(' ')) {
|
||||
segment = segment.substr(0, segment.length - 1)
|
||||
}
|
||||
} else {
|
||||
segment = description
|
||||
}
|
||||
|
||||
description = description.substr(segment.length) // Remaining
|
||||
segment = segment.trimRight() // Trim the trailing space
|
||||
newReadme.push(` # ${segment}`)
|
||||
}
|
||||
|
||||
// Input and default
|
||||
if (input.default !== undefined) {
|
||||
newReadme.push(` # Default: ${input.default}`)
|
||||
}
|
||||
newReadme.push(` ${key}: ''`)
|
||||
|
||||
firstInput = false
|
||||
}
|
||||
|
||||
newReadme.push('```')
|
||||
|
||||
// Append the end
|
||||
newReadme.push(originalReadme.substr(endTokenIndex))
|
||||
|
||||
// Write the new README
|
||||
fs.writeFileSync(readmePath, newReadme.join(os.EOL))
|
||||
}
|
||||
|
||||
updateUsage(
|
||||
'actions/checkout@v2-beta',
|
||||
path.join(__dirname, '..', '..', 'action.yml'),
|
||||
path.join(__dirname, '..', '..', 'README.md')
|
||||
)
|
||||
@@ -0,0 +1,109 @@
|
||||
import {IGitCommandManager} from './git-command-manager'
|
||||
|
||||
export interface ICheckoutInfo {
|
||||
ref: string
|
||||
startPoint: string
|
||||
}
|
||||
|
||||
export async function getCheckoutInfo(
|
||||
git: IGitCommandManager,
|
||||
ref: string,
|
||||
commit: string
|
||||
): Promise<ICheckoutInfo> {
|
||||
if (!git) {
|
||||
throw new Error('Arg git cannot be empty')
|
||||
}
|
||||
|
||||
if (!ref && !commit) {
|
||||
throw new Error('Args ref and commit cannot both be empty')
|
||||
}
|
||||
|
||||
const result = ({} as unknown) as ICheckoutInfo
|
||||
const upperRef = (ref || '').toUpperCase()
|
||||
|
||||
// SHA only
|
||||
if (!ref) {
|
||||
result.ref = commit
|
||||
}
|
||||
// refs/heads/
|
||||
else if (upperRef.startsWith('REFS/HEADS/')) {
|
||||
const branch = ref.substring('refs/heads/'.length)
|
||||
result.ref = branch
|
||||
result.startPoint = `refs/remotes/origin/${branch}`
|
||||
}
|
||||
// refs/pull/
|
||||
else if (upperRef.startsWith('REFS/PULL/')) {
|
||||
const branch = ref.substring('refs/pull/'.length)
|
||||
result.ref = `refs/remotes/pull/${branch}`
|
||||
}
|
||||
// refs/tags/
|
||||
else if (upperRef.startsWith('REFS/')) {
|
||||
result.ref = ref
|
||||
}
|
||||
// Unqualified ref, check for a matching branch or tag
|
||||
else {
|
||||
if (await git.branchExists(true, `origin/${ref}`)) {
|
||||
result.ref = ref
|
||||
result.startPoint = `refs/remotes/origin/${ref}`
|
||||
} else if (await git.tagExists(`${ref}`)) {
|
||||
result.ref = `refs/tags/${ref}`
|
||||
} else {
|
||||
throw new Error(
|
||||
`A branch or tag with the name '${ref}' could not be found`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function getRefSpec(ref: string, commit: string): string[] {
|
||||
if (!ref && !commit) {
|
||||
throw new Error('Args ref and commit cannot both be empty')
|
||||
}
|
||||
|
||||
const upperRef = (ref || '').toUpperCase()
|
||||
|
||||
// SHA
|
||||
if (commit) {
|
||||
// refs/heads
|
||||
if (upperRef.startsWith('REFS/HEADS/')) {
|
||||
const branch = ref.substring('refs/heads/'.length)
|
||||
return [`+${commit}:refs/remotes/origin/${branch}`]
|
||||
}
|
||||
// refs/pull/
|
||||
else if (upperRef.startsWith('REFS/PULL/')) {
|
||||
const branch = ref.substring('refs/pull/'.length)
|
||||
return [`+${commit}:refs/remotes/pull/${branch}`]
|
||||
}
|
||||
// refs/tags/
|
||||
else if (upperRef.startsWith('REFS/TAGS/')) {
|
||||
return [`+${commit}:${ref}`]
|
||||
}
|
||||
// Otherwise no destination ref
|
||||
else {
|
||||
return [commit]
|
||||
}
|
||||
}
|
||||
// Unqualified ref, check for a matching branch or tag
|
||||
else if (!upperRef.startsWith('REFS/')) {
|
||||
return [
|
||||
`+refs/heads/${ref}*:refs/remotes/origin/${ref}*`,
|
||||
`+refs/tags/${ref}*:refs/tags/${ref}*`
|
||||
]
|
||||
}
|
||||
// refs/heads/
|
||||
else if (upperRef.startsWith('REFS/HEADS/')) {
|
||||
const branch = ref.substring('refs/heads/'.length)
|
||||
return [`+${ref}:refs/remotes/origin/${branch}`]
|
||||
}
|
||||
// refs/pull/
|
||||
else if (upperRef.startsWith('REFS/PULL/')) {
|
||||
const branch = ref.substring('refs/pull/'.length)
|
||||
return [`+${ref}:refs/remotes/pull/${branch}`]
|
||||
}
|
||||
// refs/tags/
|
||||
else {
|
||||
return [`+${ref}:${ref}`]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as core from '@actions/core'
|
||||
|
||||
const defaultMaxAttempts = 3
|
||||
const defaultMinSeconds = 10
|
||||
const defaultMaxSeconds = 20
|
||||
|
||||
export class RetryHelper {
|
||||
private maxAttempts: number
|
||||
private minSeconds: number
|
||||
private maxSeconds: number
|
||||
|
||||
constructor(
|
||||
maxAttempts: number = defaultMaxAttempts,
|
||||
minSeconds: number = defaultMinSeconds,
|
||||
maxSeconds: number = defaultMaxSeconds
|
||||
) {
|
||||
this.maxAttempts = maxAttempts
|
||||
this.minSeconds = Math.floor(minSeconds)
|
||||
this.maxSeconds = Math.floor(maxSeconds)
|
||||
if (this.minSeconds > this.maxSeconds) {
|
||||
throw new Error('min seconds should be less than or equal to max seconds')
|
||||
}
|
||||
}
|
||||
|
||||
async execute<T>(action: () => Promise<T>): Promise<T> {
|
||||
let attempt = 1
|
||||
while (attempt < this.maxAttempts) {
|
||||
// Try
|
||||
try {
|
||||
return await action()
|
||||
} catch (err) {
|
||||
core.info(err.message)
|
||||
}
|
||||
|
||||
// Sleep
|
||||
const seconds = this.getSleepAmount()
|
||||
core.info(`Waiting ${seconds} seconds before trying again`)
|
||||
await this.sleep(seconds)
|
||||
attempt++
|
||||
}
|
||||
|
||||
// Last attempt
|
||||
return await action()
|
||||
}
|
||||
|
||||
private getSleepAmount(): number {
|
||||
return (
|
||||
Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) +
|
||||
this.minSeconds
|
||||
)
|
||||
}
|
||||
|
||||
private async sleep(seconds: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, seconds * 1000))
|
||||
}
|
||||
}
|
||||
|
||||
export async function execute<T>(action: () => Promise<T>): Promise<T> {
|
||||
const retryHelper = new RetryHelper()
|
||||
return await retryHelper.execute(action)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as coreCommand from '@actions/core/lib/command'
|
||||
|
||||
/**
|
||||
* Indicates whether the POST action is running
|
||||
*/
|
||||
export const IsPost = !!process.env['STATE_isPost']
|
||||
|
||||
/**
|
||||
* The repository path for the POST action. The value is empty during the MAIN action.
|
||||
*/
|
||||
export const RepositoryPath =
|
||||
(process.env['STATE_repositoryPath'] as string) || ''
|
||||
|
||||
/**
|
||||
* Save the repository path so the POST action can retrieve the value.
|
||||
*/
|
||||
export function setRepositoryPath(repositoryPath: string) {
|
||||
coreCommand.issueCommand(
|
||||
'save-state',
|
||||
{name: 'repositoryPath'},
|
||||
repositoryPath
|
||||
)
|
||||
}
|
||||
|
||||
// Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic.
|
||||
// This is necessary since we don't have a separate entry point.
|
||||
if (!IsPost) {
|
||||
coreCommand.issueCommand('save-state', {name: 'isPost'}, 'true')
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Unit tests for the action's entrypoint, src/index.ts
|
||||
*/
|
||||
|
||||
import * as main from '../src/main'
|
||||
|
||||
// Mock the action's entrypoint
|
||||
const runMock = jest.spyOn(main, 'run').mockImplementation()
|
||||
|
||||
describe('index', () => {
|
||||
it('calls run when imported', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
require('../src/index')
|
||||
|
||||
expect(runMock).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
+15
-18
@@ -1,19 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"rootDir": "./src",
|
||||
"moduleResolution": "NodeNext",
|
||||
"baseUrl": "./",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"newLine": "lf"
|
||||
},
|
||||
"exclude": ["./dist", "./node_modules", "./tests", "./coverage"]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": ["__test__", "lib", "node_modules"]
|
||||
}
|
||||
Reference in new issue
Block a user