Skip to content

Commit d106d46

Browse files
dschodfdez
andauthored
Add support for sparse checkouts (#1369)
* Add support for sparse checkouts * sparse-checkout: optionally turn off cone mode While it _is_ true that cone mode is the default nowadays (mainly for performance reasons: code mode is much faster than non-cone mode), there _are_ legitimate use cases where non-cone mode is really useful. Let's add a flag to optionally disable cone mode. Signed-off-by: Johannes Schindelin <[email protected]> * Verify minimum Git version for sparse checkout The `git sparse-checkout` command is available only since Git version v2.25.0. The `actions/checkout` Action actually supports older Git versions than that; As of time of writing, the minimum version is v2.18.0. Instead of raising this minimum version even for users who do not require a sparse checkout, only check for this minimum version specifically when a sparse checkout was asked for. Suggested-by: Tingluo Huang <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]> * Support sparse checkout/LFS better Instead of fetching all the LFS objects present in the current revision in a sparse checkout, whether they are needed inside the sparse cone or not, let's instead only pull the ones that are actually needed. To do that, let's avoid running that preemptive `git lfs fetch` call in case of a sparse checkout. An alternative that was considered during the development of this patch (and ultimately rejected) was to use `git lfs pull --include <path>...`, but it turned out to be too inflexible because it requires exact paths, not the patterns that are available via the sparse checkout definition, and that risks running into command-line length limitations. Signed-off-by: Johannes Schindelin <[email protected]> --------- Signed-off-by: Johannes Schindelin <[email protected]> Co-authored-by: Daniel <[email protected]>
1 parent f095bcc commit d106d46

14 files changed

+395
-31
lines changed

.github/workflows/test.yml

+27
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,33 @@ jobs:
7272
shell: bash
7373
run: __test__/verify-side-by-side.sh
7474

75+
# Sparse checkout
76+
- name: Sparse checkout
77+
uses: ./
78+
with:
79+
sparse-checkout: |
80+
__test__
81+
.github
82+
dist
83+
path: sparse-checkout
84+
85+
- name: Verify sparse checkout
86+
run: __test__/verify-sparse-checkout.sh
87+
88+
# Sparse checkout (non-cone mode)
89+
- name: Sparse checkout (non-cone mode)
90+
uses: ./
91+
with:
92+
sparse-checkout: |
93+
/__test__/
94+
/.github/
95+
/dist/
96+
sparse-checkout-cone-mode: false
97+
path: sparse-checkout-non-cone-mode
98+
99+
- name: Verify sparse checkout (non-cone mode)
100+
run: __test__/verify-sparse-checkout-non-cone-mode.sh
101+
75102
# LFS
76103
- name: Checkout LFS
77104
uses: ./

README.md

+40
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
7474
# Default: true
7575
clean: ''
7676

77+
# Do a sparse checkout on given patterns. Each pattern should be separated with
78+
# new lines
79+
# Default: null
80+
sparse-checkout: ''
81+
82+
# Specifies whether to use cone-mode when doing a sparse checkout.
83+
# Default: true
84+
sparse-checkout-cone-mode: ''
85+
7786
# Number of commits to fetch. 0 indicates all history for all branches and tags.
7887
# Default: 1
7988
fetch-depth: ''
@@ -106,6 +115,9 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
106115
107116
# Scenarios
108117
118+
- [Fetch only the root files](#Fetch-only-the-root-files)
119+
- [Fetch only the root files and `.github` and `src` folder](#Fetch-only-the-root-files-and-github-and-src-folder)
120+
- [Fetch only a single file](#Fetch-only-a-single-file)
109121
- [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches)
110122
- [Checkout a different branch](#Checkout-a-different-branch)
111123
- [Checkout HEAD^](#Checkout-HEAD)
@@ -116,6 +128,34 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
116128
- [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event)
117129
- [Push a commit using the built-in token](#Push-a-commit-using-the-built-in-token)
118130

131+
## Fetch only the root files
132+
133+
```yaml
134+
- uses: actions/checkout@v3
135+
with:
136+
sparse-checkout: .
137+
```
138+
139+
## Fetch only the root files and `.github` and `src` folder
140+
141+
```yaml
142+
- uses: actions/checkout@v3
143+
with:
144+
sparse-checkout: |
145+
.github
146+
src
147+
```
148+
149+
## Fetch only a single file
150+
151+
```yaml
152+
- uses: actions/checkout@v3
153+
with:
154+
sparse-checkout: |
155+
README.md
156+
sparse-checkout-cone-mode: false
157+
```
158+
119159
## Fetch all history for all tags and branches
120160

121161
```yaml

__test__/git-auth-helper.test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,8 @@ async function setup(testName: string): Promise<void> {
727727
branchDelete: jest.fn(),
728728
branchExists: jest.fn(),
729729
branchList: jest.fn(),
730+
sparseCheckout: jest.fn(),
731+
sparseCheckoutNonConeMode: jest.fn(),
730732
checkout: jest.fn(),
731733
checkoutDetach: jest.fn(),
732734
config: jest.fn(
@@ -800,6 +802,8 @@ async function setup(testName: string): Promise<void> {
800802
authToken: 'some auth token',
801803
clean: true,
802804
commit: '',
805+
sparseCheckout: [],
806+
sparseCheckoutConeMode: true,
803807
fetchDepth: 1,
804808
lfs: false,
805809
submodules: false,

__test__/git-command-manager.test.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ describe('git-auth-helper tests', () => {
3939
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
4040
const workingDirectory = 'test'
4141
const lfs = false
42-
git = await commandManager.createCommandManager(workingDirectory, lfs)
42+
const doSparseCheckout = false
43+
git = await commandManager.createCommandManager(
44+
workingDirectory,
45+
lfs,
46+
doSparseCheckout
47+
)
4348

4449
let branches = await git.branchList(false)
4550

@@ -70,7 +75,12 @@ describe('git-auth-helper tests', () => {
7075
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
7176
const workingDirectory = 'test'
7277
const lfs = false
73-
git = await commandManager.createCommandManager(workingDirectory, lfs)
78+
const doSparseCheckout = false
79+
git = await commandManager.createCommandManager(
80+
workingDirectory,
81+
lfs,
82+
doSparseCheckout
83+
)
7484

7585
let branches = await git.branchList(false)
7686

__test__/git-directory-helper.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,8 @@ async function setup(testName: string): Promise<void> {
462462
branchList: jest.fn(async () => {
463463
return []
464464
}),
465+
sparseCheckout: jest.fn(),
466+
sparseCheckoutNonConeMode: jest.fn(),
465467
checkout: jest.fn(),
466468
checkoutDetach: jest.fn(),
467469
config: jest.fn(),

__test__/input-helper.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ describe('input-helper tests', () => {
7979
expect(settings.clean).toBe(true)
8080
expect(settings.commit).toBeTruthy()
8181
expect(settings.commit).toBe('1234567890123456789012345678901234567890')
82+
expect(settings.sparseCheckout).toBe(undefined)
83+
expect(settings.sparseCheckoutConeMode).toBe(true)
8284
expect(settings.fetchDepth).toBe(1)
8385
expect(settings.lfs).toBe(false)
8486
expect(settings.ref).toBe('refs/heads/some-ref')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/sh
2+
3+
# Verify .git folder
4+
if [ ! -d "./sparse-checkout-non-cone-mode/.git" ]; then
5+
echo "Expected ./sparse-checkout-non-cone-mode/.git folder to exist"
6+
exit 1
7+
fi
8+
9+
# Verify sparse-checkout (non-cone-mode)
10+
cd sparse-checkout-non-cone-mode
11+
12+
ENABLED=$(git config --local --get-all core.sparseCheckout)
13+
14+
if [ "$?" != "0" ]; then
15+
echo "Failed to verify that sparse-checkout is enabled"
16+
exit 1
17+
fi
18+
19+
# Check that sparse-checkout is enabled
20+
if [ "$ENABLED" != "true" ]; then
21+
echo "Expected sparse-checkout to be enabled (is: $ENABLED)"
22+
exit 1
23+
fi
24+
25+
SPARSE_CHECKOUT_FILE=$(git rev-parse --git-path info/sparse-checkout)
26+
27+
if [ "$?" != "0" ]; then
28+
echo "Failed to validate sparse-checkout"
29+
exit 1
30+
fi
31+
32+
# Check that sparse-checkout list is not empty
33+
if [ ! -f "$SPARSE_CHECKOUT_FILE" ]; then
34+
echo "Expected sparse-checkout file to exist"
35+
exit 1
36+
fi
37+
38+
# Check that all folders from sparse-checkout exists
39+
for pattern in $(cat "$SPARSE_CHECKOUT_FILE")
40+
do
41+
if [ ! -d "${pattern#/}" ]; then
42+
echo "Expected directory '${pattern#/}' to exist"
43+
exit 1
44+
fi
45+
done
46+
47+
# Verify that the root directory is not checked out
48+
if [ -f README.md ]; then
49+
echo "Expected top-level files not to exist"
50+
exit 1
51+
fi

__test__/verify-sparse-checkout.sh

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/bin/sh
2+
3+
# Verify .git folder
4+
if [ ! -d "./sparse-checkout/.git" ]; then
5+
echo "Expected ./sparse-checkout/.git folder to exist"
6+
exit 1
7+
fi
8+
9+
# Verify sparse-checkout
10+
cd sparse-checkout
11+
12+
SPARSE=$(git sparse-checkout list)
13+
14+
if [ "$?" != "0" ]; then
15+
echo "Failed to validate sparse-checkout"
16+
exit 1
17+
fi
18+
19+
# Check that sparse-checkout list is not empty
20+
if [ -z "$SPARSE" ]; then
21+
echo "Expected sparse-checkout list to not be empty"
22+
exit 1
23+
fi
24+
25+
# Check that all folders of the sparse checkout exist
26+
for pattern in $SPARSE
27+
do
28+
if [ ! -d "$pattern" ]; then
29+
echo "Expected directory '$pattern' to exist"
30+
exit 1
31+
fi
32+
done
33+
34+
checkSparse () {
35+
if [ ! -d "./$1" ]; then
36+
echo "Expected directory '$1' to exist"
37+
exit 1
38+
fi
39+
40+
for file in $(git ls-tree -r --name-only HEAD $1)
41+
do
42+
if [ ! -f "$file" ]; then
43+
echo "Expected file '$file' to exist"
44+
exit 1
45+
fi
46+
done
47+
}
48+
49+
# Check that all folders and their children have been checked out
50+
checkSparse __test__
51+
checkSparse .github
52+
checkSparse dist
53+
54+
# Check that only sparse-checkout folders have been checked out
55+
for pattern in $(git ls-tree --name-only HEAD)
56+
do
57+
if [ -d "$pattern" ]; then
58+
if [[ "$pattern" != "__test__" && "$pattern" != ".github" && "$pattern" != "dist" ]]; then
59+
echo "Expected directory '$pattern' to not exist"
60+
exit 1
61+
fi
62+
fi
63+
done

action.yml

+9
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ inputs:
5353
clean:
5454
description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching'
5555
default: true
56+
sparse-checkout:
57+
description: >
58+
Do a sparse checkout on given patterns.
59+
Each pattern should be separated with new lines
60+
default: null
61+
sparse-checkout-cone-mode:
62+
description: >
63+
Specifies whether to use cone-mode when doing a sparse checkout.
64+
default: true
5665
fetch-depth:
5766
description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.'
5867
default: 1

0 commit comments

Comments
 (0)