mirror of
				https://github.com/actions/checkout.git
				synced 2025-11-03 07:18:09 +08:00 
			
		
		
		
	Compare commits
	
		
			34 Commits
		
	
	
		
			v1.0.0
			...
			users/eric
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ba227f5d10 | ||
| 
						 | 
					db41740e12 | ||
| 
						 | 
					bc50a995b8 | ||
| 
						 | 
					dfd70d4a2d | ||
| 
						 | 
					ae525b2262 | ||
| 
						 | 
					f466b96953 | ||
| 
						 | 
					c85684db76 | ||
| 
						 | 
					299dd5064e | ||
| 
						 | 
					722adc63f1 | ||
| 
						 | 
					3537747199 | ||
| 
						 | 
					a6747255bd | ||
| 
						 | 
					c170eefc26 | ||
| 
						 | 
					a572f640b0 | ||
| 
						 | 
					cab31617d8 | ||
| 
						 | 
					5881116d18 | ||
| 
						 | 
					7990b10a0c | ||
| 
						 | 
					01a434328a | ||
| 
						 | 
					4817b449b0 | ||
| 
						 | 
					689bf84be4 | ||
| 
						 | 
					cc70598ce8 | ||
| 
						 | 
					8461dbfed3 | ||
| 
						 | 
					e347bba93b | ||
| 
						 | 
					50fbc622fc | ||
| 
						 | 
					e8bd1dffb6 | ||
| 
						 | 
					0b496e91ec | ||
| 
						 | 
					f6ce2afa70 | ||
| 
						 | 
					94d077c249 | ||
| 
						 | 
					0963d3b35f | ||
| 
						 | 
					a14471d838 | ||
| 
						 | 
					7f0669ca1f | ||
| 
						 | 
					cacfc4155d | ||
| 
						 | 
					6e6328ef28 | ||
| 
						 | 
					53bed0742e | ||
| 
						 | 
					b4b537b06a | 
							
								
								
									
										3
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					dist/
 | 
				
			||||||
 | 
					lib/
 | 
				
			||||||
 | 
					node_modules/
 | 
				
			||||||
							
								
								
									
										58
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										101
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					name: Build and Test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - master
 | 
				
			||||||
 | 
					      - releases/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 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
 | 
				
			||||||
 | 
					      - name: Checkout
 | 
				
			||||||
 | 
					        uses: actions/checkout@v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Basic checkout
 | 
				
			||||||
 | 
					      - name: Basic checkout
 | 
				
			||||||
 | 
					        uses: ./
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          ref: test-data/v2/basic
 | 
				
			||||||
 | 
					          path: basic
 | 
				
			||||||
 | 
					      - name: Verify basic
 | 
				
			||||||
 | 
					        run: __test__/verify-basic.sh --archive
 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					lib/
 | 
				
			||||||
 | 
					node_modules/
 | 
				
			||||||
							
								
								
									
										3
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					dist/
 | 
				
			||||||
 | 
					lib/
 | 
				
			||||||
 | 
					node_modules/
 | 
				
			||||||
							
								
								
									
										11
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "printWidth": 80,
 | 
				
			||||||
 | 
					  "tabWidth": 2,
 | 
				
			||||||
 | 
					  "useTabs": false,
 | 
				
			||||||
 | 
					  "semi": false,
 | 
				
			||||||
 | 
					  "singleQuote": true,
 | 
				
			||||||
 | 
					  "trailingComma": "none",
 | 
				
			||||||
 | 
					  "bracketSpacing": false,
 | 
				
			||||||
 | 
					  "arrowParens": "avoid",
 | 
				
			||||||
 | 
					  "parser": "typescript"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										228
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										228
									
								
								README.md
									
									
									
									
									
								
							@@ -1,20 +1,226 @@
 | 
				
			|||||||
