0
1
Fork 0
mirror of https://github.com/actions/checkout synced 2024-06-25 15:48:19 +02:00

Merge remote-tracking branch 'origin/main' into users/cory-miller/git-user-and-email

This commit is contained in:
Cory Miller 2024-04-25 20:06:13 +00:00
commit 5dac6c7132
107 changed files with 39738 additions and 64059 deletions

20
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,20 @@
---
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
minor-npm-dependencies:
# NPM: Only group minor and patch updates (we want to carefully review major updates)
update-types: [minor, patch]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
minor-actions-dependencies:
# GitHub Actions: Only group minor and patch updates (we want to carefully review major updates)
update-types: [minor, patch]

View file

@ -24,10 +24,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set Node.js 16.x - name: Set Node.js 20.x
uses: actions/setup-node@v1 uses: actions/setup-node@v4
with: with:
node-version: 16.x node-version: 20.x
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@ -44,7 +44,7 @@ jobs:
fi fi
# If dist/ was different than expected, upload the expected version as an artifact # If dist/ was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v4
if: ${{ failure() && steps.diff.conclusion == 'failure' }} if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with: with:
name: dist name: dist

View file

@ -42,7 +42,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -55,4 +55,4 @@ jobs:
- run: rm -rf dist # We want code scanning to analyze lib instead (individual .js files) - run: rm -rf dist # We want code scanning to analyze lib instead (individual .js files)
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v3

View file

