Gitsunmin

TIL

Git Sparse Checkout

git-sparse-checkout - 작업 트리(working tree)를 저장소 전체가 아닌 일부 경로로만 축소한다.

git sparse-checkout (set | add | list | reapply | disable | check-rules | clean) [<options>]

모노레포에서 내가 맡은 apps/web만 필요한데, 저장소를 받으면 수십 개의 앱과 패키지가 모두 디스크에 풀린다. git sparse-checkout은 커밋 히스토리는 그대로 두고, 작업 트리에 꺼내 놓을 파일만 골라내는 명령어다.


언제부터 나온 명령어인가?

기능 자체는 꽤 오래됐지만, 지금의 git sparse-checkout 명령어는 비교적 최근에 생겼다.

버전시기변화
Git 1.7.02010.02core.sparseCheckout 설정과 .git/info/sparse-checkout 파일로 기능 최초 도입. 명령어 없이 파일을 직접 편집하고 git read-tree -mu HEAD로 반영해야 했다
Git 2.25.02020.01git sparse-checkout 명령어 등장 (init, set, list, disable). cone mode, git clone --sparse 추가. 당시 “experimental” 표기
Git 2.262020.03add 서브커맨드 추가
Git 2.272020.06reapply 서브커맨드 추가
Git 2.322021.06sparse index (--sparse-index) 도입
Git 2.352022.01set이 초기화까지 처리 → init은 사실상 불필요(deprecated)
Git 2.372022.06cone mode가 기본값이 되고, non-cone mode는 비권장
Git 2.412023.06check-rules 서브커맨드 추가
Git 2.522025.11clean 서브커맨드 추가

이 명령어는 Microsoft가 Windows 저장소(약 300GB, 파일 350만 개)를 Git으로 옮기면서 겪은 대규모 모노레포 성능 문제를 해결하는 과정에서 다듬어졌다. (VFS for Git → sparse-checkout을 기본으로 쓰는 Scalar → Git 2.38부터 scalar 명령이 Git에 포함)


목적

1. 필요한 파일만 작업 트리에 둔다

flowchart LR
    REPO[(저장소 전체<br/>apps/web, apps/mobile,<br/>packages/ui, packages/utils, docs)]
    REPO -->|일반 checkout| ALL[작업 트리: 전부]
    REPO -->|sparse-checkout set apps/web packages/ui| PART[작업 트리:<br/>apps/web, packages/ui<br/>+ 루트 파일]
  • 디스크 사용량 감소
  • git status, git add, git checkout 등 작업 트리를 훑는 명령이 빨라진다
  • IDE 인덱싱, 파일 검색 범위가 줄어들어 에디터도 가벼워진다

2. 히스토리는 전부 유지한다

sparse-checkout은 작업 트리만 줄인다. 커밋, 브랜치, 다른 디렉토리의 변경 이력은 모두 그대로다.

  • 보이지 않는 파일도 git log -- apps/mobile로 이력을 볼 수 있다
  • 커밋을 만들면 보이지 않는 파일까지 포함한 전체 트리 기준으로 커밋된다 (안 보이는 파일이 삭제되는 게 아니다)

3. 비슷한 기능과의 차이

기능줄이는 대상
git clone --depth (shallow clone)히스토리의 깊이 (오래된 커밋)
git clone --filter=blob:none (partial clone)내려받는 blob 객체 (필요할 때 지연 다운로드)
git sparse-checkout작업 트리에 꺼내 놓는 파일
git submodule저장소 자체를 여러 개로 분리

sparse-checkout만 쓰면 .git 안에는 여전히 모든 객체가 내려받아진다. 네트워크/저장 공간까지 아끼려면 partial clone과 함께 쓰는 것이 정석이다.


사용 방법

아래 구조의 모노레포를 예시로 사용한다.

.
├── README.md
├── package.json
├── apps
│   ├── web
│   │   ├── package.json
│   │   └── src/index.ts
│   └── mobile
│       └── App.tsx
├── packages
│   ├── ui/Button.tsx
│   └── utils/date.ts
└── docs/guide.md

1. clone 할 때부터 sparse로 받기 (권장)

git clone --filter=blob:none --sparse https://github.com/org/monorepo.git
cd monorepo
  • --sparse: 처음에는 루트의 파일만 체크아웃한다
  • --filter=blob:none: 파일 내용(blob)은 실제로 필요할 때만 내려받는다 (partial clone)
$ ls
README.md  package.json

2. 필요한 디렉토리 지정하기 — set

git sparse-checkout set apps/web packages/ui
$ find . -path ./.git -prune -o -type f -print
./README.md
./package.json
./apps/web/package.json
./apps/web/src/index.ts
./packages/ui/Button.tsx

set은 목록을 덮어쓴다. 이미 sparse-checkout이 켜져 있지 않은 저장소에서 실행해도 필요한 설정을 알아서 켜 준다.

# 이미 clone한 일반 저장소에서도 바로 사용 가능
git sparse-checkout set apps/web packages/ui

3. 디렉토리 추가하기 — add

git sparse-checkout add docs

set과 달리 기존 목록을 유지하고 추가만 한다.

4. 현재 설정 확인하기 — list

$ git sparse-checkout list
apps/web
docs
packages/ui

git status에서도 sparse 상태인지 알려 준다.

$ git status
On branch master

You are in a sparse checkout with 75% of tracked files present.

5. 규칙 다시 적용하기 — reapply