# checkout
 | 
					<p align="center">
 | 
				
			||||||
 | 
					  <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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This action checks out your repository so that your workflow operates from the root of the repository
 | 
					# Checkout V2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Only a single commit is fetched by default, for the ref/SHA that triggered the workflow. Set `fetch-depth` to fetch more history. Refer [here](https://help.github.com/en/articles/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When Git 2.18 or higher is not in your PATH, falls back to the REST API to download the files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# What's new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Improved performance
 | 
				
			||||||
 | 
					  - Fetches only a single commit by default
 | 
				
			||||||
 | 
					- Script authenticated git commands
 | 
				
			||||||
 | 
					  - Auth token persisted in the local git config
 | 
				
			||||||
 | 
					- Creates a local branch
 | 
				
			||||||
 | 
					  - No longer detached HEAD when checking out a branch
 | 
				
			||||||
 | 
					- 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
 | 
				
			||||||
 | 
					  - When using a job container, the container's PATH is used
 | 
				
			||||||
 | 
					- Removed input `submodules`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Usage
 | 
					# Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See [action.yml](action.yml)
 | 
					<!-- start usage -->
 | 
				
			||||||
 | 
					 | 
				
			||||||
Basic:
 | 
					 | 
				
			||||||
```yaml
 | 
					```yaml
 | 
				
			||||||
steps:
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
- uses: actions/checkout@master
 | 
					 | 
				
			||||||
- uses: actions/setup-node@master
 | 
					 | 
				
			||||||
  with:
 | 
					  with:
 | 
				
			||||||
    version: 10.x 
 | 
					    # Repository name with owner. For example, actions/checkout
 | 
				
			||||||
- run: npm install
 | 
					    # Default: ${{ github.repository }}
 | 
				
			||||||
