diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e77c5741..1e0313b94 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,6 +113,20 @@ jobs: - name: Verify submodules recursive run: __test__/verify-submodules-recursive.sh + # Sparse checkout + - name: Sparse checkout + uses: ./ + with: + ref: test-data/v2/sparse-checkout + path: sparse-checkout + sparse: | + dir2 + dir3 + sparse-cone: true + - name: Verify sparse checkout + run: __test__/verify-sparse-checkout.sh + + # Basic checkout using REST API - name: Remove basic if: runner.os != 'windows' diff --git a/README.md b/README.md index b85967d31..ff8ff7d18 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,14 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl # Default: false lfs: '' + # Do a sparse checkout on given patterns + # Default: null + sparse: '' + + # Use cone pattern for sparse checkout + # Default: false + sparse-cone: '' + # Whether to checkout submodules: `true` to checkout submodules or `recursive` to # recursively checkout submodules. # diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index 2acec38f8..1ef0828a0 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -766,6 +766,7 @@ async function setup(testName: string): Promise { git.env[name] = value }), shaExists: jest.fn(), + sparseCheckout: jest.fn(), submoduleForeach: jest.fn(async () => { return '' }), @@ -806,6 +807,8 @@ async function setup(testName: string): Promise { repositoryName: 'my-repo', repositoryOwner: 'my-org', repositoryPath: '', + sparse: null, + sparseCone: false, sshKey: sshPath ? 'some ssh private key' : '', sshKnownHosts: '', sshStrict: true, diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts index 70849b538..064f3c204 100644 --- a/__test__/git-directory-helper.test.ts +++ b/__test__/git-directory-helper.test.ts @@ -420,6 +420,7 @@ async function setup(testName: string): Promise { revParse: jest.fn(), setEnvironmentVariable: jest.fn(), shaExists: jest.fn(), + sparseCheckout: jest.fn(), submoduleForeach: jest.fn(), submoduleSync: jest.fn(), submoduleUpdate: jest.fn(), diff --git a/__test__/verify-sparse-checkout.sh b/__test__/verify-sparse-checkout.sh new file mode 100755 index 000000000..92e176fd8 --- /dev/null +++ b/__test__/verify-sparse-checkout.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ ! -f "./sparse-checkout/root.txt" ]; then + echo "Expected file 'root.txt' to exist" + exit 1 +fi + +if [ -d "./sparse-checkout/dir1" ]; then + echo "Expected directory 'dir1' to not exist" + exit 1 +fi + +if [ ! -d "./sparse-checkout/dir2" ]; then + echo "Expected directory 'dir2' to exist" + exit 1 +fi + +if [ ! -d "./sparse-checkout/dir3" ]; then + echo "Expected directory 'dir3' to exist" + exit 1 +fi diff --git a/action.yml b/action.yml index cab09ebd3..d5dbac4e6 100644 --- a/action.yml +++ b/action.yml @@ -59,6 +59,12 @@ inputs: lfs: description: 'Whether to download Git-LFS files' default: false + sparse: + description: 'Do a sparse checkout on given patterns' + default: null + sparse-cone: + description: 'Use cone pattern for sparse checkout' + default: false submodules: description: > Whether to checkout submodules: `true` to checkout submodules or `recursive` to diff --git a/dist/index.js b/dist/index.js index 63072b8e3..a0e18a044 100644 --- a/dist/index.js +++ b/dist/index.js @@ -7569,6 +7569,17 @@ class GitCommandManager { return output.exitCode === 0; }); } + sparseCheckout(cone, paths) { + return __awaiter(this, void 0, void 0, function* () { + const args1 = ['sparse-checkout', 'init']; + if (cone) { + args1.push('--cone'); + } + const args2 = ['sparse-checkout', 'set', '--', ...paths]; + yield this.execGit(args1); + yield this.execGit(args2); + }); + } submoduleForeach(command, recursive) { return __awaiter(this, void 0, void 0, function* () { const args = ['submodule', 'foreach']; @@ -18456,6 +18467,16 @@ function getInputs() { // LFS result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'; core.debug(`lfs = ${result.lfs}`); + if (core.getInput('sparse')) { + const paths = core + .getInput('sparse') + .trim() + .split(`\n`) + .map(s => s.trim()); + result.sparse = paths; + } + result.sparseCone = + (core.getInput('sparse-cone') || 'false').toUpperCase() === 'TRUE'; // Submodules result.submodules = false; result.nestedSubmodules = false; @@ -31928,6 +31949,12 @@ function getSource(settings) { yield git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref); core.endGroup(); } + // Sparse checkout + if (settings.sparse) { + core.startGroup('Sparse checkout'); + yield git.sparseCheckout(settings.sparseCone, settings.sparse); + core.endGroup(); + } // Checkout core.startGroup('Checking out the ref'); yield git.checkout(checkoutInfo.ref, checkoutInfo.startPoint); diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 699a963de..ed9757a92 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -38,6 +38,7 @@ export interface IGitCommandManager { revParse(ref: string): Promise setEnvironmentVariable(name: string, value: string): void shaExists(sha: string): Promise + sparseCheckout(cone: boolean, paths: string[]): Promise submoduleForeach(command: string, recursive: boolean): Promise submoduleSync(recursive: boolean): Promise submoduleUpdate(fetchDepth: number, recursive: boolean): Promise @@ -292,6 +293,19 @@ class GitCommandManager { return output.exitCode === 0 } + async sparseCheckout(cone: boolean, paths: string[]): Promise { + const args1 = ['sparse-checkout', 'init'] + + if (cone) { + args1.push('--cone') + } + + const args2 = ['sparse-checkout', 'set', '--', ...paths] + + await this.execGit(args1) + await this.execGit(args2) + } + async submoduleForeach(command: string, recursive: boolean): Promise { const args = ['submodule', 'foreach'] if (recursive) { diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index 48f20da28..568577e5f 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -191,6 +191,13 @@ export async function getSource(settings: IGitSourceSettings): Promise { core.endGroup() } + // Sparse checkout + if (settings.sparse) { + core.startGroup('Sparse checkout') + await git.sparseCheckout(settings.sparseCone, settings.sparse) + core.endGroup() + } + // Checkout core.startGroup('Checking out the ref') await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts index 2da562266..3400a69b1 100644 --- a/src/git-source-settings.ts +++ b/src/git-source-settings.ts @@ -88,4 +88,14 @@ export interface IGitSourceSettings { * User override on the GitHub Server/Host URL that hosts the repository to be cloned */ githubServerUrl: string | undefined + + /** + * Patterns to do a sparse checkout on + */ + sparse: string[] | null + + /** + * Whether or not to use cone pattern on sparse checkout + */ + sparseCone: boolean } diff --git a/src/input-helper.ts b/src/input-helper.ts index 237b06aff..e5e02e46b 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -93,6 +93,19 @@ export async function getInputs(): Promise { result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE' core.debug(`lfs = ${result.lfs}`) + if (core.getInput('sparse')) { + const paths = core + .getInput('sparse') + .trim() + .split(`\n`) + .map(s => s.trim()) + + result.sparse = paths + } + + result.sparseCone = + (core.getInput('sparse-cone') || 'false').toUpperCase() === 'TRUE' + // Submodules result.submodules = false result.nestedSubmodules = false