Add support for sparse checkouts
This commit is contained in:
		
							parent
							
								
									f095bcc56b
								
							
						
					
					
						commit
						9f59c817cf
					
				
							
								
								
									
										13
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							@ -72,6 +72,19 @@ jobs:
 | 
			
		||||
        shell: bash
 | 
			
		||||
        run: __test__/verify-side-by-side.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
 | 
			
		||||
 | 
			
		||||
      # LFS
 | 
			
		||||
      - name: Checkout LFS
 | 
			
		||||
        uses: ./
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								README.md
									
									
									
									
									
								
							@ -74,6 +74,11 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
 | 
			
		||||
    # Default: true
 | 
			
		||||
    clean: ''
 | 
			
		||||
 | 
			
		||||
    # Do a sparse checkout on given patterns. Each pattern should be separated with
 | 
			
		||||
    # new lines
 | 
			
		||||
    # Default: null
 | 
			
		||||
    sparse-checkout: ''
 | 
			
		||||
 | 
			
		||||
    # Number of commits to fetch. 0 indicates all history for all branches and tags.
 | 
			
		||||
    # Default: 1
 | 
			
		||||
    fetch-depth: ''
 | 
			
		||||
@ -106,6 +111,8 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
 | 
			
		||||
 | 
			
		||||
# 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 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)
 | 
			
		||||
@ -116,6 +123,24 @@ 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)
 | 
			
		||||
