Public Access
1
0
mirror of https://github.com/actions/checkout.git synced 2025-07-13 12:13:51 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
c455122937 . 2020-03-19 00:43:38 -04:00
605b7eb961 . 2020-03-19 00:37:38 -04:00
036772999d . 2020-03-19 00:14:00 -04:00
46054cf00b . 2020-03-18 23:34:02 -04:00
85a425b582 use token for workflow repo 2020-03-18 23:33:26 -04:00
116 changed files with 5945 additions and 37559 deletions

View File

@ -1,6 +1,6 @@
{ {
"plugins": ["jest", "@typescript-eslint"], "plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/recommended"], "extends": ["plugin:github/es6"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 9,
@ -16,19 +16,23 @@
"@typescript-eslint/no-require-imports": "error", "@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error", "@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error", "@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"camelcase": "off", "camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"], "@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-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error", "@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error", "@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn", "@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-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error", "@typescript-eslint/no-useless-constructor": "error",
@ -36,6 +40,7 @@
"@typescript-eslint/prefer-for-of": "warn", "@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn", "@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error", "@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error", "@typescript-eslint/require-array-sort-compare": "error",

1
.gitattributes vendored
View File

@ -1 +0,0 @@
.licenses/** -diff linguist-generated=true

View File

@ -1,51 +0,0 @@
# `dist/index.js` is a special file in Actions.
# When you reference an action with `uses:` in a workflow,
# `index.js` is the code that will run.
# For our project, we generate this file through a build process
# from other source files.
# We need to make sure the checked-in `index.js` actually matches what we expect it to be.
name: Check dist
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
workflow_dispatch:
jobs:
check-dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Install dependencies
run: npm ci
- name: Rebuild the index.js file
run: npm run build
- name: Compare the expected and actual dist/ directories
run: |
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi
# If dist/ was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v2
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
path: dist/

View File

@ -1,58 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '28 9 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- run: npm ci
- run: npm run build
- run: rm -rf dist # We want code scanning to analyze lib instead (individual .js files)
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -1,14 +0,0 @@
name: Licensed
on:
push: {branches: main}
pull_request: {branches: main}
jobs:
test:
runs-on: ubuntu-latest
name: Check licenses
steps:
- uses: actions/checkout@v3
- run: npm ci
- run: npm run licensed-check

View File

@ -4,7 +4,7 @@ on:
pull_request: pull_request:
push: push:
branches: branches:
- main - master
- releases/* - releases/*
jobs: jobs:
@ -13,8 +13,8 @@ jobs:
steps: steps:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 16.x node-version: 12.x
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
- run: npm run format-check - run: npm run format-check
@ -32,7 +32,7 @@ jobs:
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
# Basic checkout # Basic checkout
- name: Checkout basic - name: Checkout basic
@ -150,7 +150,7 @@ jobs:
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
# Basic checkout using git # Basic checkout using git
- name: Checkout basic - name: Checkout basic
@ -182,7 +182,7 @@ jobs:
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
# Basic checkout using git # Basic checkout using git
- name: Checkout basic - name: Checkout basic
@ -205,41 +205,3 @@ jobs:
path: basic path: basic
- name: Verify basic - name: Verify basic
run: __test__/verify-basic.sh --archive run: __test__/verify-basic.sh --archive
test-git-container:
runs-on: ubuntu-latest
container: bitnami/git:latest
steps:
# Clone this repo
- name: Checkout
uses: actions/checkout@v3
with:
path: v3
# Basic checkout using git
- name: Checkout basic
uses: ./v3
with:
ref: test-data/v2/basic
- name: Verify basic
run: |
if [ ! -f "./basic-file.txt" ]; then
echo "Expected basic file does not exist"
exit 1
fi
# Verify .git folder
if [ ! -d "./.git" ]; then
echo "Expected ./.git folder to exist"
exit 1
fi
# Verify auth token
git config --global --add safe.directory "*"
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
# needed to make checkout post cleanup succeed
- name: Fix Checkout v3
uses: actions/checkout@v3
with:
path: v3

4
.gitignore vendored
View File

@ -1,5 +1,3 @@
__test__/_temp __test__/_temp
_temp/
lib/ lib/
node_modules/ node_modules/
.vscode/

View File

@ -1,14 +0,0 @@
sources:
npm: true
allowed:
- apache-2.0
- bsd-2-clause
- bsd-3-clause
- isc
- mit
- cc0-1.0
- unlicense
reviewed:
npm:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,54 +1,5 @@
# Changelog # Changelog
## v3.1.0
- [Use @actions/core `saveState` and `getState`](https://github.com/actions/checkout/pull/939)
- [Add `github-server-url` input](https://github.com/actions/checkout/pull/922)
## v3.0.2
- [Add input `set-safe-directory`](https://github.com/actions/checkout/pull/770)
## v3.0.1
- [Fixed an issue where checkout failed to run in container jobs due to the new git setting `safe.directory`](https://github.com/actions/checkout/pull/762)
- [Bumped various npm package versions](https://github.com/actions/checkout/pull/744)
## v3.0.0
- [Update to node 16](https://github.com/actions/checkout/pull/689)
## v2.3.1
- [Fix default branch resolution for .wiki and when using SSH](https://github.com/actions/checkout/pull/284)
## v2.3.0
- [Fallback to the default branch](https://github.com/actions/checkout/pull/278)
## v2.2.0
- [Fetch all history for all tags and branches when fetch-depth=0](https://github.com/actions/checkout/pull/258)
## v2.1.1
- Changes to support GHES ([here](https://github.com/actions/checkout/pull/236) and [here](https://github.com/actions/checkout/pull/248))
## v2.1.0
- [Group output](https://github.com/actions/checkout/pull/191)
- [Changes to support GHES alpha release](https://github.com/actions/checkout/pull/199)
- [Persist core.sshCommand for submodules](https://github.com/actions/checkout/pull/184)
- [Add support ssh](https://github.com/actions/checkout/pull/163)
- [Convert submodule SSH URL to HTTPS, when not using SSH](https://github.com/actions/checkout/pull/179)
- [Add submodule support](https://github.com/actions/checkout/pull/157)
- [Follow proxy settings](https://github.com/actions/checkout/pull/144)
- [Fix ref for pr closed event when a pr is merged](https://github.com/actions/checkout/pull/141)
- [Fix issue checking detached when git less than 2.22](https://github.com/actions/checkout/pull/128)
## v2.0.0
- [Do not pass cred on command line](https://github.com/actions/checkout/pull/108)
- [Add input persist-credentials](https://github.com/actions/checkout/pull/107)
- [Fallback to REST API to download repo](https://github.com/actions/checkout/pull/104)
## v2 (beta) ## v2 (beta)
- Improved fetch performance - Improved fetch performance

View File

@ -1 +0,0 @@
* @actions/actions-runtime

123
README.md
View File

@ -2,11 +2,11 @@
<a href="https://github.com/actions/checkout"><img alt="GitHub Actions status" src="https://github.com/actions/checkout/workflows/test-local/badge.svg"></a> <a href="https://github.com/actions/checkout"><img alt="GitHub Actions status" src="https://github.com/actions/checkout/workflows/test-local/badge.svg"></a>
</p> </p>
# Checkout V3 # Checkout V2
This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it. This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it.
Only a single commit is fetched by default, for the ref/SHA that triggered the workflow. Set `fetch-depth: 0` to fetch all history for all branches and tags. Refer [here](https://help.github.com/en/articles/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events. 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. 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.
@ -14,14 +14,27 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# What's new # What's new
- Updated to the node16 runtime by default - Improved performance
- This requires a minimum [Actions Runner](https://github.com/actions/runner/releases/tag/v2.285.0) version of v2.285.0 to run, which is by default available in GHES 3.4 or later. - 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
<!-- start usage --> <!-- start usage -->
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v2
with: with:
# Repository name with owner. For example, actions/checkout # Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }} # Default: ${{ github.repository }}
@ -29,7 +42,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# The branch, tag or SHA to checkout. When checking out the repository that # 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. # triggered a workflow, this defaults to the reference or SHA for that event.
# Otherwise, uses the default branch. # Otherwise, defaults to `master`.
ref: '' ref: ''
# Personal access token (PAT) used to fetch the repository. The PAT is configured # Personal access token (PAT) used to fetch the repository. The PAT is configured
@ -76,7 +89,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# Default: true # Default: true
clean: '' clean: ''
# Number of commits to fetch. 0 indicates all history for all branches and tags. # Number of commits to fetch. 0 indicates all history.
# Default: 1 # Default: 1
fetch-depth: '' fetch-depth: ''
@ -92,23 +105,11 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# #
# Default: false # Default: false
submodules: '' submodules: ''
# Add repository path as safe.directory for Git global config by running `git
# config --global --add safe.directory <path>`
# Default: true
set-safe-directory: ''
# The base URL for the GitHub instance that you are trying to clone from, will use
# environment defaults to fetch from the same instance that the workflow is
# running from unless specified. Example URLs are https://github.com or
# https://my-ghes-server.example.com
github-server-url: ''
``` ```
<!-- end usage --> <!-- end usage -->
# Scenarios # Scenarios
- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
- [Checkout a different branch](#Checkout-a-different-branch) - [Checkout a different branch](#Checkout-a-different-branch)
- [Checkout HEAD^](#Checkout-HEAD) - [Checkout HEAD^](#Checkout-HEAD)
- [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side) - [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side)
@ -116,20 +117,15 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
- [Checkout multiple repos (private)](#Checkout-multiple-repos-private) - [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 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 pull request on closed event](#Checkout-pull-request-on-closed-event)
- [Push a commit using the built-in token](#Push-a-commit-using-the-built-in-token) - [Checkout submodules](#Checkout-submodules)
- [Fetch all tags](#Fetch-all-tags)
## Fetch all history for all tags and branches - [Fetch all branches](#Fetch-all-branches)
- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
```yaml
- uses: actions/checkout@v3
with:
fetch-depth: 0
```
## Checkout a different branch ## Checkout a different branch
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v2
with: with:
ref: my-branch ref: my-branch
``` ```
@ -137,7 +133,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Checkout HEAD^ ## Checkout HEAD^
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v2
with: with:
fetch-depth: 2 fetch-depth: 2
- run: git checkout HEAD^ - run: git checkout HEAD^
@ -147,12 +143,12 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: main path: main
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@ -162,10 +158,10 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@ -175,15 +171,15 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
```yaml ```yaml
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: main path: main
- name: Checkout private tools - name: Checkout private tools
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
repository: my-org/my-private-tools repository: my-org/my-private-tools
token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT token: ${{ secrets.GitHub_PAT }} # `GitHub_PAT` is a secret that contains your PAT
path: my-tools path: my-tools
``` ```
@ -193,7 +189,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Checkout pull request HEAD commit instead of merge commit ## Checkout pull request HEAD commit instead of merge commit
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v2
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
``` ```
@ -203,31 +199,50 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
```yaml ```yaml
on: on:
pull_request: pull_request:
branches: [main] branches: [master]
types: [opened, synchronize, closed] types: [opened, synchronize, closed]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
``` ```
## Push a commit using the built-in token ## Checkout submodules
```yaml ```yaml
on: push - uses: actions/checkout@v2
jobs: - name: Checkout submodules
build: shell: bash
runs-on: ubuntu-latest run: |
steps: # If your submodules are configured to use SSH instead of HTTPS please uncomment the following line
- uses: actions/checkout@v3 # git config --global url."https://github.com/".insteadOf "git@github.com:"
- run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)"
date > generated.txt git submodule sync --recursive
git config user.name github-actions git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
git config user.email github-actions@github.com ```
git add .
git commit -m "generated" ## Fetch all tags
git push
```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

View File

@ -20,7 +20,6 @@ let tempHomedir: string
let git: IGitCommandManager & {env: {[key: string]: string}} let git: IGitCommandManager & {env: {[key: string]: string}}
let settings: IGitSourceSettings let settings: IGitSourceSettings
let sshPath: string let sshPath: string
let githubServerUrl: string
describe('git-auth-helper tests', () => { describe('git-auth-helper tests', () => {
beforeAll(async () => { beforeAll(async () => {
@ -68,18 +67,11 @@ describe('git-auth-helper tests', () => {
} }
}) })
async function testAuthHeader( const configureAuth_configuresAuthHeader =
testName: string, 'configureAuth configures auth header'
serverUrl: string | undefined = undefined it(configureAuth_configuresAuthHeader, async () => {
) {
// Arrange // Arrange
let expectedServerUrl = 'https://github.com' await setup(configureAuth_configuresAuthHeader)
if (serverUrl) {
githubServerUrl = serverUrl
expectedServerUrl = githubServerUrl
}
await setup(testName)
expect(settings.authToken).toBeTruthy() // sanity check expect(settings.authToken).toBeTruthy() // sanity check
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
@ -96,33 +88,9 @@ describe('git-auth-helper tests', () => {
).toString('base64') ).toString('base64')
expect( expect(
configContent.indexOf( configContent.indexOf(
`http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}` `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
) )
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
}
const configureAuth_configuresAuthHeader =
'configureAuth configures auth header'
it(configureAuth_configuresAuthHeader, async () => {
await testAuthHeader(configureAuth_configuresAuthHeader)
})
const configureAuth_AcceptsGitHubServerUrl =
'inject https://my-ghes-server.com as github server url'
it(configureAuth_AcceptsGitHubServerUrl, async () => {
await testAuthHeader(
configureAuth_AcceptsGitHubServerUrl,
'https://my-ghes-server.com'
)
})
const configureAuth_AcceptsGitHubServerUrlSetToGHEC =
'inject https://github.com as github server url'
it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
await testAuthHeader(
configureAuth_AcceptsGitHubServerUrl,
'https://github.com'
)
}) })
const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse = const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse =
@ -449,7 +417,7 @@ describe('git-auth-helper tests', () => {
`Did not expect file to exist: '${globalGitConfigPath}'` `Did not expect file to exist: '${globalGitConfigPath}'`
) )
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -550,17 +518,12 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/ /unset-all.*insteadOf/
) )
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/url.*insteadOf/)
/url.*insteadOf.*git@github.com:/
)
expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch(
/url.*insteadOf.*org-123456@github.com:/
)
} }
) )
@ -638,7 +601,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(actualKeyPath) await fs.promises.stat(actualKeyPath)
throw new Error('SSH key should have been deleted') throw new Error('SSH key should have been deleted')
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -648,7 +611,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(actualKnownHostsPath) await fs.promises.stat(actualKnownHostsPath)
throw new Error('SSH known hosts should have been deleted') throw new Error('SSH known hosts should have been deleted')
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -675,11 +638,10 @@ describe('git-auth-helper tests', () => {
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
}) })
const removeGlobalConfig_removesOverride = const removeGlobalAuth_removesOverride = 'removeGlobalAuth removes override'
'removeGlobalConfig removes override' it(removeGlobalAuth_removesOverride, async () => {
it(removeGlobalConfig_removesOverride, async () => {
// Arrange // Arrange
await setup(removeGlobalConfig_removesOverride) await setup(removeGlobalAuth_removesOverride)
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth() await authHelper.configureAuth()
await authHelper.configureGlobalAuth() await authHelper.configureGlobalAuth()
@ -688,7 +650,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig')) await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig'))
// Act // Act
await authHelper.removeGlobalConfig() await authHelper.removeGlobalAuth()
// Assert // Assert
expect(git.env['HOME']).toBeUndefined() expect(git.env['HOME']).toBeUndefined()
@ -696,7 +658,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(homeOverride) await fs.promises.stat(homeOverride)
throw new Error(`Should have been deleted '${homeOverride}'`) throw new Error(`Should have been deleted '${homeOverride}'`)
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -752,7 +714,6 @@ async function setup(testName: string): Promise<void> {
), ),
env: {}, env: {},
fetch: jest.fn(), fetch: jest.fn(),
getDefaultBranch: jest.fn(),
getWorkingDirectory: jest.fn(() => workspace), getWorkingDirectory: jest.fn(() => workspace),
init: jest.fn(), init: jest.fn(),
isDetached: jest.fn(), isDetached: jest.fn(),
@ -761,11 +722,10 @@ async function setup(testName: string): Promise<void> {
log1: jest.fn(), log1: jest.fn(),
remoteAdd: jest.fn(), remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]), removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn((name: string, value: string) => { setEnvironmentVariable: jest.fn((name: string, value: string) => {
git.env[name] = value git.env[name] = value
}), }),
shaExists: jest.fn(), setRemoteUrl: jest.fn(),
submoduleForeach: jest.fn(async () => { submoduleForeach: jest.fn(async () => {
return '' return ''
}), }),
@ -789,7 +749,7 @@ async function setup(testName: string): Promise<void> {
} }
), ),
tryDisableAutomaticGarbageCollection: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(), tryGetRemoteUrl: jest.fn(),
tryReset: jest.fn() tryReset: jest.fn()
} }
@ -798,20 +758,18 @@ async function setup(testName: string): Promise<void> {
clean: true, clean: true,
commit: '', commit: '',
fetchDepth: 1, fetchDepth: 1,
isWorkflowRepository: true,
lfs: false, lfs: false,
submodules: false, submodules: false,
nestedSubmodules: false, nestedSubmodules: false,
persistCredentials: true, persistCredentials: true,
ref: 'refs/heads/main', ref: 'refs/heads/master',
repositoryName: 'my-repo', repositoryName: 'my-repo',
repositoryOwner: 'my-org', repositoryOwner: 'my-org',
repositoryPath: '', repositoryPath: '',
sshKey: sshPath ? 'some ssh private key' : '', sshKey: sshPath ? 'some ssh private key' : '',
sshKnownHosts: '', sshKnownHosts: '',
sshStrict: true, sshStrict: true
workflowOrganizationId: 123456,
setSafeDirectory: true,
githubServerUrl: githubServerUrl
} }
} }

View File

@ -7,9 +7,9 @@ import {IGitCommandManager} from '../lib/git-command-manager'
const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper') const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper')
let repositoryPath: string let repositoryPath: string
let repositoryUrl: string let httpsUrl: string
let sshUrl: string
let clean: boolean let clean: boolean
let ref: string
let git: IGitCommandManager let git: IGitCommandManager
describe('git-directory-helper tests', () => { describe('git-directory-helper tests', () => {
@ -41,9 +41,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -64,9 +64,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -90,9 +90,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -112,9 +112,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -141,9 +141,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -161,16 +161,16 @@ describe('git-directory-helper tests', () => {
await setup(removesContentsWhenDifferentRepositoryUrl) await setup(removesContentsWhenDifferentRepositoryUrl)
clean = false clean = false
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const differentRepositoryUrl = const differentRemoteUrl =
'https://github.com/my-different-org/my-different-repo' 'https://github.com/my-different-org/my-different-repo'
// Act // Act
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
differentRepositoryUrl, differentRemoteUrl,
clean, [differentRemoteUrl],
ref clean
) )
// Assert // Assert
@ -193,9 +193,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -219,9 +219,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -244,9 +244,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
undefined, undefined,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -269,9 +269,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -300,9 +300,9 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
@ -317,66 +317,54 @@ describe('git-directory-helper tests', () => {
expect(git.tryReset).not.toHaveBeenCalled() expect(git.tryReset).not.toHaveBeenCalled()
}) })
const removesAncestorRemoteBranch = 'removes ancestor remote branch' const removesRemoteBranches = 'removes local branches'
it(removesAncestorRemoteBranch, async () => { it(removesRemoteBranches, async () => {
// Arrange // Arrange
await setup(removesAncestorRemoteBranch) await setup(removesRemoteBranches)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any> const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => { mockBranchList.mockImplementation(async (remote: boolean) => {
return remote ? ['origin/remote-branch-1', 'origin/remote-branch-2'] : [] return remote ? ['remote-branch-1', 'remote-branch-2'] : []
}) })
ref = 'remote-branch-1/conflict'
// Act // Act
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
clean, [httpsUrl, sshUrl],
ref clean
) )
// Assert // Assert
const files = await fs.promises.readdir(repositoryPath) const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file']) expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.branchDelete).toHaveBeenCalledTimes(1) expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-1')
expect(git.branchDelete).toHaveBeenCalledWith( expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-2')
true,
'origin/remote-branch-1'
)
}) })
const removesDescendantRemoteBranches = 'removes descendant remote branch' const updatesRemoteUrl = 'updates remote URL'
it(removesDescendantRemoteBranches, async () => { it(updatesRemoteUrl, async () => {
// Arrange // Arrange
await setup(removesDescendantRemoteBranches) await setup(updatesRemoteUrl)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const mockBranchList = git.branchList as jest.Mock<any, any>
mockBranchList.mockImplementation(async (remote: boolean) => {
return remote
? ['origin/remote-branch-1/conflict', 'origin/remote-branch-2']
: []
})
ref = 'remote-branch-1'
// Act // Act
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, sshUrl,
clean, [sshUrl, httpsUrl],
ref clean
) )
// Assert // Assert
const files = await fs.promises.readdir(repositoryPath) const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file']) expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.branchDelete).toHaveBeenCalledTimes(1) expect(git.isDetached).toHaveBeenCalled()
expect(git.branchDelete).toHaveBeenCalledWith( expect(git.branchList).toHaveBeenCalled()
true, expect(core.warning).not.toHaveBeenCalled()
'origin/remote-branch-1/conflict' expect(git.setRemoteUrl).toHaveBeenCalledWith(sshUrl)
)
}) })
}) })
@ -387,15 +375,13 @@ async function setup(testName: string): Promise<void> {
repositoryPath = path.join(testWorkspace, testName) repositoryPath = path.join(testWorkspace, testName)
await fs.promises.mkdir(path.join(repositoryPath, '.git'), {recursive: true}) await fs.promises.mkdir(path.join(repositoryPath, '.git'), {recursive: true})
// Repository URL // Remote URLs
repositoryUrl = 'https://github.com/my-org/my-repo' httpsUrl = 'https://github.com/my-org/my-repo'
sshUrl = 'git@github.com:my-org/my-repo'
// Clean // Clean
clean = true clean = true
// Ref
ref = ''
// Git command manager // Git command manager
git = { git = {
branchDelete: jest.fn(), branchDelete: jest.fn(),
@ -408,7 +394,6 @@ async function setup(testName: string): Promise<void> {
config: jest.fn(), config: jest.fn(),
configExists: jest.fn(), configExists: jest.fn(),
fetch: jest.fn(), fetch: jest.fn(),
getDefaultBranch: jest.fn(),
getWorkingDirectory: jest.fn(() => repositoryPath), getWorkingDirectory: jest.fn(() => repositoryPath),
init: jest.fn(), init: jest.fn(),
isDetached: jest.fn(), isDetached: jest.fn(),
@ -417,9 +402,8 @@ async function setup(testName: string): Promise<void> {
log1: jest.fn(), log1: jest.fn(),
remoteAdd: jest.fn(), remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn(), removeEnvironmentVariable: jest.fn(),
revParse: jest.fn(),
setEnvironmentVariable: jest.fn(), setEnvironmentVariable: jest.fn(),
shaExists: jest.fn(), setRemoteUrl: jest.fn(),
submoduleForeach: jest.fn(), submoduleForeach: jest.fn(),
submoduleSync: jest.fn(), submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(), submoduleUpdate: jest.fn(),
@ -429,10 +413,10 @@ async function setup(testName: string): Promise<void> {
}), }),
tryConfigUnset: jest.fn(), tryConfigUnset: jest.fn(),
tryDisableAutomaticGarbageCollection: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(async () => { tryGetRemoteUrl: jest.fn(async () => {
// Sanity check - this function shouldn't be called when the .git directory doesn't exist // Sanity check - this function shouldn't be called when the .git directory doesn't exist
await fs.promises.stat(path.join(repositoryPath, '.git')) await fs.promises.stat(path.join(repositoryPath, '.git'))
return repositoryUrl return httpsUrl
}), }),
tryReset: jest.fn(async () => { tryReset: jest.fn(async () => {
return true return true

View File

@ -1,9 +1,9 @@
import * as assert from 'assert'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as fsHelper from '../lib/fs-helper' import * as fsHelper from '../lib/fs-helper'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as inputHelper from '../lib/input-helper' import * as inputHelper from '../lib/input-helper'
import * as path from 'path' import * as path from 'path'
import * as workflowContextHelper from '../lib/workflow-context-helper'
import {IGitSourceSettings} from '../lib/git-source-settings' import {IGitSourceSettings} from '../lib/git-source-settings'
const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE'] const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE']
@ -43,11 +43,6 @@ describe('input-helper tests', () => {
.spyOn(fsHelper, 'directoryExistsSync') .spyOn(fsHelper, 'directoryExistsSync')
.mockImplementation((path: string) => path == gitHubWorkspace) .mockImplementation((path: string) => path == gitHubWorkspace)
// Mock ./workflowContextHelper getOrganizationId()
jest
.spyOn(workflowContextHelper, 'getOrganizationId')
.mockImplementation(() => Promise.resolve(123456))
// GitHub workspace // GitHub workspace
process.env['GITHUB_WORKSPACE'] = gitHubWorkspace process.env['GITHUB_WORKSPACE'] = gitHubWorkspace
}) })
@ -72,8 +67,8 @@ describe('input-helper tests', () => {
jest.restoreAllMocks() jest.restoreAllMocks()
}) })
it('sets defaults', async () => { it('sets defaults', () => {
const settings: IGitSourceSettings = await inputHelper.getInputs() const settings: IGitSourceSettings = inputHelper.getInputs()
expect(settings).toBeTruthy() expect(settings).toBeTruthy()
expect(settings.authToken).toBeFalsy() expect(settings.authToken).toBeFalsy()
expect(settings.clean).toBe(true) expect(settings.clean).toBe(true)
@ -85,14 +80,13 @@ describe('input-helper tests', () => {
expect(settings.repositoryName).toBe('some-repo') expect(settings.repositoryName).toBe('some-repo')
expect(settings.repositoryOwner).toBe('some-owner') expect(settings.repositoryOwner).toBe('some-owner')
expect(settings.repositoryPath).toBe(gitHubWorkspace) expect(settings.repositoryPath).toBe(gitHubWorkspace)
expect(settings.setSafeDirectory).toBe(true)
}) })
it('qualifies ref', async () => { it('qualifies ref', () => {
let originalRef = github.context.ref let originalRef = github.context.ref
try { try {
github.context.ref = 'some-unqualified-ref' github.context.ref = 'some-unqualified-ref'
const settings: IGitSourceSettings = await inputHelper.getInputs() const settings: IGitSourceSettings = inputHelper.getInputs()
expect(settings).toBeTruthy() expect(settings).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890') expect(settings.commit).toBe('1234567890123456789012345678901234567890')
expect(settings.ref).toBe('refs/heads/some-unqualified-ref') expect(settings.ref).toBe('refs/heads/some-unqualified-ref')
@ -101,42 +95,39 @@ describe('input-helper tests', () => {
} }
}) })
it('requires qualified repo', async () => { it('requires qualified repo', () => {
inputs.repository = 'some-unqualified-repo' inputs.repository = 'some-unqualified-repo'
try { assert.throws(() => {
await inputHelper.getInputs() inputHelper.getInputs()
throw 'should not reach here' }, /Invalid repository 'some-unqualified-repo'/)
} catch (err) {
expect(`(${(err as any).message}`).toMatch(
"Invalid repository 'some-unqualified-repo'"
)
}
}) })
it('roots path', async () => { it('roots path', () => {
inputs.path = 'some-directory/some-subdirectory' inputs.path = 'some-directory/some-subdirectory'
const settings: IGitSourceSettings = await inputHelper.getInputs() const settings: IGitSourceSettings = inputHelper.getInputs()
expect(settings.repositoryPath).toBe( expect(settings.repositoryPath).toBe(
path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory') path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory')
) )
}) })
it('sets ref to empty when explicit sha', async () => { it('sets correct default ref/sha for other repo', () => {
inputs.repository = 'some-owner/some-other-repo'
const settings: IGitSourceSettings = inputHelper.getInputs()
expect(settings.ref).toBe('refs/heads/master')
expect(settings.commit).toBeFalsy()
})
it('sets ref to empty when explicit sha', () => {
inputs.ref = '1111111111222222222233333333334444444444' inputs.ref = '1111111111222222222233333333334444444444'
const settings: IGitSourceSettings = await inputHelper.getInputs() const settings: IGitSourceSettings = inputHelper.getInputs()
expect(settings.ref).toBeFalsy() expect(settings.ref).toBeFalsy()
expect(settings.commit).toBe('1111111111222222222233333333334444444444') expect(settings.commit).toBe('1111111111222222222233333333334444444444')
}) })
it('sets sha to empty when explicit ref', async () => { it('sets sha to empty when explicit ref', () => {
inputs.ref = 'refs/heads/some-other-ref' inputs.ref = 'refs/heads/some-other-ref'
const settings: IGitSourceSettings = await inputHelper.getInputs() const settings: IGitSourceSettings = inputHelper.getInputs()
expect(settings.ref).toBe('refs/heads/some-other-ref') expect(settings.ref).toBe('refs/heads/some-other-ref')
expect(settings.commit).toBeFalsy() expect(settings.commit).toBeFalsy()
}) })
it('sets workflow organization ID', async () => {
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.workflowOrganizationId).toBe(123456)
})
}) })

View File

@ -2,5 +2,5 @@
mkdir override-git-version mkdir override-git-version
cd override-git-version cd override-git-version
echo @echo override git version 1.2.3 > git.cmd echo @echo override git version 1.2.3 > git.cmd
echo "%CD%" >> $GITHUB_PATH echo ::add-path::%CD%
cd .. cd ..

View File

@ -5,5 +5,5 @@ cd override-git-version
echo "#!/bin/sh" > git echo "#!/bin/sh" > git
echo "echo override git version 1.2.3" >> git echo "echo override git version 1.2.3" >> git
chmod +x git chmod +x git
echo "$(pwd)" >> $GITHUB_PATH echo "::add-path::$(pwd)"
cd .. cd ..

View File

@ -16,7 +16,7 @@ describe('ref-helper tests', () => {
await refHelper.getCheckoutInfo(git, 'refs/heads/my/branch', commit) await refHelper.getCheckoutInfo(git, 'refs/heads/my/branch', commit)
throw new Error('Should not reach here') throw new Error('Should not reach here')
} catch (err) { } catch (err) {
expect((err as any)?.message).toBe('Arg git cannot be empty') expect(err.message).toBe('Arg git cannot be empty')
} }
}) })
@ -25,9 +25,7 @@ describe('ref-helper tests', () => {
await refHelper.getCheckoutInfo(git, '', '') await refHelper.getCheckoutInfo(git, '', '')
throw new Error('Should not reach here') throw new Error('Should not reach here')
} catch (err) { } catch (err) {
expect((err as any)?.message).toBe( expect(err.message).toBe('Args ref and commit cannot both be empty')
'Args ref and commit cannot both be empty'
)
} }
}) })
@ -104,7 +102,7 @@ describe('ref-helper tests', () => {
await refHelper.getCheckoutInfo(git, 'my-ref', '') await refHelper.getCheckoutInfo(git, 'my-ref', '')
throw new Error('Should not reach here') throw new Error('Should not reach here')
} catch (err) { } catch (err) {
expect((err as any)?.message).toBe( expect(err.message).toBe(
"A branch or tag with the name 'my-ref' could not be found" "A branch or tag with the name 'my-ref' could not be found"
) )
} }

View File

@ -74,7 +74,7 @@ describe('retry-helper tests', () => {
throw new Error(`some error ${++attempts}`) throw new Error(`some error ${++attempts}`)
}) })
} catch (err) { } catch (err) {
error = err as Error error = err
} }
expect(error.message).toBe('some error 3') expect(error.message).toBe('some error 3')
expect(attempts).toBe(3) expect(attempts).toBe(3)

View File

@ -20,5 +20,5 @@ else
# Verify auth token # Verify auth token
cd basic cd basic
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main git fetch --no-tags --depth=1 origin +refs/heads/master:refs/remotes/origin/master
fi fi

View File

@ -12,6 +12,6 @@ if [[ "$(git status --porcelain)" != "" ]]; then
echo ---------------------------------------- echo ----------------------------------------
echo Troubleshooting echo Troubleshooting
echo ---------------------------------------- echo ----------------------------------------
echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run format && npm run build" echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run all"
exit 1 exit 1
fi fi

View File

@ -8,7 +8,7 @@ inputs:
description: > description: >
The branch, tag or SHA to checkout. When checking out the repository that 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 triggered a workflow, this defaults to the reference or SHA for that
event. Otherwise, uses the default branch. event. Otherwise, defaults to `master`.
token: token:
description: > description: >
Personal access token (PAT) used to fetch the repository. The PAT is configured Personal access token (PAT) used to fetch the repository. The PAT is configured
@ -54,7 +54,7 @@ inputs:
description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching' description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching'
default: true default: true
fetch-depth: fetch-depth:
description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.' description: 'Number of commits to fetch. 0 indicates all history.'
default: 1 default: 1
lfs: lfs:
description: 'Whether to download Git-LFS files' description: 'Whether to download Git-LFS files'
@ -68,13 +68,7 @@ inputs:
When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are
converted to HTTPS. converted to HTTPS.
default: false default: false
set-safe-directory:
description: Add repository path as safe.directory for Git global config by running `git config --global --add safe.directory <path>`
default: true
github-server-url:
description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com
required: false
runs: runs:
using: node16 using: node12
main: dist/index.js main: dist/index.js
post: dist/index.js post: dist/index.js

View File

@ -24,31 +24,19 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
description: > description: >
The branch, tag or SHA to checkout. When checking out the repository that 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 triggered a workflow, this defaults to the reference or SHA for that
event. Otherwise, uses the default branch. event. Otherwise, defaults to `master`.
token: token:
description: > description: >
Personal access token (PAT) used to fetch the repository. The PAT is configured Personal access token (PAT) used to fetch the repository. The PAT is configured
with the local git config, which enables your scripts to run authenticated git with the local git config, which enables your scripts to run authenticated git
commands. The post-job step removes the PAT. commands. The post-job step removes the PAT. [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)
We recommend using a service account with the least permissions necessary.
Also when generating a new PAT, select the least scopes necessary.
[Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
default: ${{ github.token }} default: ${{ github.token }}
ssh-key: ssh-key:
description: > description: >
SSH key used to fetch the repository. The SSH key is configured with the local SSH key used to fetch the repository. SSH key is configured with the local
git config, which enables your scripts to run authenticated git commands. git config, which enables your scripts to run authenticated git commands.
The post-job step removes the SSH key. The post-job step removes the SSH key. [Learn more about creating and using
We recommend using a service account with the least permissions necessary.
[Learn more about creating and using
encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets) encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
ssh-known-hosts: ssh-known-hosts:
description: > description: >
@ -56,10 +44,7 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
SSH keys for a host may be obtained using the utility `ssh-keyscan`. For example, SSH keys for a host may be obtained using the utility `ssh-keyscan`. For example,
`ssh-keyscan github.com`. The public key for github.com is always implicitly added. `ssh-keyscan github.com`. The public key for github.com is always implicitly added.
ssh-strict: ssh-strict:
description: > description: 'Whether to perform strict host key checking'
Whether to perform strict host key checking. When true, adds the options `StrictHostKeyChecking=yes`
and `CheckHostIP=no` to the SSH command line. Use the input `ssh-known-hosts` to
configure additional hosts.
default: true default: true
persist-credentials: persist-credentials:
description: 'Whether to configure the token or SSH key with the local git config' description: 'Whether to configure the token or SSH key with the local git config'
@ -70,7 +55,7 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching' description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching'
default: true default: true
fetch-depth: fetch-depth:
description: 'Number of commits to fetch. 0 indicates all history for all tags and branches.' description: 'Number of commits to fetch. 0 indicates all history.'
default: 1 default: 1
lfs: lfs:
description: 'Whether to download Git-LFS files' description: 'Whether to download Git-LFS files'
@ -79,11 +64,7 @@ We want to take this opportunity to make behavioral changes, from v1. This docum
description: > description: >
Whether to checkout submodules: `true` to checkout submodules or `recursive` to Whether to checkout submodules: `true` to checkout submodules or `recursive` to
recursively checkout submodules. recursively checkout submodules.
default: 'false'
When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are
converted to HTTPS.
default: false
``` ```
Note: Note:
@ -277,7 +258,7 @@ Note:
### Branching strategy and release tags ### Branching strategy and release tags
- Create a servicing branch for V1: `releases/v1` - Create a servicing branch for V1: `releases/v1`
- Merge the changes into the default branch - Merge the changes into `master`
- Release using a new tag `preview` - Release using a new tag `preview`
- When stable, release using a new tag `v2` - When stable, release using a new tag `v2`

22801
dist/index.js vendored

File diff suppressed because one or more lines are too long

18921
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,14 @@
{ {
"name": "checkout", "name": "checkout",
"version": "3.1.0", "version": "2.0.2",
"description": "checkout action", "description": "checkout action",
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
"build": "tsc && ncc build && node lib/misc/generate-docs.js", "build": "tsc && ncc build && node lib/misc/generate-docs.js",
"format": "prettier --write '**/*.ts'", "format": "prettier --write **/*.ts",
"format-check": "prettier --check '**/*.ts'", "format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts", "lint": "eslint src/**/*.ts",
"test": "jest", "test": "jest"
"licensed-check": "src/misc/licensed-check.sh",
"licensed-generate": "src/misc/licensed-generate.sh"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -28,27 +26,27 @@
}, },
"homepage": "https://github.com/actions/checkout#readme", "homepage": "https://github.com/actions/checkout#readme",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.1.3",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/github": "^2.2.0", "@actions/github": "^2.0.2",
"@actions/io": "^1.0.1", "@actions/io": "^1.0.1",
"@actions/tool-cache": "^1.1.2", "@actions/tool-cache": "^1.1.2",
"uuid": "^3.3.3" "uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.0.2", "@types/jest": "^24.0.23",
"@types/node": "^12.7.12", "@types/node": "^12.7.12",
"@types/uuid": "^3.4.6", "@types/uuid": "^3.4.6",
"@typescript-eslint/parser": "^5.1.0", "@typescript-eslint/parser": "^2.8.0",
"@zeit/ncc": "^0.20.5", "@zeit/ncc": "^0.20.5",
"eslint": "^7.32.0", "eslint": "^5.16.0",
"eslint-plugin-github": "^4.3.2", "eslint-plugin-github": "^2.0.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^22.21.0",
"jest": "^27.3.0", "jest": "^24.9.0",
"jest-circus": "^27.3.0", "jest-circus": "^24.9.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"ts-jest": "^27.0.7", "ts-jest": "^24.2.0",
"typescript": "^4.4.4" "typescript": "^3.6.4"
} }
} }

