diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d5fffd8b..d8b0b6d58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,6 +85,20 @@ jobs: - name: Verify sparse checkout run: __test__/verify-sparse-checkout.sh + # 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 - name: Checkout LFS uses: ./ diff --git a/README.md b/README.md index 14b436650..5427a500a 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,10 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl # 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. # Default: 1 fetch-depth: '' @@ -113,6 +117,7 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl - [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) - [Checkout a different branch](#Checkout-a-different-branch) - [Checkout HEAD^](#Checkout-HEAD) @@ -141,6 +146,16 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl src ``` +## Fetch only a single file + +```yaml +- uses: actions/checkout@v3 + with: + sparse-checkout: | + README.md + sparse-checkout-cone-mode: false +``` + ## Fetch all history for all tags and branches ```yaml diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index f2cbfa502..fec657394 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -728,6 +728,7 @@ async function setup(testName: string): Promise { branchExists: jest.fn(), branchList: jest.fn(), sparseCheckout: jest.fn(), + sparseCheckoutNonConeMode: jest.fn(), checkout: jest.fn(), checkoutDetach: jest.fn(), config: jest.fn( @@ -802,6 +803,7 @@ async function setup(testName: string): Promise { clean: true, commit: '', sparseCheckout: [], + sparseCheckoutConeMode: true, fetchDepth: 1, lfs: false, submodules: false, diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts index 8b05606df..362133f4c 100644 --- a/__test__/git-directory-helper.test.ts +++ b/__test__/git-directory-helper.test.ts @@ -463,6 +463,7 @@ async function setup(testName: string): Promise { return [] }), sparseCheckout: jest.fn(), + sparseCheckoutNonConeMode: jest.fn(), checkout: jest.fn(), checkoutDetach: jest.fn(), config: jest.fn(), diff --git a/__test__/input-helper.test.ts b/__test__/input-helper.test.ts index 6d7421b4d..069fda4be 100644 --- a/__test__/input-helper.test.ts +++ b/__test__/input-helper.test.ts @@ -80,6 +80,7 @@ describe('input-helper tests', () => { expect(settings.commit).toBeTruthy() expect(settings.commit).toBe('1234567890123456789012345678901234567890') expect(settings.sparseCheckout).toBe(undefined) + expect(settings.sparseCheckoutConeMode).toBe(true) expect(settings.fetchDepth).toBe(1) expect(settings.lfs).toBe(false) expect(settings.ref).toBe('refs/heads/some-ref') diff --git a/__test__/verify-sparse-checkout-non-cone-mode.sh b/__test__/verify-sparse-checkout-non-cone-mode.sh new file mode 100644 index 000000000..1ea93bf1e --- /dev/null +++ b/__test__/verify-sparse-checkout-non-cone-mode.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# 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 \ No newline at end of file diff --git a/action.yml b/action.yml index d0c96b1e0..e562b569f 100644 --- a/action.yml +++ b/action.yml @@ -58,6 +58,10 @@ inputs: 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: description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.' default: 1 diff --git a/dist/index.js b/dist/index.js index b48bffb96..2c9219bcf 100644 --- a/dist/index.js +++ b/dist/index.js @@ -470,6 +470,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.createCommandManager = exports.MinimumGitVersion = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); +const fs = __importStar(__nccwpck_require__(7147)); const fshelper = __importStar(__nccwpck_require__(7219)); const io = __importStar(__nccwpck_require__(7436)); const path = __importStar(__nccwpck_require__(1017)); @@ -579,6 +580,18 @@ class GitCommandManager { yield this.execGit(['sparse-checkout', 'set', ...sparseCheckout]); }); } + sparseCheckoutNonConeMode(sparseCheckout) { + return __awaiter(this, void 0, void 0, function* () { + yield this.execGit(['config', 'core.sparseCheckout', 'true']); + const output = yield this.execGit([ + 'rev-parse', + '--git-path', + 'info/sparse-checkout' + ]); + const sparseCheckoutPath = output.stdout.trimRight(); + yield fs.promises.appendFile(sparseCheckoutPath, `\n${sparseCheckout.join('\n')}\n`); + }); + } checkout(ref, startPoint) { return __awaiter(this, void 0, void 0, function* () { const args = ['checkout', '--progress', '--force']; @@ -1253,7 +1266,12 @@ function getSource(settings) { // Sparse checkout if (settings.sparseCheckout) { core.startGroup('Setting up sparse checkout'); - yield git.sparseCheckout(settings.sparseCheckout); + if (settings.sparseCheckoutConeMode) { + yield git.sparseCheckout(settings.sparseCheckout); + } + else { + yield git.sparseCheckoutNonConeMode(settings.sparseCheckout); + } core.endGroup(); } // Checkout @@ -1697,6 +1715,9 @@ function getInputs() { result.sparseCheckout = sparseCheckout; core.debug(`sparse checkout = ${result.sparseCheckout}`); } + result.sparseCheckoutConeMode = + (core.getInput('sparse-checkout-cone-mode') || 'true').toUpperCase() !== + 'FALSE'; // Fetch depth result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1')); if (isNaN(result.fetchDepth) || result.fetchDepth < 0) { diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 3a0ec5830..f1f177dc6 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -1,5 +1,6 @@ import * as core from '@actions/core' import * as exec from '@actions/exec' +import * as fs from 'fs' import * as fshelper from './fs-helper' import * as io from '@actions/io' import * as path from 'path' @@ -17,6 +18,7 @@ export interface IGitCommandManager { branchExists(remote: boolean, pattern: string): Promise branchList(remote: boolean): Promise sparseCheckout(sparseCheckout: string[]): Promise + sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise checkout(ref: string, startPoint: string): Promise checkoutDetach(): Promise config( @@ -165,6 +167,20 @@ class GitCommandManager { await this.execGit(['sparse-checkout', 'set', ...sparseCheckout]) } + async sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise { + await this.execGit(['config', 'core.sparseCheckout', 'true']) + const output = await this.execGit([ + 'rev-parse', + '--git-path', + 'info/sparse-checkout' + ]) + const sparseCheckoutPath = output.stdout.trimRight() + await fs.promises.appendFile( + sparseCheckoutPath, + `\n${sparseCheckout.join('\n')}\n` + ) + } + async checkout(ref: string, startPoint: string): Promise { const args = ['checkout', '--progress', '--force'] if (startPoint) { diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index 92e9d005c..b96eb98d3 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -197,7 +197,11 @@ export async function getSource(settings: IGitSourceSettings): Promise { // Sparse checkout if (settings.sparseCheckout) { core.startGroup('Setting up sparse checkout') - await git.sparseCheckout(settings.sparseCheckout) + if (settings.sparseCheckoutConeMode) { + await git.sparseCheckout(settings.sparseCheckout) + } else { + await git.sparseCheckoutNonConeMode(settings.sparseCheckout) + } core.endGroup() } diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts index 182c4536c..3272e638c 100644 --- a/src/git-source-settings.ts +++ b/src/git-source-settings.ts @@ -34,6 +34,11 @@ export interface IGitSourceSettings { */ sparseCheckout: string[] + /** + * Indicates whether to use cone mode in the sparse checkout (if any) + */ + sparseCheckoutConeMode: boolean + /** * The depth when fetching */ diff --git a/src/input-helper.ts b/src/input-helper.ts index e9a2d73d3..2738c0b11 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -89,6 +89,10 @@ export async function getInputs(): Promise { core.debug(`sparse checkout = ${result.sparseCheckout}`) } + result.sparseCheckoutConeMode = + (core.getInput('sparse-checkout-cone-mode') || 'true').toUpperCase() !== + 'FALSE' + // Fetch depth result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1')) if (isNaN(result.fetchDepth) || result.fetchDepth < 0) {