- [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@v3
 | 
			
		||||
  with:
 | 
			
		||||
    sparse-checkout: .
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Fetch only the root files and `.github` and `src` folder
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
- uses: actions/checkout@v3
 | 
			
		||||
  with:
 | 
			
		||||
    sparse-checkout: |
 | 
			
		||||
      .github
 | 
			
		||||
      src
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Fetch all history for all tags and branches
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
 | 
			
		||||
@ -727,6 +727,7 @@ async function setup(testName: string): Promise<void> {
 | 
			
		||||
    branchDelete: jest.fn(),
 | 
			
		||||
    branchExists: jest.fn(),
 | 
			
		||||
    branchList: jest.fn(),
 | 
			
		||||
    sparseCheckout: jest.fn(),
 | 
			
		||||
    checkout: jest.fn(),
 | 
			
		||||
    checkoutDetach: jest.fn(),
 | 
			
		||||
    config: jest.fn(
 | 
			
		||||
@ -800,6 +801,7 @@ async function setup(testName: string): Promise<void> {
 | 
			
		||||
    authToken: 'some auth token',
 | 
			
		||||
    clean: true,
 | 
			
		||||
    commit: '',
 | 
			
		||||
    sparseCheckout: [],
 | 
			
		||||
    fetchDepth: 1,
 | 
			
		||||
    lfs: false,
 | 
			
		||||
    submodules: false,
 | 
			
		||||
 | 
			
		||||
@ -462,6 +462,7 @@ async function setup(testName: string): Promise<void> {
 | 
			
		||||
    branchList: jest.fn(async () => {
 | 
			
		||||
      return []
 | 
			
		||||
    }),
 | 
			
		||||
    sparseCheckout: jest.fn(),
 | 
			
		||||
    checkout: jest.fn(),
 | 
			
		||||
    checkoutDetach: jest.fn(),
 | 
			
		||||
    config: jest.fn(),
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,7 @@ describe('input-helper tests', () => {
 | 
			
		||||
    expect(settings.clean).toBe(true)
 | 
			
		||||
    expect(settings.commit).toBeTruthy()
 | 
			
		||||
    expect(settings.commit).toBe('1234567890123456789012345678901234567890')
 | 
			
		||||
    expect(settings.sparseCheckout).toBe(undefined)
 | 
			
		||||
    expect(settings.fetchDepth).toBe(1)
 | 
			
		||||
    expect(settings.lfs).toBe(false)
 | 
			
		||||
    expect(settings.ref).toBe('refs/heads/some-ref')
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										63
									
								
								__test__/verify-sparse-checkout.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										63
									
								
								__test__/verify-sparse-checkout.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
@ -53,6 +53,11 @@ inputs:
 | 
			
		||||
  clean:
 | 
			
		||||
    description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching'
 | 
			
		||||
    default: true
 | 
			
		||||
  sparse-checkout:
 | 
			
		||||
    description: >
 | 
			
		||||
      Do a sparse checkout on given patterns.
 | 
			
		||||
      Each pattern should be separated with new lines
 | 
			
		||||
    default: null
 | 
			
		||||
  fetch-depth:
 | 
			
		||||
    description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.'
 | 
			
		||||
    default: 1
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							@ -574,6 +574,11 @@ class GitCommandManager {
 | 
			
		||||
            return result;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    sparseCheckout(sparseCheckout) {
 | 
			
		||||
        return __awaiter(this, void 0, void 0, function* () {
 | 
			
		||||
            yield this.execGit(['sparse-checkout', 'set', ...sparseCheckout]);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    checkout(ref, startPoint) {
 | 
			
		||||
        return __awaiter(this, void 0, void 0, function* () {
 | 
			
		||||
            const args = ['checkout', '--progress', '--force'];
 | 
			
		||||
@ -615,15 +620,18 @@ class GitCommandManager {
 | 
			
		||||
            return output.exitCode === 0;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    fetch(refSpec, fetchDepth) {
 | 
			
		||||
    fetch(refSpec, options) {
 | 
			
		||||
        return __awaiter(this, void 0, void 0, function* () {
 | 
			
		||||
            const args = ['-c', 'protocol.version=2', 'fetch'];
 | 
			
		||||
            if (!refSpec.some(x => x === refHelper.tagsRefSpec)) {
 | 
			
		||||
                args.push('--no-tags');
 | 
			
		||||
            }
 | 
			
		||||
            args.push('--prune', '--progress', '--no-recurse-submodules');
 | 
			
		||||
            if (fetchDepth && fetchDepth > 0) {
 | 
			
		||||
                args.push(`--depth=${fetchDepth}`);
 | 
			
		||||
            if (options.filter) {
 | 
			
		||||
                args.push(`--filter=${options.filter}`);
 | 
			
		||||
            }
 | 
			
		||||
            if (options.fetchDepth && options.fetchDepth > 0) {
 | 
			
		||||
                args.push(`--depth=${options.fetchDepth}`);
 | 
			
		||||
            }
 | 
			
		||||
            else if (fshelper.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))) {
 | 
			
		||||
                args.push('--unshallow');
 | 
			
		||||
@ -1210,20 +1218,24 @@ function getSource(settings) {
 | 
			
		||||
            }
 | 
			
		||||
            // Fetch
 | 
			
		||||
            core.startGroup('Fetching the repository');
 | 
			
		||||
            const fetchOptions = {};
 | 
			
		||||
            if (settings.sparseCheckout)
 | 
			
		||||
                fetchOptions.filter = 'blob:none';
 | 
			
		||||
            if (settings.fetchDepth <= 0) {
 | 
			
		||||
                // Fetch all branches and tags
 | 
			
		||||
                let refSpec = refHelper.getRefSpecForAllHistory(settings.ref, settings.commit);
 | 
			
		||||
                yield git.fetch(refSpec);
 | 
			
		||||
                yield git.fetch(refSpec, fetchOptions);
 | 
			
		||||
                // 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.
 | 
			
		||||
                if (!(yield refHelper.testRef(git, settings.ref, settings.commit))) {
 | 
			
		||||
                    refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
 | 
			
		||||
                    yield git.fetch(refSpec);
 | 
			
		||||
                    yield git.fetch(refSpec, fetchOptions);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                fetchOptions.fetchDepth = settings.fetchDepth;
 | 
			
		||||
                const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
 | 
			
		||||
                yield git.fetch(refSpec, settings.fetchDepth);
 | 
			
		||||
                yield git.fetch(refSpec, fetchOptions);
 | 
			
		||||
            }
 | 
			
		||||
            core.endGroup();
 | 
			
		||||
            // Checkout info
 | 
			
		||||
@ -1238,6 +1250,12 @@ function getSource(settings) {
 | 
			
		||||
                yield git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref);
 | 
			
		||||
                core.endGroup();
 | 
			
		||||
            }
 | 
			
		||||
            // Sparse checkout
 | 
			
		||||
            if (settings.sparseCheckout) {
 | 
			
		||||
                core.startGroup('Setting up sparse checkout');
 | 
			
		||||
                yield git.sparseCheckout(settings.sparseCheckout);
 | 
			
		||||
                core.endGroup();
 | 
			
		||||
            }
 | 
			
		||||
            // Checkout
 | 
			
		||||
            core.startGroup('Checking out the ref');
 | 
			
		||||
            yield git.checkout(checkoutInfo.ref, checkoutInfo.startPoint);
 | 
			
		||||
@ -1673,6 +1691,12 @@ function getInputs() {
 | 
			
		||||
        // Clean
 | 
			
		||||
        result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE';
 | 
			
		||||
        core.debug(`clean = ${result.clean}`);
 | 
			
		||||
        // Sparse checkout
 | 
			
		||||
        const sparseCheckout = core.getMultilineInput('sparse-checkout');
 | 
			
		||||
        if (sparseCheckout.length) {
 | 
			
		||||
            result.sparseCheckout = sparseCheckout;
 | 
			
		||||
            core.debug(`sparse checkout = ${result.sparseCheckout}`);
 | 
			
		||||
        }
 | 
			
		||||
        // Fetch depth
 | 
			
		||||
        result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1'));
 | 
			
		||||
        if (isNaN(result.fetchDepth) || result.fetchDepth < 0) {
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ export interface IGitCommandManager {
 | 
			
		||||
  branchDelete(remote: boolean, branch: string): Promise<void>
 | 
			
		||||
  branchExists(remote: boolean, pattern: string): Promise<boolean>
 | 
			
		||||
  branchList(remote: boolean): Promise<string[]>
 | 
			
		||||
  sparseCheckout(sparseCheckout: string[]): Promise<void>
 | 
			
		||||
  checkout(ref: string, startPoint: string): Promise<void>
 | 
			
		||||
  checkoutDetach(): Promise<void>
 | 
			
		||||
  config(
 | 
			
		||||
@ -25,7 +26,13 @@ export interface IGitCommandManager {
 | 
			
		||||
    add?: boolean
 | 
			
		||||
  ): Promise<void>
 | 
			
		||||
  configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
 | 
			
		||||
  fetch(refSpec: string[], fetchDepth?: number): Promise<void>
 | 
			
		||||
  fetch(
 | 
			
		||||
    refSpec: string[],
 | 
			
		||||
    options: {
 | 
			
		||||
      filter?: string
 | 
			
		||||
      fetchDepth?: number
 | 
			
		||||
    }
 | 
			
		||||
  ): Promise<void>
 | 
			
		||||
  getDefaultBranch(repositoryUrl: string): Promise<string>
 | 
			
		||||
  getWorkingDirectory(): string
 | 
			
		||||
  init(): Promise<void>
 | 
			
		||||
@ -154,6 +161,10 @@ class GitCommandManager {
 | 
			
		||||
    return result
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async sparseCheckout(sparseCheckout: string[]): Promise<void> {
 | 
			
		||||
    await this.execGit(['sparse-checkout', 'set', ...sparseCheckout])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async checkout(ref: string, startPoint: string): Promise<void> {
 | 
			
		||||
    const args = ['checkout', '--progress', '--force']
 | 
			
		||||
    if (startPoint) {
 | 
			
		||||
@ -202,15 +213,23 @@ class GitCommandManager {
 | 
			
		||||
    return output.exitCode === 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fetch(refSpec: string[], fetchDepth?: number): Promise<void> {
 | 
			
		||||
  async fetch(
 | 
			
		||||
    refSpec: string[],
 | 
			
		||||
    options: {filter?: string; fetchDepth?: number}
 | 
			
		||||
  ): Promise<void> {
 | 
			
		||||
    const args = ['-c', 'protocol.version=2', 'fetch']
 | 
			
		||||
    if (!refSpec.some(x => x === refHelper.tagsRefSpec)) {
 | 
			
		||||
      args.push('--no-tags')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    args.push('--prune', '--progress', '--no-recurse-submodules')
 | 
			
		||||
    if (fetchDepth && fetchDepth > 0) {
 | 
			
		||||
      args.push(`--depth=${fetchDepth}`)
 | 
			
		||||
 | 
			
		||||
    if (options.filter) {
 | 
			
		||||
      args.push(`--filter=${options.filter}`)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (options.fetchDepth && options.fetchDepth > 0) {
 | 
			
		||||
      args.push(`--depth=${options.fetchDepth}`)
 | 
			
		||||
    } else if (
 | 
			
		||||
      fshelper.fileExistsSync(
 | 
			
		||||
        path.join(this.workingDirectory, '.git', 'shallow')
 | 
			
		||||
 | 
			
		||||
@ -153,23 +153,26 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
 | 
			
		||||
 | 
			
		||||
    // Fetch
 | 
			
		||||
    core.startGroup('Fetching the repository')
 | 
			
		||||
    const fetchOptions: {filter?: string; fetchDepth?: number} = {}
 | 
			
		||||
    if (settings.sparseCheckout) fetchOptions.filter = 'blob:none'
 | 
			
		||||
    if (settings.fetchDepth <= 0) {
 | 
			
		||||
      // Fetch all branches and tags
 | 
			
		||||
      let refSpec = refHelper.getRefSpecForAllHistory(
 | 
			
		||||
        settings.ref,
 | 
			
		||||
        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
 | 
			
		||||
      // commit (push or force push). If so, fetch again with a targeted refspec.
 | 
			
		||||
      if (!(await refHelper.testRef(git, settings.ref, settings.commit))) {
 | 
			
		||||
        refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
 | 
			
		||||
        await git.fetch(refSpec)
 | 
			
		||||
        await git.fetch(refSpec, fetchOptions)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      fetchOptions.fetchDepth = settings.fetchDepth
 | 
			
		||||
      const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
 | 
			
		||||
      await git.fetch(refSpec, settings.fetchDepth)
 | 
			
		||||
      await git.fetch(refSpec, fetchOptions)
 | 
			
		||||
    }
 | 
			
		||||
    core.endGroup()
 | 
			
		||||
 | 
			
		||||
@ -191,6 +194,13 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
 | 
			
		||||
      core.endGroup()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sparse checkout
 | 
			
		||||
    if (settings.sparseCheckout) {
 | 
			
		||||
      core.startGroup('Setting up sparse checkout')
 | 
			
		||||
      await git.sparseCheckout(settings.sparseCheckout)
 | 
			
		||||
      core.endGroup()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Checkout
 | 
			
		||||
    core.startGroup('Checking out the ref')
 | 
			
		||||
    await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,11 @@ export interface IGitSourceSettings {
 | 
			
		||||
   */
 | 
			
		||||
  clean: boolean
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The array of folders to make the sparse checkout
 | 
			
		||||
   */
 | 
			
		||||
  sparseCheckout: string[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The depth when fetching
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
@ -82,6 +82,13 @@ export async function getInputs(): Promise<IGitSourceSettings> {
 | 
			
		||||
  result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'
 | 
			
		||||
  core.debug(`clean = ${result.clean}`)
 | 
			
		||||
 | 
			
		||||
  // Sparse checkout
 | 
			
		||||
  const sparseCheckout = core.getMultilineInput('sparse-checkout')
 | 
			
		||||
  if (sparseCheckout.length) {
 | 
			
		||||
    result.sparseCheckout = sparseCheckout
 | 
			
		||||
    core.debug(`sparse checkout = ${result.sparseCheckout}`)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Fetch depth
 | 
			
		||||
  result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1'))
 | 
			
		||||
  if (isNaN(result.fetchDepth) || result.fetchDepth < 0) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user