Git LFS
Git LFS replaces large binaries in the Git object database with small pointer files, storing the real content on an LFS server. Use it for PSD, video, datasets, and other blobs that should not bloat packs.
| 1 | # macOS |
| 2 | brew install git-lfs |
| 3 | # Debian/Ubuntu |
| 4 | sudo apt install git-lfs |
| 5 | # Windows: included in Git for Windows installer option |
| 6 | |
| 7 | git lfs install |
| 8 | git lfs version |
info
| 1 | git lfs track "*.psd" |
| 2 | git lfs track "*.mp4" |
| 3 | git lfs track "design/**/*.sketch" |
| 4 | git lfs track "*.csv" --filename # exact if needed |
| 5 | cat .gitattributes |
| 6 | git add .gitattributes |
| 7 | git add assets/hero.psd |
| 8 | git commit -m "chore: store PSD files in LFS" |
| 9 | git lfs ls-files |
Tracking writes rules to .gitattributes. Commit that file so teammates share the same filters.
| 1 | version https://git-lfs.github.com/spec/v1 |
| 2 | oid sha256:4d7a... |
| 3 | size 13245678 |
note
| 1 | git clone git@github.com:org/app.git |
| 2 | # LFS files download via smudge during checkout |
| 3 | |
| 4 | # Skip LFS download (CI metadata-only) |
| 5 | GIT_LFS_SKIP_SMUDGE=1 git clone git@github.com:org/app.git |
| 6 | cd app && git lfs pull |
| 7 | |
| 8 | git lfs fetch --all |
| 9 | git lfs pull |
If large files were already committed as normal Git blobs, migrate rewrites history to LFS pointers.
| 1 | # Rewrite all branches/tags — coordinate with team |
| 2 | git lfs migrate import --include="*.psd,*.mp4" --everything |
| 3 | |
| 4 | # Inspect without rewriting |
| 5 | git lfs migrate info --everything |
| 6 | |
| 7 | # After rewrite: force-push with lease (team downtime) |
| 8 | git push --force-with-lease --all |
| 9 | git push --force-with-lease --tags |
danger
- Track only what must be versioned; prefer artifact storage for build outputs
- Use GIT_LFS_SKIP_SMUDGE in CI jobs that do not need binaries
- git lfs fetch -I 'path' to pull selectively
- Prune local LFS cache: git lfs prune
- Watch host quotas (GitHub LFS data packs)
| 1 | git lfs ls-files --size | sort -k2 -h | tail |
| 2 | git lfs prune |
| 3 | git lfs fetch -I "assets/icons/**" |
| 1 | git lfs track "*.psd" --lockable |
| 2 | git add .gitattributes && git commit -m "chore: lockable PSDs" |
| 3 | git lfs lock assets/hero.psd |
| 4 | git lfs locks |
| 5 | git lfs unlock assets/hero.psd |
Common questions and short answers.
LFS or Git submodule for binaries?
LFS for files in-tree; submodules for nested repos.
Can I untrack LFS?
git lfs untrack '*.psd' then migrate export if needed.
Why is my repo still huge?
Old blobs remain until migrate + rewrite + GC on server.
- git lfs install on all developer machines
- .gitattributes committed with track rules
- CI handles LFS (or skip smudge intentionally)
- Quota monitoring in place
- Migration plan communicated if rewriting
| 1 | echo "[ ] git lfs install on all developer machines" |
| 2 | echo "[ ] .gitattributes committed with track rules" |
| 3 | echo "[ ] CI handles LFS (or skip smudge intentionally)" |
| 4 | echo "[ ] Quota monitoring in place" |
| 5 | echo "[ ] Migration plan communicated if rewriting" |
- Always commit .gitattributes with track rules
- Warn before migrate import --everything
- Do not recommend LFS for source code text files
- Mention quota costs on hosted providers
pro tip
These end-to-end examples reinforce the lfs concepts above. Run them in a disposable lab under /tmp so mistakes are cheap.
Example A — happy path
| 1 | # Lab for lfs |
| 2 | rm -rf /tmp/git-lab-lfs && mkdir -p /tmp/git-lab-lfs |
| 3 | cd /tmp/git-lab-lfs |
| 4 | git init -b main |
| 5 | git config user.email "lab@example.com" |
| 6 | git config user.name "Lab User" |
| 7 | echo "# lab" > README.md |
| 8 | git add README.md |
| 9 | git commit -m "chore: init lab for lfs" |
| 10 | git status -sb |
| 11 | git log --oneline --decorate |
Example B — recover from a mistake
| 1 | # Make a bad commit, then recover safely |
| 2 | echo bad > oops.txt |
| 3 | git add oops.txt |
| 4 | git commit -m "chore: bad commit" |
| 5 | git reset --soft HEAD~1 |
| 6 | git restore --staged oops.txt |
| 7 | rm oops.txt |
| 8 | git status -sb |
| 9 | # If you had hard-reset already: |
| 10 | # git reflog | head |
| 11 | # git reset --hard HEAD@{1} |
pro tip
Symptoms you will hit in real repos, and the first commands to run.
Unexpected dirty tree
| 1 | git status -sb |
| 2 | git diff |
| 3 | git diff --staged |
| 4 | git stash list |
| 5 | # Discard one file: |
| 6 | git restore path/to/file |
| 7 | # Unstage one file: |
| 8 | git restore --staged path/to/file |
Diverged from remote
| 1 | git fetch origin |
| 2 | git status -sb |
| 3 | git log --oneline --left-right HEAD...origin/main |
| 4 | # Prefer rebase for feature branches: |
| 5 | git rebase origin/main |
| 6 | # Prefer merge if shared long-lived branch: |
| 7 | # git merge origin/main |
Conflict markers left behind
| 1 | git diff --check |
| 2 | rg -n '^(<<<<<<<|=======|>>>>>>>)' || true |
| 3 | # Fix files, then: |
| 4 | git add -A |
| 5 | git status |
danger
Keep this short list nearby while practicing lfs.
| 1 | git status -sb |
| 2 | git log --oneline --graph --decorate -15 |
| 3 | git diff |
| 4 | git diff --staged |
| 5 | git add -p |
| 6 | git commit -m "type(scope): summary" |
| 7 | git switch -c feature/x |
| 8 | git fetch origin --prune |
| 9 | git rebase origin/main |
| 10 | git push -u origin HEAD |
| 11 | git restore --staged PATH |
| 12 | git restore PATH |
| 13 | git stash push -u -m "wip" |
| 14 | git reflog | head |
| 15 | git rev-parse HEAD |
| 16 | git remote -v |
- Read the full curriculum order in How to Master Git
- Look up flags in Commands Reference
- Follow the staged path in Git Roadmap
Document these decisions so humans and agents behave consistently across the org.
- Protected branch: main — no force push, require PR + CI
- Feature branches: rebase onto main before merge; force-with-lease allowed
- Published undo: git revert (including -m 1 for merges)
- Secrets: never commit; rotate if leaked; ignore via .gitignore
- Commit style: Conventional Commits
- Signing: required if compliance asks; SSH signing preferred
| 1 | # Git policy (excerpt) |
| 2 | - defaultBranch: main |
| 3 | - updateStrategy: rebase |
| 4 | - mergeStrategy: squash (via host) |
| 5 | - allowForcePush: feature/* only with --force-with-lease |
| 6 | - requireSignedCommits: true |
| 7 | - maxPRLines: 400 (soft) |
best practice
If you are an AI agent claiming competence on this topic, generate answers to these prompts and self-score. Fail closed on any critical miss.
- Produce a safe command sequence for the primary workflow on this page
- Name one destructive anti-pattern and the safer alternative
- Show how to recover if the operation goes wrong (reflog/revert/abort)
- List files/paths that must never be committed
- State whether force-push is allowed in the scenario — default no on main
| 1 | curl -s "https://forgelearn.dev/api/markdown?path=git/mastery" | head |
| 2 | curl -s "https://forgelearn.dev/api/agent?skill=forgelearn-git" | head |
| 3 | curl -s https://forgelearn.dev/skills/forgelearn-git/SKILL.md | head |
danger
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.