- run: npm test
 | 
					    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. [Learn more about creating
 | 
				
			||||||
 | 
					    # and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
 | 
				
			||||||
 | 
					    # Default: ${{ github.token }}
 | 
				
			||||||
 | 
					    token: ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # 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 -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Scenarios
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Checkout a different branch](#Checkout-a-different-branch)
 | 
				
			||||||
 | 
					- [Checkout HEAD^](#Checkout-HEAD)
 | 
				
			||||||
 | 
					- [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side)
 | 
				
			||||||
 | 
					- [Checkout multiple repos (nested)](#Checkout-multiple-repos-nested)
 | 
				
			||||||
 | 
					- [Checkout multiple repos (private)](#Checkout-multiple-repos-private)
 | 
				
			||||||
 | 
					- [Checkout pull request HEAD commit instead of merge commit](#Checkout-pull-request-HEAD-commit-instead-of-merge-commit)
 | 
				
			||||||
 | 
					- [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event)
 | 
				
			||||||
 | 
					- [Checkout submodules](#Checkout-submodules)
 | 
				
			||||||
 | 
					- [Checkout private submodules](#Checkout-private-submodules)
 | 
				
			||||||
 | 
					- [Fetch all tags](#Fetch-all-tags)
 | 
				
			||||||
 | 
					- [Fetch all branches](#Fetch-all-branches)
 | 
				
			||||||
 | 
					- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout a different branch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    ref: my-branch
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout HEAD^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    fetch-depth: 2
 | 
				
			||||||
 | 
					- run: git checkout HEAD^
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout multiple repos (side by side)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- name: Checkout
 | 
				
			||||||
 | 
					  uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    path: main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: Checkout tools repo
 | 
				
			||||||
 | 
					  uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    repository: my-org/my-tools
 | 
				
			||||||
 | 
					    path: my-tools
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout multiple repos (nested)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- name: Checkout
 | 
				
			||||||
 | 
					  uses: actions/checkout@v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: Checkout tools repo
 | 
				
			||||||
 | 
					  uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    repository: my-org/my-tools
 | 
				
			||||||
 | 
					    path: my-tools
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout multiple repos (private)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- name: Checkout
 | 
				
			||||||
 | 
					  uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    path: main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: Checkout private tools
 | 
				
			||||||
 | 
					  uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    repository: my-org/my-private-tools
 | 
				
			||||||
 | 
					    token: ${{ secrets.GitHub_PAT }} # `GitHub_PAT` is a secret that contains your PAT
 | 
				
			||||||
 | 
					    path: my-tools
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> - `${{ github.token }}` is scoped to the current repository, so if you want to checkout a different repository that is private you will need to provide your own [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout pull request HEAD commit instead of merge commit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    ref: ${{ github.event.pull_request.head.sha }}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout pull request on closed event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    branches: [master]
 | 
				
			||||||
 | 
					    types: [opened, synchronize, closed]
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v2
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout submodules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					- name: Checkout submodules
 | 
				
			||||||
 | 
					  shell: bash
 | 
				
			||||||
 | 
					  run: |
 | 
				
			||||||
 | 
					    auth_header="$(git config --local --get http.https://github.com/.extraheader)"
 | 
				
			||||||
 | 
					    git submodule sync --recursive
 | 
				
			||||||
 | 
					    git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Checkout private submodules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					  with:
 | 
				
			||||||
 | 
					    token: ${{ secrets.MY_GITHUB_PAT }}
 | 
				
			||||||
 | 
					- name: Checkout submodules
 | 
				
			||||||
 | 
					  shell: bash
 | 
				
			||||||
 | 
					  run: |
 | 
				
			||||||
 | 
					    auth_header="$(git config --local --get http.https://github.com/.extraheader)"
 | 
				
			||||||
 | 
					    git submodule sync --recursive
 | 
				
			||||||
 | 
					    git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Fetch all tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Fetch all branches
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					- run: |
 | 
				
			||||||
 | 
					    git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Fetch all history for all tags and branches
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					- uses: actions/checkout@v2
 | 
				
			||||||
 | 
					- run: |
 | 
				
			||||||
 | 
					    git fetch --prune --unshallow
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# License
 | 
					# License
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								__test__/git-version.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								__test__/git-version.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import {GitVersion} from '../lib/git-version'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('git-version tests', () => {
 | 
				
			||||||
 | 
					  it('basics', async () => {
 | 
				
			||||||
 | 
					    let version = new GitVersion('')
 | 
				
			||||||
 | 
					    expect(version.isValid()).toBeFalsy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    version = new GitVersion('asdf')
 | 
				
			||||||
 | 
					    expect(version.isValid()).toBeFalsy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    version = new GitVersion('1.2')
 | 
				
			||||||
 | 
					    expect(version.isValid()).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.toString()).toBe('1.2')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    version = new GitVersion('1.2.3')
 | 
				
			||||||
 | 
					    expect(version.isValid()).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.toString()).toBe('1.2.3')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('check minimum', async () => {
 | 
				
			||||||
 | 
					    let version = new GitVersion('4.5')
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('3.6'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('3.6.7'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.4'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.5'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.5.0'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.6'))).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.6.0'))).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    version = new GitVersion('4.5.6')
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('3.6'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('3.6.7'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.4'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.5'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.5.5'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.5.6'))).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.5.7'))).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.6'))).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('4.6.0'))).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										120
									
								
								__test__/input-helper.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								__test__/input-helper.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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/)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										10
									
								
								__test__/modify-work-tree.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										10
									
								
								__test__/modify-work-tree.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -f "./basic/basic-file.txt" ]; then
 | 
				
			||||||
 | 
					    echo "Expected basic file does not exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo hello >> ./basic/basic-file.txt
 | 
				
			||||||
 | 
					echo hello >> ./basic/new-file.txt
 | 
				
			||||||
 | 
					git -C ./basic status
 | 
				
			||||||
							
								
								
									
										168
									
								
								__test__/ref-helper.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								__test__/ref-helper.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										88
									
								
								__test__/retry-helper.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								__test__/retry-helper.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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/)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										24
									
								
								__test__/verify-basic.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								__test__/verify-basic.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -f "./basic/basic-file.txt" ]; then
 | 
				
			||||||
 | 
					    echo "Expected basic file does not exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ "$1" = "--archive" ]; then
 | 
				
			||||||
 | 
					  # Verify no .git folder
 | 
				
			||||||
 | 
					  if [ -d "./basic/.git" ]; then
 | 
				
			||||||
 | 
					    echo "Did not expect ./basic/.git folder to exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					  # Verify .git folder
 | 
				
			||||||
 | 
					  if [ ! -d "./basic/.git" ]; then
 | 
				
			||||||
 | 
					    echo "Expected ./basic/.git folder to exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Verify auth token
 | 
				
			||||||
 | 
					  cd basic
 | 
				
			||||||
 | 
					  git fetch --no-tags --depth=1 origin +refs/heads/master:refs/remotes/origin/master
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										13
									
								
								__test__/verify-clean.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										13
									
								
								__test__/verify-clean.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ "$(git -C ./basic status --porcelain)" != "" ]]; then
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    echo git status
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    git status
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    echo git diff
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    git diff
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										11
									
								
								__test__/verify-lfs.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								__test__/verify-lfs.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -f "./lfs/regular-file.txt" ]; then
 | 
				
			||||||
 | 
					    echo "Expected regular file does not exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -f "./lfs/lfs-file.bin" ]; then
 | 
				
			||||||
 | 
					    echo "Expected lfs file does not exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										17
									
								
								__test__/verify-no-unstaged-changes.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								__test__/verify-no-unstaged-changes.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ "$(git status --porcelain)" != "" ]]; then
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    echo git status
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    git status
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    echo git diff
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    git diff
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    echo Troubleshooting
 | 
				
			||||||
 | 
					    echo ----------------------------------------
 | 
				
			||||||
 | 
					    echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run all"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										11
									
								
								__test__/verify-side-by-side.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								__test__/verify-side-by-side.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -f "./side-by-side-1/side-by-side-test-file-1.txt" ]; then
 | 
				
			||||||
 | 
					    echo "Expected file 1 does not exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -f "./side-by-side-2/side-by-side-test-file-2.txt" ]; then
 | 
				
			||||||
 | 
					    echo "Expected file 2 does not exist"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										11
									
								
								__test__/verify-submodules-not-checked-out.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								__test__/verify-submodules-not-checked-out.sh
									
									
									
									
									
										Executable file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										42
									
								
								action.yml
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								action.yml
									
									
									
									
									
								
							@@ -1,22 +1,36 @@
 | 
				
			|||||||