@ -7,13 +7,18 @@ on:
- main - main
- releases/* - releases/*
# Note that when you see patterns like "ref: test-data/v2/basic" within this workflow,
# these refer to "test-data" branches on this actions/checkout repo.
# (For example, test-data/v2/basic -> https://github.com/actions/checkout/tree/test-data/v2/basic)
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v4
with: with:
node-version: 16.x node-version: 20.x
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
@ -32,7 +37,7 @@ jobs:
steps: steps:
# Clone this repo # Clone this repo
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4.1.1
# Basic checkout # Basic checkout
- name: Checkout basic - name: Checkout basic
@ -72,6 +77,53 @@ jobs:
shell: bash shell: bash
run: __test__/verify-side-by-side.sh run: __test__/verify-side-by-side.sh
# Filter
- name: Fetch filter
uses: ./
with:
filter: 'blob:none'
path: fetch-filter
- name: Verify fetch filter
run: __test__/verify-fetch-filter.sh
# Sparse checkout
- name: Sparse checkout
uses: ./
with:
sparse-checkout: |
__test__
.github
dist
path: sparse-checkout
- name: Verify sparse checkout
run: __test__/verify-sparse-checkout.sh
# Disabled sparse checkout in existing checkout
- name: Disabled sparse checkout
uses: ./
with:
path: sparse-checkout
- name: Verify disabled sparse checkout
shell: bash
run: set -x && ls -l sparse-checkout/src/git-command-manager.ts
# Sparse checkout (non-cone mode)
- name: Sparse checkout (non-cone mode)
uses: ./
with:
sparse-checkout: |
/__test__/
/.github/
/dist/
sparse-checkout-cone-mode: false
path: sparse-checkout-non-cone-mode
- name: Verify sparse checkout (non-cone mode)
run: __test__/verify-sparse-checkout-non-cone-mode.sh
# LFS # LFS
- name: Checkout LFS - name: Checkout LFS
uses: ./ uses: ./
@ -138,7 +190,7 @@ jobs:
test-proxy: test-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: alpine/git:latest image: ghcr.io/actions/test-ubuntu-git:main.20240221.114913.703z
options: --dns 127.0.0.1 options: --dns 127.0.0.1
services: services:
squid-proxy: squid-proxy:

View file

@ -1,5 +1,5 @@
name: Update Main Version name: Update Main Version
run-name: Move ${{ github.event.inputs.main_version }} to ${{ github.event.inputs.target }} run-name: Move ${{ github.event.inputs.major_version }} to ${{ github.event.inputs.target }}
on: on:
workflow_dispatch: workflow_dispatch:
@ -7,17 +7,22 @@ on:
target: target:
description: The tag or reference to use description: The tag or reference to use
required: true required: true
main_version: major_version:
type: choice type: choice
description: The main version to update description: The major version to update
options: options:
- v4
- v3 - v3
- v2
jobs: jobs:
tag: tag:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 # Note this update workflow can also be used as a rollback tool.
# For that reason, it's best to pin `actions/checkout` to a known, stable version
# (typically, about two releases back).
- uses: actions/checkout@v4.1.1
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Git config - name: Git config
@ -25,6 +30,6 @@ jobs:
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Tag new target - name: Tag new target
run: git tag -f ${{ github.event.inputs.main_version }} ${{ github.event.inputs.target }} run: git tag -f ${{ github.event.inputs.major_version }} ${{ github.event.inputs.target }}
- name: Push new tag - name: Push new tag
run: git push origin ${{ github.event.inputs.main_version }} --force run: git push origin ${{ github.event.inputs.major_version }} --force

View file

@ -0,0 +1,59 @@
name: Publish test-ubuntu-git Container
on:
# Use an on demand workflow trigger.
# (Forked copies of actions/checkout won't have permission to update GHCR.io/actions,
# so avoid trigger events that run automatically.)
workflow_dispatch:
inputs:
publish:
description: 'Publish to ghcr.io? (main branch only)'
type: boolean
required: true
default: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: actions/test-ubuntu-git
jobs:
build-and-push-image:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Use `docker/login-action` to log in to GHCR.io.
# Once published, the packages are scoped to the account defined here.
- name: Log in to the ghcr.io container registry
uses: docker/login-action@v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Format Timestamp
id: timestamp
# Use `date` with a custom format to achieve the key=value format GITHUB_OUTPUT expects.
run: date -u "+now=%Y%m%d.%H%M%S.%3NZ" >> "$GITHUB_OUTPUT"
- name: Issue Image Publish Warning
if: ${{ inputs.publish && github.ref_name != 'main' }}
run: echo "::warning::test-ubuntu-git images can only be published from the actions/checkout 'main' branch. Workflow will continue with push/publish disabled."
# Use `docker/build-push-action` to build (and optionally publish) the image.
- name: Build Docker Image (with optional Push)
uses: docker/build-push-action@v5.3.0
with:
context: .
file: images/test-ubuntu-git.Dockerfile
# For now, attempts to push to ghcr.io must target the `main` branch.
# In the future, consider also allowing attempts from `releases/*` branches.
push: ${{ inputs.publish && github.ref_name == 'main' }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}.${{ steps.timestamp.outputs.now }}

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.

BIN
.licenses/npm/qs.dep.yml generated

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,5 +1,66 @@
# Changelog # Changelog
## v4.1.4
- Disable `extensions.worktreeConfig` when disabling `sparse-checkout` by @jww3 in https://github.com/actions/checkout/pull/1692
- Add dependabot config by @cory-miller in https://github.com/actions/checkout/pull/1688
- Bump the minor-actions-dependencies group with 2 updates by @dependabot in https://github.com/actions/checkout/pull/1693
- Bump word-wrap from 1.2.3 to 1.2.5 by @dependabot in https://github.com/actions/checkout/pull/1643
## v4.1.3
- Check git version before attempting to disable `sparse-checkout` by @jww3 in https://github.com/actions/checkout/pull/1656
- Add SSH user parameter by @cory-miller in https://github.com/actions/checkout/pull/1685
- Update `actions/checkout` version in `update-main-version.yml` by @jww3 in https://github.com/actions/checkout/pull/1650
## v4.1.2
- Fix: Disable sparse checkout whenever `sparse-checkout` option is not present @dscho in https://github.com/actions/checkout/pull/1598
## v4.1.1
- Correct link to GitHub Docs by @peterbe in https://github.com/actions/checkout/pull/1511
- Link to release page from what's new section by @cory-miller in https://github.com/actions/checkout/pull/1514
## v4.1.0
- [Add support for partial checkout filters](https://github.com/actions/checkout/pull/1396)
## v4.0.0
- [Support fetching without the --progress option](https://github.com/actions/checkout/pull/1067)
- [Update to node20](https://github.com/actions/checkout/pull/1436)
## v3.6.0
- [Fix: Mark test scripts with Bash'isms to be run via Bash](https://github.com/actions/checkout/pull/1377)
- [Add option to fetch tags even if fetch-depth > 0](https://github.com/actions/checkout/pull/579)
## v3.5.3
- [Fix: Checkout fail in self-hosted runners when faulty submodule are checked-in](https://github.com/actions/checkout/pull/1196)
- [Fix typos found by codespell](https://github.com/actions/checkout/pull/1287)
- [Add support for sparse checkouts](https://github.com/actions/checkout/pull/1369)
## v3.5.2
- [Fix api endpoint for GHES](https://github.com/actions/checkout/pull/1289)
## v3.5.1
- [Fix slow checkout on Windows](https://github.com/actions/checkout/pull/1246)
## v3.5.0
* [Add new public key for known_hosts](https://github.com/actions/checkout/pull/1237)
## v3.4.0
- [Upgrade codeql actions to v2](https://github.com/actions/checkout/pull/1209)
- [Upgrade dependencies](https://github.com/actions/checkout/pull/1210)
- [Upgrade @actions/io](https://github.com/actions/checkout/pull/1225)
## v3.3.0
- [Implement branch list using callbacks from exec function](https://github.com/actions/checkout/pull/1045)
- [Add in explicit reference to private checkout options](https://github.com/actions/checkout/pull/1050)
- [Fix comment typos (that got added in #770)](https://github.com/actions/checkout/pull/1057)
## v3.2.0
- [Add GitHub Action to perform release](https://github.com/actions/checkout/pull/942)
- [Fix status badge](https://github.com/actions/checkout/pull/967)
- [Replace datadog/squid with ubuntu/squid Docker image](https://github.com/actions/checkout/pull/1002)
- [Wrap pipeline commands for submoduleForeach in quotes](https://github.com/actions/checkout/pull/964)
- [Update @actions/io to 1.1.2](https://github.com/actions/checkout/pull/1029)
- [Upgrading version to 3.2.0](https://github.com/actions/checkout/pull/1039)
## v3.1.0 ## v3.1.0
- [Use @actions/core `saveState` and `getState`](https://github.com/actions/checkout/pull/939) - [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) - [Add `github-server-url` input](https://github.com/actions/checkout/pull/922)

View file

@ -1 +1 @@
* @actions/actions-runtime * @actions/actions-launch

View file

@ -1,10 +1,10 @@
[![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml) [![Build and Test](https://github.com/actions/checkout/actions/workflows/test.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/test.yml)
# Checkout V3 # Checkout V4
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: 0` to fetch all history for all branches and tags. Refer [here](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows) to learn which commit `$GITHUB_SHA` points to for different events.
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out. 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.
@ -12,14 +12,13 @@ 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 Please refer to the [release page](https://github.com/actions/checkout/releases/latest) for the latest release notes.
- This requires a minimum [Actions Runner](https://github.com/actions/runner/releases/tag/v2.285.0) version of v2.285.0 to run, which is by default available in GHES 3.4 or later.
# Usage # Usage
<!-- start usage --> <!-- start usage -->
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
# Repository name with owner. For example, actions/checkout # Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }} # Default: ${{ github.repository }}
@ -63,6 +62,11 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# Default: true # Default: true
ssh-strict: '' ssh-strict: ''
# The user to use when connecting to the remote SSH host. By default 'git' is
# used.
# Default: git
ssh-user: ''
# Whether to configure the token or SSH key with the local git config # Whether to configure the token or SSH key with the local git config
# Default: true # Default: true
persist-credentials: '' persist-credentials: ''
@ -74,10 +78,31 @@ 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: ''
# Partially clone against a given filter. Overrides sparse-checkout if set.
# Default: null
filter: ''
# Do a sparse checkout on given patterns. Each pattern should be separated with
# new lines.
# Default: null
sparse-checkout: ''
# Specifies whether to use cone-mode when doing a sparse checkout.
# Default: true
sparse-checkout-cone-mode: ''
# Number of commits to fetch. 0 indicates all history for all branches and tags. # Number of commits to fetch. 0 indicates all history for all branches and tags.
# Default: 1 # Default: 1
fetch-depth: '' fetch-depth: ''
# Whether to fetch tags, even if fetch-depth > 0.
# Default: false
fetch-tags: ''
# Whether to show progress status output when fetching.
# Default: true
show-progress: ''
# Whether to download Git-LFS files # Whether to download Git-LFS files
# Default: false # Default: false
lfs: '' lfs: ''
@ -106,6 +131,9 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
# Scenarios # Scenarios
- [Fetch only the root files](#Fetch-only-the-root-files)
- [Fetch only the root files and `.github` and `src` folder](#Fetch-only-the-root-files-and-github-and-src-folder)
- [Fetch only a single file](#Fetch-only-a-single-file)
- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches) - [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)
@ -116,10 +144,38 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
- [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) - [Push a commit using the built-in token](#Push-a-commit-using-the-built-in-token)
## Fetch only the root files
```yaml
- uses: actions/checkout@v4
with:
sparse-checkout: .
```
## Fetch only the root files and `.github` and `src` folder
```yaml
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github
src
```
## Fetch only a single file
```yaml
- uses: actions/checkout@v4
with:
sparse-checkout: |
README.md
sparse-checkout-cone-mode: false
```
## Fetch all history for all tags and branches ## Fetch all history for all tags and branches
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
``` ```
@ -127,7 +183,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
## Checkout a different branch ## Checkout a different branch
```yaml ```yaml
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
ref: my-branch ref: my-branch
``` ```
@ -135,7 +191,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@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- run: git checkout HEAD^ - run: git checkout HEAD^
@ -145,12 +201,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@v4
with: with:
path: main path: main
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@ -161,10 +217,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@v4
- name: Checkout tools repo - name: Checkout tools repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: my-org/my-tools repository: my-org/my-tools
path: my-tools path: my-tools
@ -175,12 +231,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@v4
with: with:
path: main path: main
- name: Checkout private tools - name: Checkout private tools
uses: actions/checkout@v3 uses: actions/checkout@v4
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.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
@ -193,7 +249,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@v4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
``` ```
@ -209,7 +265,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
``` ```
## Push a commit using the built-in token ## Push a commit using the built-in token
@ -220,7 +276,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: | - run: |
date > generated.txt date > generated.txt
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"

View file

@ -169,8 +169,9 @@ describe('git-auth-helper tests', () => {
// Mock fs.promises.readFile // Mock fs.promises.readFile
const realReadFile = fs.promises.readFile const realReadFile = fs.promises.readFile
jest.spyOn(fs.promises, 'readFile').mockImplementation( jest
async (file: any, options: any): Promise<Buffer> => { .spyOn(fs.promises, 'readFile')
.mockImplementation(async (file: any, options: any): Promise<Buffer> => {
const userKnownHostsPath = path.join( const userKnownHostsPath = path.join(
os.homedir(), os.homedir(),
'.ssh', '.ssh',
@ -181,8 +182,7 @@ describe('git-auth-helper tests', () => {
} }
return await realReadFile(file, options) return await realReadFile(file, options)
} })
)
// Act // Act
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
@ -727,6 +727,9 @@ async function setup(testName: string): Promise<void> {
branchDelete: jest.fn(), branchDelete: jest.fn(),
branchExists: jest.fn(), branchExists: jest.fn(),
branchList: jest.fn(), branchList: jest.fn(),
disableSparseCheckout: jest.fn(),
sparseCheckout: jest.fn(),
sparseCheckoutNonConeMode: jest.fn(),
checkout: jest.fn(), checkout: jest.fn(),
checkoutDetach: jest.fn(), checkoutDetach: jest.fn(),
config: jest.fn( config: jest.fn(
@ -770,6 +773,9 @@ async function setup(testName: string): Promise<void> {
return '' return ''
}), }),
submoduleSync: jest.fn(), submoduleSync: jest.fn(),
submoduleStatus: jest.fn(async () => {
return true
}),
submoduleUpdate: jest.fn(), submoduleUpdate: jest.fn(),
tagExists: jest.fn(), tagExists: jest.fn(),
tryClean: jest.fn(), tryClean: jest.fn(),
@ -790,14 +796,20 @@ async function setup(testName: string): Promise<void> {
), ),
tryDisableAutomaticGarbageCollection: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(), tryGetFetchUrl: jest.fn(),
tryReset: jest.fn() tryReset: jest.fn(),
version: jest.fn()
} }
settings = { settings = {
authToken: 'some auth token', authToken: 'some auth token',
clean: true, clean: true,
commit: '', commit: '',
filter: undefined,
sparseCheckout: [],
sparseCheckoutConeMode: true,
fetchDepth: 1, fetchDepth: 1,
fetchTags: false,
showProgress: true,
lfs: false, lfs: false,
submodules: false, submodules: false,
nestedSubmodules: false, nestedSubmodules: false,
@ -809,6 +821,7 @@ async function setup(testName: string): Promise<void> {
sshKey: sshPath ? 'some ssh private key' : '', sshKey: sshPath ? 'some ssh private key' : '',
sshKnownHosts: '', sshKnownHosts: '',
sshStrict: true, sshStrict: true,
sshUser: '',
workflowOrganizationId: 123456, workflowOrganizationId: 123456,
setSafeDirectory: true, setSafeDirectory: true,
githubServerUrl: githubServerUrl githubServerUrl: githubServerUrl

View file

@ -39,7 +39,12 @@ describe('git-auth-helper tests', () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec) jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test' const workingDirectory = 'test'
const lfs = false const lfs = false
git = await commandManager.createCommandManager(workingDirectory, lfs) const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
let branches = await git.branchList(false) let branches = await git.branchList(false)
@ -70,7 +75,12 @@ describe('git-auth-helper tests', () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec) jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test' const workingDirectory = 'test'
const lfs = false const lfs = false
git = await commandManager.createCommandManager(workingDirectory, lfs) const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
let branches = await git.branchList(false) let branches = await git.branchList(false)
@ -78,3 +88,291 @@ describe('git-auth-helper tests', () => {
expect(branches.sort()).toEqual(['foo'].sort()) expect(branches.sort()).toEqual(['foo'].sort())
}) })
}) })
describe('Test fetchDepth and fetchTags options', () => {
beforeEach(async () => {
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
mockExec.mockImplementation((path, args, options) => {
console.log(args, options.listeners.stdout)
if (args.includes('version')) {
options.listeners.stdout(Buffer.from('2.18'))
}
return 0
})
})
afterEach(() => {
jest.restoreAllMocks()
})
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 0,
fetchTags: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is false', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 0,
fetchTags: false
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is false', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 1,
fetchTags: false
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'--depth=1',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 1,
fetchTags: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--prune',
'--no-recurse-submodules',
'--filter=filterValue',
'--depth=1',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
showProgress: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchDepth is 42 and showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchDepth: 42,
showProgress: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--no-tags',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'--depth=42',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
it('should call execGit with the correct arguments when fetchTags is true and showProgress is true', async () => {
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
const workingDirectory = 'test'
const lfs = false
const doSparseCheckout = false
git = await commandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
const refSpec = ['refspec1', 'refspec2']
const options = {
filter: 'filterValue',
fetchTags: true,
showProgress: true
}
await git.fetch(refSpec, options)
expect(mockExec).toHaveBeenCalledWith(
expect.any(String),
[
'-c',
'protocol.version=2',
'fetch',
'--prune',
'--no-recurse-submodules',
'--progress',
'--filter=filterValue',
'origin',
'refspec1',
'refspec2'
],
expect.any(Object)
)
})
})

View file

@ -281,6 +281,65 @@ describe('git-directory-helper tests', () => {
expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-2') expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-2')
}) })
const cleanWhenSubmoduleStatusIsFalse =
'cleans when submodule status is false'
it(cleanWhenSubmoduleStatusIsFalse, async () => {
// Arrange
await setup(cleanWhenSubmoduleStatusIsFalse)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
//mock bad submodule
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return false
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files).toHaveLength(0)
expect(git.tryClean).toHaveBeenCalled()
})
const doesNotCleanWhenSubmoduleStatusIsTrue =
'does not clean when submodule status is true'
it(doesNotCleanWhenSubmoduleStatusIsTrue, async () => {
// Arrange
await setup(doesNotCleanWhenSubmoduleStatusIsTrue)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const submoduleStatus = git.submoduleStatus as jest.Mock<any, any>
submoduleStatus.mockImplementation(async (remote: boolean) => {
return true
})
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
repositoryUrl,
clean,
ref
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.tryClean).toHaveBeenCalled()
})
const removesLockFiles = 'removes lock files' const removesLockFiles = 'removes lock files'
it(removesLockFiles, async () => { it(removesLockFiles, async () => {
// Arrange // Arrange
@ -403,6 +462,9 @@ async function setup(testName: string): Promise<void> {
branchList: jest.fn(async () => { branchList: jest.fn(async () => {
return [] return []
}), }),
disableSparseCheckout: jest.fn(),
sparseCheckout: jest.fn(),
sparseCheckoutNonConeMode: jest.fn(),
checkout: jest.fn(), checkout: jest.fn(),
checkoutDetach: jest.fn(), checkoutDetach: jest.fn(),
config: jest.fn(), config: jest.fn(),
@ -423,6 +485,9 @@ async function setup(testName: string): Promise<void> {
submoduleForeach: jest.fn(), submoduleForeach: jest.fn(),
submoduleSync: jest.fn(), submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(), submoduleUpdate: jest.fn(),
submoduleStatus: jest.fn(async () => {
return true
}),
tagExists: jest.fn(), tagExists: jest.fn(),
tryClean: jest.fn(async () => { tryClean: jest.fn(async () => {
return true return true
@ -436,6 +501,7 @@ async function setup(testName: string): Promise<void> {
}), }),
tryReset: jest.fn(async () => { tryReset: jest.fn(async () => {
return true return true
}) }),
version: jest.fn()
} }
} }

View file

@ -1,4 +1,5 @@
import {GitVersion} from '../lib/git-version' import {GitVersion} from '../src/git-version'
import {MinimumGitSparseCheckoutVersion} from '../src/git-command-manager'
describe('git-version tests', () => { describe('git-version tests', () => {
it('basics', async () => { it('basics', async () => {
@ -42,4 +43,44 @@ describe('git-version tests', () => {
expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy() expect(version.checkMinimum(new GitVersion('5.1'))).toBeFalsy()
expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy() expect(version.checkMinimum(new GitVersion('5.1.2'))).toBeFalsy()
}) })
it('sparse checkout', async () => {
const minSparseVer = MinimumGitSparseCheckoutVersion
expect(new GitVersion('1.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('1.99').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.24').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.24.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.24.9').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25.1').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.25.9').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26.1').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.26.9').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27.0').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27.1').checkMinimum(minSparseVer)).toBeFalsy()
expect(new GitVersion('2.27.9').checkMinimum(minSparseVer)).toBeFalsy()
// /---------------------------------------
// ^^^ before / after vvv
// --------------------------/
expect(new GitVersion('2.28').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.28.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.28.1').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.28.9').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29.1').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.29.9').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('2.99').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('3.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('3.99').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('4.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('4.99').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('5.0').checkMinimum(minSparseVer)).toBeTruthy()
expect(new GitVersion('5.99').checkMinimum(minSparseVer)).toBeTruthy()
})
}) })

View file

@ -79,7 +79,12 @@ describe('input-helper tests', () => {
expect(settings.clean).toBe(true) expect(settings.clean).toBe(true)
expect(settings.commit).toBeTruthy() expect(settings.commit).toBeTruthy()
expect(settings.commit).toBe('1234567890123456789012345678901234567890') expect(settings.commit).toBe('1234567890123456789012345678901234567890')
expect(settings.filter).toBe(undefined)
expect(settings.sparseCheckout).toBe(undefined)
expect(settings.sparseCheckoutConeMode).toBe(true)
expect(settings.fetchDepth).toBe(1) expect(settings.fetchDepth).toBe(1)
expect(settings.fetchTags).toBe(false)
expect(settings.showProgress).toBe(true)
expect(settings.lfs).toBe(false) expect(settings.lfs).toBe(false)
expect(settings.ref).toBe('refs/heads/some-ref') expect(settings.ref).toBe('refs/heads/some-ref')
expect(settings.repositoryName).toBe('some-repo') expect(settings.repositoryName).toBe('some-repo')

View file

@ -7,11 +7,11 @@ let git: IGitCommandManager
describe('ref-helper tests', () => { describe('ref-helper tests', () => {
beforeEach(() => { beforeEach(() => {
git = ({} as unknown) as IGitCommandManager git = {} as unknown as IGitCommandManager
}) })
it('getCheckoutInfo requires git', async () => { it('getCheckoutInfo requires git', async () => {
const git = (null as unknown) as IGitCommandManager const git = null as unknown as IGitCommandManager
try { try {
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')

View file

@ -68,7 +68,7 @@ describe('retry-helper tests', () => {
it('all attempts fail succeeds', async () => { it('all attempts fail succeeds', async () => {
let attempts = 0 let attempts = 0
let error: Error = (null as unknown) as Error let error: Error = null as unknown as Error
try { try {
await retryHelper.execute(() => { await retryHelper.execute(() => {
throw new Error(`some error ${++attempts}`) throw new Error(`some error ${++attempts}`)

View file

@ -18,6 +18,20 @@ else
exit 1 exit 1
fi fi
# Verify that sparse-checkout is disabled.
SPARSE_CHECKOUT_ENABLED=$(git -C ./basic config --local --get-all core.sparseCheckout)
if [ "$SPARSE_CHECKOUT_ENABLED" != "" ]; then
echo "Expected sparse-checkout to be disabled (discovered: $SPARSE_CHECKOUT_ENABLED)"
exit 1
fi
# Verify git configuration shows worktreeConfig is effectively disabled
WORKTREE_CONFIG_ENABLED=$(git -C ./basic config --local --get-all extensions.worktreeConfig)
if [[ "$WORKTREE_CONFIG_ENABLED" != "" ]]; then
echo "Expected extensions.worktreeConfig (boolean) to be disabled in git config. This could be an artifact of sparse checkout functionality."
exit 1
fi
# 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/main:refs/remotes/origin/main

16
__test__/verify-fetch-filter.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
# Verify .git folder
if [ ! -d "./fetch-filter/.git" ]; then
echo "Expected ./fetch-filter/.git folder to exist"
exit 1
fi
# Verify .git/config contains partialclonefilter
CLONE_FILTER=$(git -C fetch-filter config --local --get remote.origin.partialclonefilter)
if [ "$CLONE_FILTER" != "blob:none" ]; then
echo "Expected ./fetch-filter/.git/config to have 'remote.origin.partialclonefilter' set to 'blob:none'"
exit 1
fi

View file

@ -0,0 +1,51 @@
#!/bin/bash
# Verify .git folder
if [ ! -d "./sparse-checkout-non-cone-mode/.git" ]; then
echo "Expected ./sparse-checkout-non-cone-mode/.git folder to exist"
exit 1
fi
# Verify sparse-checkout (non-cone-mode)
cd sparse-checkout-non-cone-mode
ENABLED=$(git config --local --get-all core.sparseCheckout)
if [ "$?" != "0" ]; then
echo "Failed to verify that sparse-checkout is enabled"
exit 1
fi
# Check that sparse-checkout is enabled
if [ "$ENABLED" != "true" ]; then
echo "Expected sparse-checkout to be enabled (is: $ENABLED)"
exit 1
fi
SPARSE_CHECKOUT_FILE=$(git rev-parse --git-path info/sparse-checkout)
if [ "$?" != "0" ]; then
echo "Failed to validate sparse-checkout"
exit 1
fi
# Check that sparse-checkout list is not empty
if [ ! -f "$SPARSE_CHECKOUT_FILE" ]; then
echo "Expected sparse-checkout file to exist"
exit 1
fi
# Check that all folders from sparse-checkout exists
for pattern in $(cat "$SPARSE_CHECKOUT_FILE")
do
if [ ! -d "${pattern#/}" ]; then
echo "Expected directory '${pattern#/}' to exist"
exit 1
fi
done
# Verify that the root directory is not checked out
if [ -f README.md ]; then
echo "Expected top-level files not to exist"
exit 1
fi

View file

@ -0,0 +1,63 @@
#!/bin/bash
# Verify .git folder
if [ ! -d "./sparse-checkout/.git" ]; then
echo "Expected ./sparse-checkout/.git folder to exist"
exit 1
fi
# Verify sparse-checkout
cd sparse-checkout
SPARSE=$(git sparse-checkout list)
if [ "$?" != "0" ]; then
echo "Failed to validate sparse-checkout"
exit 1
fi
# Check that sparse-checkout list is not empty
if [ -z "$SPARSE" ]; then
echo "Expected sparse-checkout list to not be empty"
exit 1
fi
# Check that all folders of the sparse checkout exist
for pattern in $SPARSE
do
if [ ! -d "$pattern" ]; then
echo "Expected directory '$pattern' to exist"
exit 1
fi
done
checkSparse () {
if [ ! -d "./$1" ]; then
echo "Expected directory '$1' to exist"
exit 1
fi
for file in $(git ls-tree -r --name-only HEAD $1)
do
if [ ! -f "$file" ]; then
echo "Expected file '$file' to exist"
exit 1
fi
done
}
# Check that all folders and their children have been checked out
checkSparse __test__
checkSparse .github
checkSparse dist
# Check that only sparse-checkout folders have been checked out
for pattern in $(git ls-tree --name-only HEAD)
do
if [ -d "$pattern" ]; then
if [[ "$pattern" != "__test__" && "$pattern" != ".github" && "$pattern" != "dist" ]]; then
echo "Expected directory '$pattern' to not exist"
exit 1
fi
fi
done

View file

@ -45,6 +45,10 @@ inputs:
and `CheckHostIP=no` to the SSH command line. Use the input `ssh-known-hosts` to and `CheckHostIP=no` to the SSH command line. Use the input `ssh-known-hosts` to
configure additional hosts. configure additional hosts.
default: true default: true
ssh-user:
description: >
The user to use when connecting to the remote SSH host. By default 'git' is used.
default: git
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'
default: true default: true
@ -53,9 +57,29 @@ inputs:
clean: clean:
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
filter:
description: >
Partially clone against a given filter.
Overrides sparse-checkout if set.
default: null
sparse-checkout:
description: >
Do a sparse checkout on given patterns.
Each pattern should be separated with new lines.
default: null
sparse-checkout-cone-mode:
description: >
Specifies whether to use cone-mode when doing a sparse checkout.
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 for all branches and tags.'
default: 1 default: 1
fetch-tags:
description: 'Whether to fetch tags, even if fetch-depth > 0.'
default: false
show-progress:
description: 'Whether to show progress status output when fetching.'
default: true
lfs: lfs:
description: 'Whether to download Git-LFS files' description: 'Whether to download Git-LFS files'
default: false default: false
@ -75,6 +99,6 @@ inputs:
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 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 required: false
runs: runs:
using: node16 using: node20
main: dist/index.js main: dist/index.js
post: dist/index.js post: dist/index.js

View file

@ -181,7 +181,7 @@ GITHUB_WORKSPACE=/home/runner/work/foo/foo
RUNNER_WORKSPACE=/home/runner/work/foo RUNNER_WORKSPACE=/home/runner/work/foo
``` ```
V2 introduces a new contraint on the checkout path. The location must now be under `github.workspace`. Whereas the checkout@v1 constraint was one level up, under `runner.workspace`. V2 introduces a new constraint on the checkout path. The location must now be under `github.workspace`. Whereas the checkout@v1 constraint was one level up, under `runner.workspace`.
V2 no longer changes `github.workspace` to follow wherever the self repo is checked-out. V2 no longer changes `github.workspace` to follow wherever the self repo is checked-out.

70147
dist/index.js vendored

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,12 @@
# Defines the test-ubuntu-git Container Image.
# Consumed by actions/checkout CI/CD validation workflows.
FROM ubuntu:latest
RUN apt update
RUN apt install -y git
LABEL org.opencontainers.image.title="Ubuntu + git (validation image)"
LABEL org.opencontainers.image.description="Ubuntu image with git pre-installed. Intended primarily for testing `actions/checkout` during CI/CD workflows."
LABEL org.opencontainers.image.documentation="https://github.com/actions/checkout/tree/main/images/test-ubuntu-git.md"
LABEL org.opencontainers.image.licenses=MIT

15
images/test-ubuntu-git.md Normal file
View file

@ -0,0 +1,15 @@
# `test-ubuntu-git` Container Image
[![Publish test-ubuntu-git Container](https://github.com/actions/checkout/actions/workflows/update-test-ubuntu-git.yml/badge.svg)](https://github.com/actions/checkout/actions/workflows/update-test-ubuntu-git.yml)
## Purpose
`test-ubuntu-git` is a container image hosted on the GitHub Container Registry, `ghcr.io`.
It is intended primarily for testing the [`actions/checkout` repository](https://github.com/actions/checkout) as part of `actions/checkout`'s CI/CD workflows.
The composition of `test-ubuntu-git` is intentionally minimal. It is comprised of [git](https://git-scm.com/) installed on top of a [base-level ubuntu image](https://hub.docker.com/_/ubuntu/tags).
# License
`test-ubuntu-git` is released under the [MIT License](/LICENSE).

View file

@ -1,5 +1,6 @@
module.exports = { module.exports = {
clearMocks: true, clearMocks: true,
fakeTimers: {},
moduleFileExtensions: ['js', 'ts'], moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node', testEnvironment: 'node',
testMatch: ['**/*.test.ts'], testMatch: ['**/*.test.ts'],

32338
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "checkout", "name": "checkout",
"version": "3.2.0", "version": "4.1.4",
"description": "checkout action", "description": "checkout action",
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
@ -28,28 +28,28 @@
}, },
"homepage": "https://github.com/actions/checkout#readme", "homepage": "https://github.com/actions/checkout#readme",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.1",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.1.1",
"@actions/github": "^2.2.0", "@actions/github": "^6.0.0",
"@actions/io": "^1.1.2", "@actions/io": "^1.1.3",
"@actions/tool-cache": "^1.1.2", "@actions/tool-cache": "^2.0.1",
"uuid": "^3.3.3" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.0.2", "@types/jest": "^29.5.12",
"@types/node": "^12.7.12", "@types/node": "^20.12.7",
"@types/uuid": "^3.4.6", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^7.7.1",
"@zeit/ncc": "^0.20.5", "@vercel/ncc": "^0.38.1",
"eslint": "^7.32.0", "eslint": "^8.57.0",
"eslint-plugin-github": "^4.3.2", "eslint-plugin-github": "^4.10.2",
"eslint-plugin-jest": "^25.7.0", "eslint-plugin-jest": "^28.2.0",
"jest": "^27.3.0", "jest": "^29.7.0",
"jest-circus": "^27.3.0", "jest-circus": "^29.7.0",
"js-yaml": "^3.13.1", "js-yaml": "^4.1.0",
"prettier": "^1.19.1", "prettier": "^3.2.5",
"ts-jest": "^27.0.7", "ts-jest": "^29.1.2",
"typescript": "^4.4.4" "typescript": "^5.4.5"
} }
} }

View file

@ -18,8 +18,9 @@ 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: ${
?.message ?? error}` (error as any)?.message ?? error
}`
) )
} }
@ -45,8 +46,9 @@ export function existsSync(path: string): 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: ${
?.message ?? error}` (error as any)?.message ?? error
}`
) )
} }
@ -67,8 +69,9 @@ export function fileExistsSync(path: string): 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: ${
?.message ?? error}` (error as any)?.message ?? error
}`
) )
} }

View file

@ -8,7 +8,7 @@ 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 * as urlHelper from './url-helper'
import {default as uuid} from 'uuid/v4' import {v4 as uuid} from 'uuid'
import {IGitCommandManager} from './git-command-manager' import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
@ -49,7 +49,7 @@ class GitAuthHelper {
gitSourceSettings: IGitSourceSettings | undefined gitSourceSettings: IGitSourceSettings | undefined
) { ) {
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) const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl)
@ -247,7 +247,7 @@ class GitAuthHelper {
if (this.settings.sshKnownHosts) { if (this.settings.sshKnownHosts) {
knownHosts += `# Begin from input known hosts\n${this.settings.sshKnownHosts}\n# end from input known hosts\n` knownHosts += `# Begin from input known hosts\n${this.settings.sshKnownHosts}\n# end from input known hosts\n`
} }
knownHosts += `# Begin implicitly added github.com\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n# End implicitly added github.com\n` knownHosts += `# Begin implicitly added github.com\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\n# End implicitly added github.com\n`
this.sshKnownHostsPath = path.join(runnerTemp, `${uniqueId}_known_hosts`) this.sshKnownHostsPath = path.join(runnerTemp, `${uniqueId}_known_hosts`)
stateHelper.setSshKnownHostsPath(this.sshKnownHostsPath) stateHelper.setSshKnownHostsPath(this.sshKnownHostsPath)
await fs.promises.writeFile(this.sshKnownHostsPath, knownHosts) await fs.promises.writeFile(this.sshKnownHostsPath, knownHosts)

View file

@ -1,5 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as fs from 'fs'
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'
@ -10,12 +11,17 @@ import {GitVersion} from './git-version'
// Auth header not supported before 2.9 // Auth header not supported before 2.9
// Wire protocol v2 not supported before 2.18 // Wire protocol v2 not supported before 2.18
// sparse-checkout not [well-]supported before 2.28 (see https://github.com/actions/checkout/issues/1386)
export const MinimumGitVersion = new GitVersion('2.18') export const MinimumGitVersion = new GitVersion('2.18')
export const MinimumGitSparseCheckoutVersion = new GitVersion('2.28')
export interface IGitCommandManager { export interface IGitCommandManager {
branchDelete(remote: boolean, branch: string): Promise<void> branchDelete(remote: boolean, branch: string): Promise<void>
branchExists(remote: boolean, pattern: string): Promise<boolean> branchExists(remote: boolean, pattern: string): Promise<boolean>
branchList(remote: boolean): Promise<string[]> branchList(remote: boolean): Promise<string[]>
disableSparseCheckout(): Promise<void>
sparseCheckout(sparseCheckout: string[]): Promise<void>
sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise<void>
checkout(ref: string, startPoint: string): Promise<void> checkout(ref: string, startPoint: string): Promise<void>
checkoutDetach(): Promise<void> checkoutDetach(): Promise<void>
config( config(
@ -25,7 +31,15 @@ export interface IGitCommandManager {
add?: 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(
refSpec: string[],
options: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
}
): Promise<void>
getDefaultBranch(repositoryUrl: string): Promise<string> getDefaultBranch(repositoryUrl: string): Promise<string>
getWorkingDirectory(): string getWorkingDirectory(): string
init(): Promise<void> init(): Promise<void>
@ -41,19 +55,26 @@ export interface IGitCommandManager {
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>
submoduleStatus(): Promise<boolean>
tagExists(pattern: string): Promise<boolean> tagExists(pattern: string): Promise<boolean>
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> tryGetFetchUrl(): Promise<string>
tryReset(): Promise<boolean> tryReset(): Promise<boolean>
version(): Promise<GitVersion>
} }
export async function createCommandManager( export async function createCommandManager(
workingDirectory: string, workingDirectory: string,
lfs: boolean lfs: boolean,
doSparseCheckout: boolean
): Promise<IGitCommandManager> { ): Promise<IGitCommandManager> {
return await GitCommandManager.createCommandManager(workingDirectory, lfs) return await GitCommandManager.createCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
} }
class GitCommandManager { class GitCommandManager {
@ -63,7 +84,9 @@ class GitCommandManager {
} }
private gitPath = '' private gitPath = ''
private lfs = false private lfs = false
private doSparseCheckout = false
private workingDirectory = '' private workingDirectory = ''
private gitVersion: GitVersion = new GitVersion()
// Private constructor; use createCommandManager() // Private constructor; use createCommandManager()
private constructor() {} private constructor() {}
@ -153,6 +176,33 @@ class GitCommandManager {
return result return result
} }
async disableSparseCheckout(): Promise<void> {
await this.execGit(['sparse-checkout', 'disable'])
// Disabling 'sparse-checkout` leaves behind an undesirable side-effect in config (even in a pristine environment).
await this.tryConfigUnset('extensions.worktreeConfig', false)
}
async sparseCheckout(sparseCheckout: string[]): Promise<void> {
await this.execGit(['sparse-checkout', 'set', ...sparseCheckout])
}
async sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise<void> {
await this.execGit(['config', 'core.sparseCheckout', 'true'])
const output = await this.execGit([
'rev-parse',
'--git-path',
'info/sparse-checkout'
])
const sparseCheckoutPath = path.join(
this.workingDirectory,
output.stdout.trimRight()
)
await fs.promises.appendFile(
sparseCheckoutPath,
`\n${sparseCheckout.join('\n')}\n`
)
}
async checkout(ref: string, startPoint: string): Promise<void> { async checkout(ref: string, startPoint: string): Promise<void> {
const args = ['checkout', '--progress', '--force'] const args = ['checkout', '--progress', '--force']
if (startPoint) { if (startPoint) {
@ -201,15 +251,31 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async fetch(refSpec: string[], fetchDepth?: number): Promise<void> { async fetch(
refSpec: string[],
options: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
}
): Promise<void> {
const args = ['-c', 'protocol.version=2', 'fetch'] const args = ['-c', 'protocol.version=2', 'fetch']
if (!refSpec.some(x => x === refHelper.tagsRefSpec)) { if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
args.push('--no-tags') args.push('--no-tags')
} }
args.push('--prune', '--progress', '--no-recurse-submodules') args.push('--prune', '--no-recurse-submodules')
if (fetchDepth && fetchDepth > 0) { if (options.showProgress) {
args.push(`--depth=${fetchDepth}`) args.push('--progress')
}
if (options.filter) {
args.push(`--filter=${options.filter}`)
}
if (options.fetchDepth && options.fetchDepth > 0) {
args.push(`--depth=${options.fetchDepth}`)
} else if ( } else if (
fshelper.fileExistsSync( fshelper.fileExistsSync(
path.join(this.workingDirectory, '.git', 'shallow') path.join(this.workingDirectory, '.git', 'shallow')
@ -288,8 +354,8 @@ class GitCommandManager {
} }
async log1(format?: string): Promise<string> { async log1(format?: string): Promise<string> {
var args = format ? ['log', '-1', format] : ['log', '-1'] const args = format ? ['log', '-1', format] : ['log', '-1']
var silent = format ? false : true const silent = format ? false : true
const output = await this.execGit(args, false, silent) const output = await this.execGit(args, false, silent)
return output.stdout return output.stdout
} }
@ -357,6 +423,12 @@ class GitCommandManager {
await this.execGit(args) await this.execGit(args)
} }
async submoduleStatus(): Promise<boolean> {
const output = await this.execGit(['submodule', 'status'], true)
core.debug(output.stdout)
return output.exitCode === 0
}
async tagExists(pattern: string): Promise<boolean> { async tagExists(pattern: string): Promise<boolean> {
const output = await this.execGit(['tag', '--list', pattern]) const output = await this.execGit(['tag', '--list', pattern])
return !!output.stdout.trim() return !!output.stdout.trim()
@ -414,12 +486,21 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async version(): Promise<GitVersion> {
return this.gitVersion
}
static async createCommandManager( static async createCommandManager(
workingDirectory: string, workingDirectory: string,
lfs: boolean lfs: boolean,
doSparseCheckout: boolean
): Promise<GitCommandManager> { ): Promise<GitCommandManager> {
const result = new GitCommandManager() const result = new GitCommandManager()
await result.initializeCommandManager(workingDirectory, lfs) await result.initializeCommandManager(
workingDirectory,
lfs,
doSparseCheckout
)
return result return result
} }
@ -469,7 +550,8 @@ class GitCommandManager {
private async initializeCommandManager( private async initializeCommandManager(
workingDirectory: string, workingDirectory: string,
lfs: boolean lfs: boolean,
doSparseCheckout: boolean
): Promise<void> { ): Promise<void> {
this.workingDirectory = workingDirectory this.workingDirectory = workingDirectory
@ -484,23 +566,23 @@ class GitCommandManager {
// Git version // Git version
core.debug('Getting git version') core.debug('Getting git version')
let gitVersion = new GitVersion() this.gitVersion = new GitVersion()
let gitOutput = await this.execGit(['version']) let gitOutput = await this.execGit(['version'])
let stdout = gitOutput.stdout.trim() let stdout = gitOutput.stdout.trim()
if (!stdout.includes('\n')) { if (!stdout.includes('\n')) {
const match = stdout.match(/\d+\.\d+(\.\d+)?/) const match = stdout.match(/\d+\.\d+(\.\d+)?/)
if (match) { if (match) {
gitVersion = new GitVersion(match[0]) this.gitVersion = new GitVersion(match[0])
} }
} }
if (!gitVersion.isValid()) { if (!this.gitVersion.isValid()) {
throw new Error('Unable to determine git version') throw new Error('Unable to determine git version')
} }
// Minimum git version // Minimum git version
if (!gitVersion.checkMinimum(MinimumGitVersion)) { if (!this.gitVersion.checkMinimum(MinimumGitVersion)) {
throw new Error( throw new Error(
`Minimum required git version is ${MinimumGitVersion}. Your git ('${this.gitPath}') is ${gitVersion}` `Minimum required git version is ${MinimumGitVersion}. Your git ('${this.gitPath}') is ${this.gitVersion}`
) )
} }
@ -532,8 +614,16 @@ class GitCommandManager {
} }
} }
this.doSparseCheckout = doSparseCheckout
if (this.doSparseCheckout) {
if (!this.gitVersion.checkMinimum(MinimumGitSparseCheckoutVersion)) {
throw new Error(
`Minimum Git version required for sparse checkout is ${MinimumGitSparseCheckoutVersion}. Your git ('${this.gitPath}') is ${this.gitVersion}`
)
}
}
// Set the user agent // Set the user agent
const gitHttpUserAgent = `git/${gitVersion} (github-actions-checkout)` const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`
core.debug(`Set git useragent to: ${gitHttpUserAgent}`) core.debug(`Set git useragent to: ${gitHttpUserAgent}`)
this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent
} }

View file

@ -81,12 +81,18 @@ export async function prepareExistingDirectory(
} }
core.endGroup() core.endGroup()
// Check for submodules and delete any existing files if submodules are present
if (!(await git.submoduleStatus())) {
remove = true
core.info('Bad Submodules found, removing existing files')
}
// Clean // Clean
if (clean) { if (clean) {
core.startGroup('Cleaning the repository') core.startGroup('Cleaning the repository')
if (!(await git.tryClean())) { if (!(await git.tryClean())) {
core.debug( 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}'.` `The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For further investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`
) )
remove = true remove = true
} else if (!(await git.tryReset())) { } else if (!(await git.tryReset())) {

View file

@ -9,7 +9,10 @@ import * as path from 'path'
import * as refHelper from './ref-helper' import * as refHelper from './ref-helper'
import * as stateHelper from './state-helper' import * as stateHelper from './state-helper'
import * as urlHelper from './url-helper' import * as urlHelper from './url-helper'
import {IGitCommandManager} from './git-command-manager' import {
MinimumGitSparseCheckoutVersion,
IGitCommandManager
} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
export async function getSource(settings: IGitSourceSettings): Promise<void> { export async function getSource(settings: IGitSourceSettings): Promise<void> {
@ -153,23 +156,38 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Fetch // Fetch
core.startGroup('Fetching the repository') core.startGroup('Fetching the repository')
const fetchOptions: {
filter?: string
fetchDepth?: number
fetchTags?: boolean
showProgress?: boolean
} = {}
if (settings.filter) {
fetchOptions.filter = settings.filter
} else if (settings.sparseCheckout) {
fetchOptions.filter = 'blob:none'
}
if (settings.fetchDepth <= 0) { if (settings.fetchDepth <= 0) {
// Fetch all branches and tags // Fetch all branches and tags
let refSpec = refHelper.getRefSpecForAllHistory( let refSpec = refHelper.getRefSpecForAllHistory(
settings.ref, settings.ref,
settings.commit settings.commit
) )
await git.fetch(refSpec) await git.fetch(refSpec, fetchOptions)
// When all history is fetched, the ref we're interested in may have moved to a different // When all history is fetched, the ref we're interested in may have moved to a different
// commit (push or force push). If so, fetch again with a targeted refspec. // commit (push or force push). If so, fetch again with a targeted refspec.
if (!(await refHelper.testRef(git, settings.ref, settings.commit))) { if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
refSpec = refHelper.getRefSpec(settings.ref, settings.commit) refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec) await git.fetch(refSpec, fetchOptions)
} }
} else { } else {
fetchOptions.fetchDepth = settings.fetchDepth
fetchOptions.fetchTags = settings.fetchTags
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(refSpec, settings.fetchDepth) await git.fetch(refSpec, fetchOptions)
} }
core.endGroup() core.endGroup()
@ -185,12 +203,30 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// LFS fetch // LFS fetch
// Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time). // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
// Explicit lfs fetch will fetch lfs objects in parallel. // Explicit lfs fetch will fetch lfs objects in parallel.
if (settings.lfs) { // For sparse checkouts, let `checkout` fetch the needed objects lazily.
if (settings.lfs && !settings.sparseCheckout) {
core.startGroup('Fetching LFS objects') core.startGroup('Fetching LFS objects')
await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref)
core.endGroup() core.endGroup()
} }
// Sparse checkout
if (!settings.sparseCheckout) {
let gitVersion = await git.version()
// no need to disable sparse-checkout if the installed git runtime doesn't even support it.
if (gitVersion.checkMinimum(MinimumGitSparseCheckoutVersion)) {
await git.disableSparseCheckout()
}
} else {
core.startGroup('Setting up sparse checkout')
if (settings.sparseCheckoutConeMode) {
await git.sparseCheckout(settings.sparseCheckout)
} else {
await git.sparseCheckoutNonConeMode(settings.sparseCheckout)
}
core.endGroup()
}
// Checkout // Checkout
core.startGroup('Checking out the ref') core.startGroup('Checking out the ref')
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
@ -261,7 +297,11 @@ export async function cleanup(repositoryPath: string): Promise<void> {
let git: IGitCommandManager let git: IGitCommandManager
try { try {
git = await gitCommandManager.createCommandManager(repositoryPath, false) git = await gitCommandManager.createCommandManager(
repositoryPath,
false,
false
)
} catch { } catch {
return return
} }
@ -297,7 +337,8 @@ async function getGitCommandManager(
try { try {
return await gitCommandManager.createCommandManager( return await gitCommandManager.createCommandManager(
settings.repositoryPath, settings.repositoryPath,
settings.lfs settings.lfs,
settings.sparseCheckout != null
) )
} catch (err) { } catch (err) {
// Git is required for LFS // Git is required for LFS

View file

@ -29,11 +29,36 @@ export interface IGitSourceSettings {
*/ */
clean: boolean clean: boolean
/**
* The filter determining which objects to include
*/
filter: string | undefined
/**
* The array of folders to make the sparse checkout
*/
sparseCheckout: string[]
/**
* Indicates whether to use cone mode in the sparse checkout (if any)
*/
sparseCheckoutConeMode: boolean
/** /**
* The depth when fetching * The depth when fetching
*/ */
fetchDepth: number fetchDepth: number
/**
* Fetch tags, even if fetchDepth > 0 (default: false)
*/
fetchTags: boolean
/**
* Indicates whether to use the --progress option when fetching
*/
showProgress: boolean
/** /**
* Indicates whether to fetch LFS objects * Indicates whether to fetch LFS objects
*/ */
@ -69,6 +94,11 @@ export interface IGitSourceSettings {
*/ */
sshStrict: boolean sshStrict: boolean
/**
* The SSH user to login as
*/
sshUser: string
/** /**
* Indicates whether to persist the credentials on disk to enable scripting authenticated git commands * Indicates whether to persist the credentials on disk to enable scripting authenticated git commands
*/ */

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