merge, rebase 중 충돌 해결을 위해 범위 밖 파일이 작업 트리에 나타날 수 있다. 정리가 끝난 뒤 다시 규칙대로 작업 트리를 맞추고 싶을 때 사용한다.

git sparse-checkout reapply

6. 경로가 규칙에 포함되는지 확인하기 — check-rules

stdin으로 받은 경로 중 현재 규칙에 매칭되는 것만 출력한다.

$ printf 'apps/mobile/App.tsx\npackages/ui/Button.tsx\nREADME.md\n' | git sparse-checkout check-rules
packages/ui/Button.tsx
README.md

7. 끄기 — disable

git sparse-checkout disable

모든 파일이 다시 작업 트리에 나타난다. (.git/info/sparse-checkout 파일은 남아 있다)


cone mode vs non-cone mode

cone mode (기본값, 권장)

디렉토리 단위로만 지정한다. set apps/web을 하면 내부적으로는 아래 규칙이 만들어진다.

$ cat .git/info/sparse-checkout
/*
!/*/
/apps/
!/apps/*/
/apps/web/

이 규칙의 의미는 다음과 같다.

  1. 지정한 디렉토리(apps/web/) 아래의 모든 파일 (깊이 무관)
  2. 지정한 디렉토리의 상위 디렉토리들에 직접 들어 있는 파일 (apps/ 바로 아래 파일)
  3. 루트의 파일 (README.md, package.json)

루트 파일이 항상 포함되는 이유는 package.json, .gitignore, 빌드 설정처럼 어느 디렉토리를 작업하든 필요한 파일들이 루트에 있기 때문이다.

디렉토리 접두사만 비교하면 되므로 매칭이 해시 조회 수준으로 빠르고, sparse index도 cone mode에서만 동작한다.

non-cone mode (비권장)

.gitignore 문법의 패턴을 그대로 쓴다.

git sparse-checkout set --no-cone '/*' '!/docs/' '*.md'

자유도는 높지만 모든 경로를 모든 패턴과 비교해야 해서 O(파일 수 × 패턴 수) 로 느리고, --sparse-index와 함께 쓸 수 없다. 공식 문서에서도 사용을 권장하지 않는다.


sparse index

git sparse-checkout set --sparse-index apps/web packages/ui

일반적인 sparse-checkout은 작업 트리만 줄일 뿐, index(.git/index)에는 여전히 모든 파일 항목이 들어 있다. 파일이 수백만 개인 저장소라면 index 자체가 커서 git status가 느리다.

sparse index를 켜면 범위 밖 디렉토리를 index에서 디렉토리 항목 하나로 접어서 저장한다.

일반 index                       sparse index
apps/mobile/App.tsx      →      apps/mobile/   (tree 하나)
apps/mobile/...
apps/web/package.json           apps/web/package.json
apps/web/src/index.ts           apps/web/src/index.ts

아직 experimental 기능이고, sparse index를 이해하지 못하는 외부 도구나 오래된 Git 버전과 호환되지 않을 수 있다. 문제가 생기면 git sparse-checkout reapply --no-sparse-index로 되돌린다.


설정은 어디에 저장되나?

$ git config --show-scope --list | grep -i -E 'sparse|worktreeconfig'
local     extensions.worktreeconfig=true
worktree  core.sparsecheckout=true
worktree  core.sparsecheckoutcone=true
  • set은 저장소 설정을 worktree별 설정(extensions.worktreeConfig) 으로 바꾼다
  • 그래서 git worktree로 여러 작업 트리를 쓸 때, 각 worktree마다 서로 다른 sparse 범위를 가질 수 있다
  • 경로 목록은 .git/info/sparse-checkout (worktree라면 .git/worktrees/<name>/info/sparse-checkout)에 저장된다

예전 방식 (Git 2.25 이전)

명령어가 생기기 전에는 아래처럼 직접 설정해야 했다. 지금은 쓸 일이 없지만, 오래된 글에서 자주 보인다.

git config core.sparseCheckout true
echo "apps/web/" >> .git/info/sparse-checkout
git read-tree -mu HEAD

주의할 점

  1. 범위 밖 파일을 수정 중이면 숨겨지지 않는다. 변경 사항이 있는 파일은 sparse 범위에서 빠져도 작업 트리에 남는다. 커밋하거나 되돌린 뒤 reapply 한다.

  2. git add로 범위 밖 경로를 추가하려면 --sparse가 필요하다.

    git add --sparse apps/mobile/App.tsx
  3. git grep, IDE 검색은 범위 안의 파일만 본다. 다른 패키지에서 쓰는 코드를 바꿀 때는 영향 범위를 놓치기 쉽다.

  4. 빌드 의존성까지 고려해서 범위를 잡아야 한다. apps/web이 packages/ui를 의존한다면 둘 다 set 해야 빌드가 된다.

  5. 범위 밖에 남은 무시된 파일 정리 — Git 2.52부터 clean 서브커맨드가 있다. (cone mode 전용)

    git sparse-checkout clean --dry-run   # 지울 디렉토리 미리 보기
    git sparse-checkout clean -f          # 실제 삭제

    범위를 좁힌 뒤에도 node_modules, 빌드 산출물 같은 무시된(ignored) 파일 때문에 디렉토리가 남아 있을 때 쓴다.


한 줄 요약

git clone --filter=blob:none --sparse <url>   # 가볍게 받고
git sparse-checkout set <dir> <dir> ...       # 필요한 디렉토리만 꺼내고
git sparse-checkout add <dir>                 # 필요하면 추가하고
git sparse-checkout disable                   # 다 보고 싶으면 끈다

참고