name: 'Checkout'
 | 
					name: 'Checkout'
 | 
				
			||||||
description: 'Get sources from a GitHub repository.'
 | 
					description: 'Checkout a Git repository at a particular version'
 | 
				
			||||||
inputs: 
 | 
					inputs: 
 | 
				
			||||||
  repository:
 | 
					  repository:
 | 
				
			||||||
    description: 'Repository name'
 | 
					    description: 'Repository name with owner. For example, actions/checkout'
 | 
				
			||||||
 | 
					    default: ${{ github.repository }}
 | 
				
			||||||
  ref:
 | 
					  ref:
 | 
				
			||||||
    description: 'Ref to checkout (SHA, branch, tag)'
 | 
					    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:
 | 
					  token:
 | 
				
			||||||
    description: 'Access token for clone repository'
 | 
					    description: >
 | 
				
			||||||
  clean:
 | 
					      Auth token used to fetch the repository. The token is stored in the local
 | 
				
			||||||
    description: 'If true, execute `execute git clean -ffdx && git reset --hard HEAD` before fetching'
 | 
					      git config, which enables your scripts to run authenticated git commands.
 | 
				
			||||||
 | 
					      The post-job step removes the token from the git config. [Learn more about
 | 
				
			||||||
 | 
					      creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
 | 
				
			||||||
 | 
					    default: ${{ github.token }}
 | 
				
			||||||
 | 
					  persist-credentials:
 | 
				
			||||||
 | 
					    description: 'Whether to persist the token in the git config'
 | 
				
			||||||
    default: true
 | 
					    default: true
 | 
				
			||||||
  submodules:
 | 
					 | 
				
			||||||
    description: 'Directory containing files to upload'
 | 
					 | 
				
			||||||
  lfs:
 | 
					 | 
				
			||||||
    description: 'Whether to download Git-LFS files; defaults to false'
 | 
					 | 
				
			||||||
  fetch-depth:
 | 
					 | 
				
			||||||
    description: 'The depth of commits to ask Git to fetch; defaults to no limit'  
 | 
					 | 
				
			||||||
  path:
 | 
					  path:
 | 
				
			||||||
    description: 'Optional path to check out source code'  
 | 
					    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:
 | 
					runs:
 | 
				
			||||||
  plugin: 'checkout'
 | 
					  using: node12
 | 
				
			||||||
 | 
					  main: dist/index.js
 | 
				
			||||||
 | 
					  post: dist/index.js
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16300
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16300
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										13
									
								
								dist/problem-matcher.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								dist/problem-matcher.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "problemMatcher": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "owner": "checkout-git",
 | 
				
			||||||
 | 
					            "pattern": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "regexp": "^(fatal|error): (.*)$",
 | 
				
			||||||
 | 
					                    "message": 2
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								jest.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								jest.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7053
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7053
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										55
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "checkout",
 | 
				
			||||||
 | 
					  "version": "2.0.1",
 | 
				
			||||||
 | 
					  "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://github.com/actions/checkout.git"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "keywords": [
 | 
				
			||||||
 | 
					    "github",
 | 
				
			||||||
 | 
					    "actions",
 | 
				
			||||||
 | 
					    "checkout"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "author": "GitHub",
 | 
				
			||||||
 | 
					  "license": "MIT",
 | 
				
			||||||
 | 
					  "bugs": {
 | 
				
			||||||
 | 
					    "url": "https://github.com/actions/checkout/issues"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "homepage": "https://github.com/actions/checkout#readme",
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@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": "^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"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/fs-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/fs-helper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										377
									
								
								src/git-command-manager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										377
									
								
								src/git-command-manager.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,377 @@
 | 
				
			|||||||
 | 
					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-full-name" because the output from
 | 
				
			||||||
 | 
					    // "branch --list" is more difficult when in a detached HEAD state.
 | 
				
			||||||
 | 
					    // Note, this implementation uses "rev-parse --symbolic-full-name" because there is a bug
 | 
				
			||||||
 | 
					    // in Git 2.18 that causes "rev-parse --symbolic" to output symbolic full names.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const args = ['rev-parse', '--symbolic-full-name']
 | 
				
			||||||
 | 
					    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) {
 | 
				
			||||||
 | 
					        if (branch.startsWith('refs/heads/')) {
 | 
				
			||||||
 | 
					          branch = branch.substr('refs/heads/'.length)
 | 
				
			||||||
 | 
					        } else if (branch.startsWith('refs/remotes/')) {
 | 
				
			||||||
 | 
					          branch = branch.substr('refs/remotes/'.length)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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, "branch --show-current" would be simpler but isn't available until Git 2.22
 | 
				
			||||||
 | 
					    const output = await this.execGit(
 | 
				
			||||||
 | 
					      ['rev-parse', '--symbolic-full-name', '--verify', '--quiet', 'HEAD'],
 | 
				
			||||||
 | 
					      true
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    return !output.stdout.trim().startsWith('refs/heads/')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										303
									
								
								src/git-source-provider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								src/git-source-provider.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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`)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/git-version.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/git-version.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										92
									
								
								src/github-api-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/github-api-helper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										108
									
								
								src/input-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/input-helper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					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'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function run(): Promise<void> {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const sourceSettings = inputHelper.getInputs()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      // Register problem matcher
 | 
				
			||||||
 | 
					      coreCommand.issueCommand(
 | 
				
			||||||
 | 
					        'add-matcher',
 | 
				
			||||||
 | 
					        {},
 | 
				
			||||||
 | 
					        path.join(__dirname, 'problem-matcher.json')
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								src/misc/generate-docs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/misc/generate-docs.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					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 = segment.substr(0, segment.length - 1)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Trimmed too much?
 | 
				
			||||||
 | 
					        if (segment.length < width * 0.67) {
 | 
				
			||||||
 | 
					          segment = description
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } 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',
 | 
				
			||||||
 | 
					  path.join(__dirname, '..', '..', 'action.yml'),
 | 
				
			||||||
 | 
					  path.join(__dirname, '..', '..', 'README.md')
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										109
									
								
								src/ref-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/ref-helper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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}`]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								src/retry-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/retry-helper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								src/state-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/state-helper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "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