View File

@ -9,7 +9,7 @@ export function directoryExistsSync(path: string, required?: boolean): boolean {
try { try {
stats = fs.statSync(path) stats = fs.statSync(path)
} catch (error) { } catch (error) {
if ((error as any)?.code === 'ENOENT') { if (error.code === 'ENOENT') {
if (!required) { if (!required) {
return false return false
} }
@ -18,8 +18,7 @@ export function directoryExistsSync(path: string, required?: boolean): boolean {
} }
throw new Error( throw new Error(
`Encountered an error when checking whether path '${path}' exists: ${(error as any) `Encountered an error when checking whether path '${path}' exists: ${error.message}`
?.message ?? error}`
) )
} }
@ -40,13 +39,12 @@ export function existsSync(path: string): boolean {
try { try {
fs.statSync(path) fs.statSync(path)
} catch (error) { } catch (error) {
if ((error as any)?.code === 'ENOENT') { if (error.code === 'ENOENT') {
return false return false
} }
throw new Error( throw new Error(
`Encountered an error when checking whether path '${path}' exists: ${(error as any) `Encountered an error when checking whether path '${path}' exists: ${error.message}`
?.message ?? error}`
) )
} }
@ -62,13 +60,12 @@ export function fileExistsSync(path: string): boolean {
try { try {
stats = fs.statSync(path) stats = fs.statSync(path)
} catch (error) { } catch (error) {
if ((error as any)?.code === 'ENOENT') { if (error.code === 'ENOENT') {
return false return false
} }
throw new Error( throw new Error(
`Encountered an error when checking whether path '${path}' exists: ${(error as any) `Encountered an error when checking whether path '${path}' exists: ${error.message}`
?.message ?? error}`
) )
} }

View File

@ -7,21 +7,20 @@ import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import * as regexpHelper from './regexp-helper' import * as regexpHelper from './regexp-helper'
import * as stateHelper from './state-helper' import * as stateHelper from './state-helper'
import * as urlHelper from './url-helper'
import {default as uuid} from 'uuid/v4' import {default as uuid} from 'uuid/v4'
import {IGitCommandManager} from './git-command-manager' import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
const IS_WINDOWS = process.platform === 'win32' const IS_WINDOWS = process.platform === 'win32'
const HOSTNAME = 'github.com'
const SSH_COMMAND_KEY = 'core.sshCommand' const SSH_COMMAND_KEY = 'core.sshCommand'
export interface IGitAuthHelper { export interface IGitAuthHelper {
configureAuth(): Promise<void> configureAuth(): Promise<void>
configureGlobalAuth(): Promise<void> configureGlobalAuth(): Promise<void>
configureSubmoduleAuth(): Promise<void> configureSubmoduleAuth(): Promise<void>
configureTempGlobalConfig(): Promise<string>
removeAuth(): Promise<void> removeAuth(): Promise<void>
removeGlobalConfig(): Promise<void> removeGlobalAuth(): Promise<void>
} }
export function createAuthHelper( export function createAuthHelper(
@ -34,26 +33,24 @@ export function createAuthHelper(
class GitAuthHelper { class GitAuthHelper {
private readonly git: IGitCommandManager private readonly git: IGitCommandManager
private readonly settings: IGitSourceSettings private readonly settings: IGitSourceSettings
private readonly tokenConfigKey: string private readonly tokenConfigKey: string = `http.https://${HOSTNAME}/.extraheader`
private readonly tokenConfigValue: string
private readonly tokenPlaceholderConfigValue: string private readonly tokenPlaceholderConfigValue: string
private readonly insteadOfKey: string private readonly insteadOfKey: string = `url.https://${HOSTNAME}/.insteadOf`
private readonly insteadOfValues: string[] = [] private readonly insteadOfValue: string = `git@${HOSTNAME}:`
private sshCommand = '' private sshCommand = ''
private sshKeyPath = '' private sshKeyPath = ''
private sshKnownHostsPath = '' private sshKnownHostsPath = ''
private temporaryHomePath = '' private temporaryHomePath = ''
private tokenConfigValue: string
constructor( constructor(
gitCommandManager: IGitCommandManager, gitCommandManager: IGitCommandManager,
gitSourceSettings: IGitSourceSettings | undefined gitSourceSettings?: IGitSourceSettings
) { ) {
this.git = gitCommandManager this.git = gitCommandManager
this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings) this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings)
// Token auth header // Token auth header
const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl)
this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader` // "origin" is SCHEME://HOSTNAME[:PORT]
const basicCredential = Buffer.from( const basicCredential = Buffer.from(
`x-access-token:${this.settings.authToken}`, `x-access-token:${this.settings.authToken}`,
'utf8' 'utf8'
@ -61,15 +58,6 @@ class GitAuthHelper {
core.setSecret(basicCredential) core.setSecret(basicCredential)
this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***` this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`
this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}` this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`
// Instead of SSH URL
this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf` // "origin" is SCHEME://HOSTNAME[:PORT]
this.insteadOfValues.push(`git@${serverUrl.hostname}:`)
if (this.settings.workflowOrganizationId) {
this.insteadOfValues.push(
`org-${this.settings.workflowOrganizationId}@github.com:`
)
}
} }
async configureAuth(): Promise<void> { async configureAuth(): Promise<void> {
@ -81,11 +69,7 @@ class GitAuthHelper {
await this.configureToken() await this.configureToken()
} }
async configureTempGlobalConfig(): Promise<string> { async configureGlobalAuth(): Promise<void> {
// Already setup global config
if (this.temporaryHomePath?.length > 0) {
return path.join(this.temporaryHomePath, '.gitconfig')
}
// Create a temp home directory // Create a temp home directory
const runnerTemp = process.env['RUNNER_TEMP'] || '' const runnerTemp = process.env['RUNNER_TEMP'] || ''
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined') assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
@ -104,7 +88,7 @@ class GitAuthHelper {
await fs.promises.stat(gitConfigPath) await fs.promises.stat(gitConfigPath)
configExists = true configExists = true
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -115,28 +99,20 @@ class GitAuthHelper {
await fs.promises.writeFile(newGitConfigPath, '') await fs.promises.writeFile(newGitConfigPath, '')
} }
// Override HOME
core.info(
`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`
)
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath)
return newGitConfigPath
}
async configureGlobalAuth(): Promise<void> {
// 'configureTempGlobalConfig' noops if already set, just returns the path
const newGitConfigPath = await this.configureTempGlobalConfig()
try { try {
// Override HOME
core.info(
`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`
)
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath)
// Configure the token // Configure the token
await this.configureToken(newGitConfigPath, true) await this.configureToken(newGitConfigPath, true)
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
await this.git.tryConfigUnset(this.insteadOfKey, true) await this.git.tryConfigUnset(this.insteadOfKey, true)
if (!this.settings.sshKey) { if (!this.settings.sshKey) {
for (const insteadOfValue of this.insteadOfValues) { await this.git.config(this.insteadOfKey, this.insteadOfValue, true)
await this.git.config(this.insteadOfKey, insteadOfValue, true, true)
}
} }
} catch (err) { } catch (err) {
// Unset in case somehow written to the real global config // Unset in case somehow written to the real global config
@ -166,7 +142,7 @@ class GitAuthHelper {
output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [] output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []
for (const configPath of configPaths) { for (const configPath of configPaths) {
core.debug(`Replacing token placeholder in '${configPath}'`) core.debug(`Replacing token placeholder in '${configPath}'`)
await this.replaceTokenPlaceholder(configPath) this.replaceTokenPlaceholder(configPath)
} }
if (this.settings.sshKey) { if (this.settings.sshKey) {
@ -177,12 +153,10 @@ class GitAuthHelper {
) )
} else { } else {
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
for (const insteadOfValue of this.insteadOfValues) { await this.git.submoduleForeach(
await this.git.submoduleForeach( `git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`,
`git config --local --add '${this.insteadOfKey}' '${insteadOfValue}'`, this.settings.nestedSubmodules
this.settings.nestedSubmodules )
)
}
} }
} }
} }
@ -192,12 +166,10 @@ class GitAuthHelper {
await this.removeToken() await this.removeToken()
} }
async removeGlobalConfig(): Promise<void> { async removeGlobalAuth(): Promise<void> {
if (this.temporaryHomePath?.length > 0) { core.debug(`Unsetting HOME override`)
core.debug(`Unsetting HOME override`) this.git.removeEnvironmentVariable('HOME')
this.git.removeEnvironmentVariable('HOME') await io.rmRF(this.temporaryHomePath)
await io.rmRF(this.temporaryHomePath)
}
} }
private async configureSsh(): Promise<void> { private async configureSsh(): Promise<void> {
@ -235,7 +207,7 @@ class GitAuthHelper {
await fs.promises.readFile(userKnownHostsPath) await fs.promises.readFile(userKnownHostsPath)
).toString() ).toString()
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
throw err throw err
} }
} }
@ -324,7 +296,7 @@ class GitAuthHelper {
try { try {
await io.rmRF(keyPath) await io.rmRF(keyPath)
} catch (err) { } catch (err) {
core.debug(`${(err as any)?.message ?? err}`) core.debug(err.message)
core.warning(`Failed to remove SSH key '${keyPath}'`) core.warning(`Failed to remove SSH key '${keyPath}'`)
} }
} }

