Engineering guide

Optimize Large iOS Repository Checkouts on a Cloud Mac

Optimize Large iOS Repository Checkouts on a Cloud Mac

When an iOS repository contains years of commit history, design source files, screen recordings, and test fixtures, a slow first-time setup on a cloud Mac is easily mistaken for a CPU or disk problem. In practice, the bottleneck often occurs before the build begins: standard Git objects are downloaded in full, Git LFS automatically retrieves every large file during checkout, and CI ultimately builds only one App. The answer is not to cache the entire directory indiscriminately. Instead, pin the inputs first, then control commit objects, working-tree paths, and LFS content as separate layers.

Measure Checkout Cost First

Do not record only the duration of git clone. Break the process into at least three stages—network transfer, working-tree materialization, and LFS downloads—and save the following results:

git --version
git lfs version
du -sh .git
du -sh .git/lfs 2>/dev/null || true
du -sh .
git count-objects -vH
git lfs ls-files | wc -l

Measure first-time runs separately from runs that reuse an existing working tree. The first run reflects the cost of transferring data from the remote, while reuse exposes ineffective cleanup, growth in historical objects, and an expanding LFS cache. Record the final commit value as well, rather than saving only the branch name:

git rev-parse HEAD
git status --short

The goal is not to make the directory look smaller. It is to ensure that the same commit, the same set of directories, and the same LFS objects can be reproduced reliably.

Combine Partial Clone and Sparse Checkout

The blob:none filter in a partial clone defers downloading regular file contents, while sparse checkout limits which directories are materialized in the working tree. The two can be combined, but neither automatically restricts Git LFS downloads, so the initial smudge step must be skipped explicitly.

export GIT_LFS_SKIP_SMUDGE=1
git clone --filter=blob:none --no-checkout "$REPO_URL" app
cd app
git lfs install --local
git sparse-checkout init --cone
git sparse-checkout set App Packages Shared
git fetch --depth=1 origin "$BUILD_REF"
git checkout --detach FETCH_HEAD
git rev-parse HEAD

--cone mode is well suited to projects organized by directory. Its rules are simpler, and it is less likely to make files in parent directories disappear unexpectedly. Replace App, Packages, and Shared with the project’s actual build dependencies. If a workspace, script, or configuration file is stored at the repository root, verify that it is still included.

Partial clone requires the remote to support object filtering. If the command reports a warning about filtering capabilities, treat the result as a fallback to a regular clone and measure the size of .git again. Do not assume that the optimization took effect.

Fetch Only the LFS Objects Required by the Current Job

After checkout, LFS files may still be pointers. First list the objects referenced by the current commit, then fetch only the paths required by the job:

git lfs ls-files
git lfs pull --include="App/Assets/**,Shared/Fixtures/**"
git lfs fsck

Path filters should be based on build inputs, not guesses derived from file extensions. UI tests may depend on images, videos, and localization fixtures. Fetching only source-code directories can allow compilation to succeed while causing tests to fail at runtime.

Check Passing result Common issue
git lfs ls-files Shows the LFS-tracked entries in the current commit .gitattributes was not retrieved with the target commit
File header check The file contains the actual binary content The file is still a pointer containing oid sha256
git lfs fsck Local object verification succeeds An interrupted download or a damaged object directory
git status --short The working tree has no unexpected changes A tool rewrote resource files after checkout

Do not put git lfs pull in a shared initialization script without path constraints. Otherwise, every job will download assets unrelated to its own workload. A more reliable approach is for each pipeline to declare its own set of required LFS inputs.

Handle CI Concurrency and Credentials

Each CI job should use an independent working tree. When multiple jobs share the same writable repository, sparse-checkout rules, index locks, and temporary LFS files can interfere with one another. Even if the jobs read identical code, do not allow two jobs to modify .git/info/sparse-checkout at the same time.

Inject credentials only during retrieval and limit them to the permissions required for the target repository. After git fetch and git lfs pull complete, remove temporary environment variables and credential-helper configuration. Do not print remote URLs containing tokens in logs. Use the following commands to verify that the displayed values are safe:

git remote -v
git config --local --get-regexp 'credential|lfs' || true

Submodules require separate handling. Partial-clone settings and sparse-checkout rules from the main repository do not propagate to submodules automatically. If a submodule also uses LFS, install the local LFS configuration in that submodule’s directory and perform the corresponding retrieval there.

Avoid Unsafe Cleanup

git lfs prune is appropriate for an exclusive repository, but not for an LFS object directory shared by multiple jobs. In a shared environment, it is safer to create an independent directory for each job and delete the entire directory when the job finishes. If the working tree must be reused, first confirm that no jobs are running concurrently, then perform cleanup and integrity checks.

Replace Post-Failure Guesswork with Pre-Build Validation

Before starting xcodebuild, add a lightweight validation gate. At a minimum, it should verify that the commit value is correct, the working tree is clean, critical project files exist, and LFS pointers have been replaced with actual content. It should also report disk usage. For example:

test -f App/App.xcodeproj/project.pbxproj
test -s App/Assets/LaunchVideo.mov
if grep -q "oid sha256:" App/Assets/LaunchVideo.mov; then
  exit 1
fi
git diff --exit-code
git lfs fsck
du -sh . .git .git/lfs

Finally, retain four data points: the commit hash, the list of sparse-checkout directories, the LFS include rules, and the validation result. When a job fails on a cloud Mac, this makes it possible to verify that the inputs are complete before investigating compiler, signing, or test logs. It also prevents repository setup errors from being misclassified as intermittent build failures.

Frequently asked questions

Do partial clone and sparse checkout replace Git LFS?

No. Partial clone limits ordinary Git object transfer, sparse checkout controls working-tree paths, and Git LFS retrieves the large files represented by LFS pointers.

Why does the checkout still contain Git LFS pointer files?

Smudge was likely skipped without a later git lfs pull. Verify the current commit, include patterns, credentials, and LFS configuration before fetching and checking the required objects.

Should every CI job run git lfs prune during cleanup?

Only when the repository and its LFS object directory belong exclusively to that job. For shared storage, discard an isolated workspace instead of pruning objects used by concurrent jobs.

Dedicated cloud Mac

Run build tasks on a dedicated physical node

Choose MiniRents M4 or MiniRents M4 Pro by day, week, month, or quarter, and select a node based on your team and code repository location. Each device is a dedicated physical machine, not a virtual machine.

Choose a model and order