View File

@ -3,7 +3,6 @@ import * as exec from '@actions/exec'
import * as fshelper from './fs-helper' import * as fshelper from './fs-helper'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as path from 'path' import * as path from 'path'
import * as refHelper from './ref-helper'
import * as regexpHelper from './regexp-helper' import * as regexpHelper from './regexp-helper'
import * as retryHelper from './retry-helper' import * as retryHelper from './retry-helper'
import {GitVersion} from './git-version' import {GitVersion} from './git-version'
@ -21,23 +20,20 @@ export interface IGitCommandManager {
config( config(
configKey: string, configKey: string,
configValue: string, configValue: string,
globalConfig?: boolean, globalConfig?: boolean
add?: boolean
): Promise<void> ): Promise<void>
configExists(configKey: string, globalConfig?: boolean): Promise<boolean> configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
fetch(refSpec: string[], fetchDepth?: number): Promise<void> fetch(fetchDepth: number, refSpec: string[]): Promise<void>
getDefaultBranch(repositoryUrl: string): Promise<string>
getWorkingDirectory(): string getWorkingDirectory(): string
init(): Promise<void> init(): Promise<void>
isDetached(): Promise<boolean> isDetached(): Promise<boolean>
lfsFetch(ref: string): Promise<void> lfsFetch(ref: string): Promise<void>
lfsInstall(): Promise<void> lfsInstall(): Promise<void>
log1(format?: string): Promise<string> log1(): Promise<void>
remoteAdd(remoteName: string, remoteUrl: string): Promise<void> remoteAdd(remoteName: string, remoteUrl: string): Promise<void>
removeEnvironmentVariable(name: string): void removeEnvironmentVariable(name: string): void
revParse(ref: string): Promise<string>
setEnvironmentVariable(name: string, value: string): void setEnvironmentVariable(name: string, value: string): void
shaExists(sha: string): Promise<boolean> setRemoteUrl(url: string): Promise<void>
submoduleForeach(command: string, recursive: boolean): Promise<string> submoduleForeach(command: string, recursive: boolean): Promise<string>
submoduleSync(recursive: boolean): Promise<void> submoduleSync(recursive: boolean): Promise<void>
submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void>
@ -45,7 +41,7 @@ export interface IGitCommandManager {
tryClean(): Promise<boolean> tryClean(): Promise<boolean>
tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean> tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean>
tryDisableAutomaticGarbageCollection(): Promise<boolean> tryDisableAutomaticGarbageCollection(): Promise<boolean>
tryGetFetchUrl(): Promise<string> tryGetRemoteUrl(): Promise<string>
tryReset(): Promise<boolean> tryReset(): Promise<boolean>
} }
@ -141,15 +137,14 @@ class GitCommandManager {
async config( async config(
configKey: string, configKey: string,
configValue: string, configValue: string,
globalConfig?: boolean, globalConfig?: boolean
add?: boolean
): Promise<void> { ): Promise<void> {
const args: string[] = ['config', globalConfig ? '--global' : '--local'] await this.execGit([
if (add) { 'config',
args.push('--add') globalConfig ? '--global' : '--local',
} configKey,
args.push(...[configKey, configValue]) configValue
await this.execGit(args) ])
} }
async configExists( async configExists(
@ -170,14 +165,17 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async fetch(refSpec: string[], fetchDepth?: number): Promise<void> { async fetch(fetchDepth: number, refSpec: string[]): Promise<void> {
const args = ['-c', 'protocol.version=2', 'fetch'] const args = [
if (!refSpec.some(x => x === refHelper.tagsRefSpec)) { '-c',
args.push('--no-tags') 'protocol.version=2',
} 'fetch',
'--no-tags',
args.push('--prune', '--progress', '--no-recurse-submodules') '--prune',
if (fetchDepth && fetchDepth > 0) { '--progress',
'--no-recurse-submodules'
]
if (fetchDepth > 0) {
args.push(`--depth=${fetchDepth}`) args.push(`--depth=${fetchDepth}`)
} else if ( } else if (
fshelper.fileExistsSync( fshelper.fileExistsSync(
@ -198,34 +196,6 @@ class GitCommandManager {
}) })
} }
async getDefaultBranch(repositoryUrl: string): Promise<string> {
let output: GitOutput | undefined
await retryHelper.execute(async () => {
output = await this.execGit([
'ls-remote',
'--quiet',
'--exit-code',
'--symref',
repositoryUrl,
'HEAD'
])
})
if (output) {
// Satisfy compiler, will always be set
for (let line of output.stdout.trim().split('\n')) {
line = line.trim()
if (line.startsWith('ref:') || line.endsWith('HEAD')) {
return line
.substr('ref:'.length, line.length - 'ref:'.length - 'HEAD'.length)
.trim()
}
}
}
throw new Error('Unexpected output when retrieving default branch')
}
getWorkingDirectory(): string { getWorkingDirectory(): string {
return this.workingDirectory return this.workingDirectory
} }
@ -256,11 +226,8 @@ class GitCommandManager {
await this.execGit(['lfs', 'install', '--local']) await this.execGit(['lfs', 'install', '--local'])
} }
async log1(format?: string): Promise<string> { async log1(): Promise<void> {
var args = format ? ['log', '-1', format] : ['log', '-1'] await this.execGit(['log', '-1'])
var silent = format ? false : true
const output = await this.execGit(args, false, silent)
return output.stdout
} }
async remoteAdd(remoteName: string, remoteUrl: string): Promise<void> { async remoteAdd(remoteName: string, remoteUrl: string): Promise<void> {
@ -271,25 +238,12 @@ class GitCommandManager {
delete this.gitEnv[name] delete this.gitEnv[name]
} }
/**
* Resolves a ref to a SHA. For a branch or lightweight tag, the commit SHA is returned.
* For an annotated tag, the tag SHA is returned.
* @param {string} ref For example: 'refs/heads/main' or '/refs/tags/v1'
* @returns {Promise<string>}
*/
async revParse(ref: string): Promise<string> {
const output = await this.execGit(['rev-parse', ref])
return output.stdout.trim()
}
setEnvironmentVariable(name: string, value: string): void { setEnvironmentVariable(name: string, value: string): void {
this.gitEnv[name] = value this.gitEnv[name] = value
} }
async shaExists(sha: string): Promise<boolean> { async setRemoteUrl(value: string): Promise<void> {
const args = ['rev-parse', '--verify', '--quiet', `${sha}^{object}`] await this.config('git.remote.url', value)
const output = await this.execGit(args, true)
return output.exitCode === 0
} }
async submoduleForeach(command: string, recursive: boolean): Promise<string> { async submoduleForeach(command: string, recursive: boolean): Promise<string> {
@ -360,7 +314,7 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async tryGetFetchUrl(): Promise<string> { async tryGetRemoteUrl(): Promise<string> {
const output = await this.execGit( const output = await this.execGit(
['config', '--local', '--get', 'remote.origin.url'], ['config', '--local', '--get', 'remote.origin.url'],
true true
@ -394,8 +348,7 @@ class GitCommandManager {
private async execGit( private async execGit(
args: string[], args: string[],
allowAllExitCodes = false, allowAllExitCodes = false
silent = false
): Promise<GitOutput> { ): Promise<GitOutput> {
fshelper.directoryExistsSync(this.workingDirectory, true) fshelper.directoryExistsSync(this.workingDirectory, true)
@ -414,7 +367,6 @@ class GitCommandManager {
const options = { const options = {
cwd: this.workingDirectory, cwd: this.workingDirectory,
env, env,
silent,
ignoreReturnCode: allowAllExitCodes, ignoreReturnCode: allowAllExitCodes,
listeners: { listeners: {
stdout: (data: Buffer) => { stdout: (data: Buffer) => {

View File

@ -5,20 +5,29 @@ import * as fsHelper from './fs-helper'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as path from 'path' import * as path from 'path'
import {IGitCommandManager} from './git-command-manager' import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings'
export async function prepareExistingDirectory( export async function prepareExistingDirectory(
git: IGitCommandManager | undefined, git: IGitCommandManager | undefined,
repositoryPath: string, repositoryPath: string,
repositoryUrl: string, preferredRemoteUrl: string,
clean: boolean, allowedRemoteUrls: string[],
ref: string clean: boolean
): Promise<void> { ): Promise<void> {
assert.ok(repositoryPath, 'Expected repositoryPath to be defined') assert.ok(repositoryPath, 'Expected repositoryPath to be defined')
assert.ok(repositoryUrl, 'Expected repositoryUrl to be defined') assert.ok(preferredRemoteUrl, 'Expected preferredRemoteUrl to be defined')
assert.ok(allowedRemoteUrls, 'Expected allowedRemoteUrls to be defined')
assert.ok(
allowedRemoteUrls.length,
'Expected allowedRemoteUrls to have at least one value'
)
// Indicates whether to delete the directory contents // Indicates whether to delete the directory contents
let remove = false let remove = false
// The remote URL
let remoteUrl: string
// Check whether using git or REST API // Check whether using git or REST API
if (!git) { if (!git) {
remove = true remove = true
@ -26,7 +35,7 @@ export async function prepareExistingDirectory(
// Fetch URL does not match // Fetch URL does not match
else if ( else if (
!fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) || !fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) ||
repositoryUrl !== (await git.tryGetFetchUrl()) allowedRemoteUrls.indexOf((remoteUrl = await git.tryGetRemoteUrl())) < 0
) { ) {
remove = true remove = true
} else { } else {
@ -39,9 +48,7 @@ export async function prepareExistingDirectory(
try { try {
await io.rmRF(lockPath) await io.rmRF(lockPath)
} catch (error) { } catch (error) {
core.debug( core.debug(`Unable to delete '${lockPath}'. ${error.message}`)
`Unable to delete '${lockPath}'. ${(error as any)?.message ?? error}`
)
} }
} }
@ -58,26 +65,10 @@ export async function prepareExistingDirectory(
await git.branchDelete(false, branch) await git.branchDelete(false, branch)
} }
// Remove any conflicting refs/remotes/origin/* // Remove all refs/remotes/origin/* to avoid conflicts
// Example 1: Consider ref is refs/heads/foo and previously fetched refs/remotes/origin/foo/bar branches = await git.branchList(true)
// Example 2: Consider ref is refs/heads/foo/bar and previously fetched refs/remotes/origin/foo for (const branch of branches) {
if (ref) { await git.branchDelete(true, branch)
ref = ref.startsWith('refs/') ? ref : `refs/heads/${ref}`
if (ref.startsWith('refs/heads/')) {
const upperName1 = ref.toUpperCase().substr('REFS/HEADS/'.length)
const upperName1Slash = `${upperName1}/`
branches = await git.branchList(true)
for (const branch of branches) {
const upperName2 = branch.substr('origin/'.length).toUpperCase()
const upperName2Slash = `${upperName2}/`
if (
upperName1.startsWith(upperName2Slash) ||
upperName2.startsWith(upperName1Slash)
) {
await git.branchDelete(true, branch)
}
}
}
} }
core.endGroup() core.endGroup()
@ -100,6 +91,13 @@ export async function prepareExistingDirectory(
) )
} }
} }
// Update to the preferred remote URL
if (remoteUrl !== preferredRemoteUrl) {
core.startGroup('Updating the remote URL')
await git.setRemoteUrl(preferredRemoteUrl)
core.endGroup()
}
} catch (error) { } catch (error) {
core.warning( core.warning(
`Unable to prepare the existing repository. The repository will be recreated instead.` `Unable to prepare the existing repository. The repository will be recreated instead.`

Some files were not shown because too many files have changed in this diff Show More