Git LFS: versioning large files without bloating the repository
Git extension for versioning large files
At a glance
- What is it?
- Git LFS replaces large files in a Git repository with small pointer files and stores the real content on a separate server. It is the standard answer to bloated clones, but it adds a server dependency and history rewrites that are hard to undo.
- Who is it for?
- Adopt Git LFS if your repository carries binaries that every clone has to download: the README's own example tracks *.psd, and the pointer mechanism keeps the Git history small. Do not adopt it if you cannot run or pay for an LFS server, or if you need partial history rewrites to be safe on a shared branch, because git lfs migrate import rewrites object IDs.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Git LFS solves, and who actually needs it
Git stores every version of every file as a blob in the object database. That is fine for source code, where a changed line produces a small delta, and painful for a 200 MB video, a Photoshop document or a compiled asset, because each revision adds another full copy to the history. Cloning such a repository transfers all of those copies, and no amount of branching discipline fixes it after the fact.
Git LFS, described in its README as a command line extension and specification for managing large files with Git, changes what lives in the object database. The large file itself is moved to an LFS server, and the repository keeps a small text pointer in its place. The audience is narrow but real: teams whose repositories carry design assets, datasets, media or build artefacts, and who still want Git's branching and review workflow around them.
The README is explicit that this is a binary utility, not a library. The go.mod file carries the same warning in its header: the project does not maintain a stable API or ABI for the module, and the module should not be imported outside of Git LFS itself. If you were hoping to embed LFS behaviour in a Go tool, the project tells you not to.
How the pointer mechanism and the filter pipeline fit together
The mechanism rests on two halves. The first is a clean and smudge filter registered in Git configuration, which is what `git lfs install` sets up. On the way into the repository, the clean filter replaces a tracked file's content with a pointer. On the way out, the smudge filter fetches the real content. The repository file names visible in the source tree, such as lfs/, lfsapi/, lfshttp/, tq/ and git/, map onto that split: the LFS client logic, the HTTP API layer, the transfer queue and the Git integration are separate packages.
The second half is the transfer. According to the README, pushing a tracked file prints an upload progress line, and the pointer goes to the Git remote while the object goes to the LFS endpoint. That endpoint is normally the same host as the Git remote, but it does not have to be. The practical consequence is that a clone is not a single operation against one server: Git fetches the history, then the LFS client resolves pointers and downloads objects, which is why `git lfs pull` exists as a separate step from `git pull`.
The README also points at a specification in docs/spec.md. That matters for anyone evaluating a self-hosted or third-party LFS server: the client is one implementation of a documented protocol, so a server that implements the batch API can serve it. The README does not document the batch API's request and response shapes; the spec file does.
Installing Git LFS on Linux, macOS and Windows
The README gives four install routes. On Linux, Debian and RPM packages come from packagecloud and the details live in INSTALLING.md. On macOS, Homebrew bottles are distributed. On Windows, Git LFS ships inside Git for Windows, with Chocolatey as an alternative. Pre-compiled binaries for Linux, macOS, Windows and FreeBSD are also published on the releases page.
If you use mise-en-place, the README gives a one-line global install:
mise use --global git-lfs@latestAfter that, the tool is available in all of your project directories. The README notes that mise supports Linux, macOS and Windows.
The binary package route runs an install script that places the binaries on the system PATH and performs the global configuration step. On Windows in particular, the README warns that you may need to restart your command shell before Git can locate the binary.
./install.shThe script runs `git lfs install` for you. If you build from source instead, you run it yourself after placing the binary from `bin` on your PATH. Building requires the latest version of Go, GNU make and a Unix-compatible build environment; on Windows you also need `goversioninfo`.
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest
make
git lfs installThe `git lfs install` step is per-machine global configuration, not per-repository. Skipping it is the usual reason a freshly copied binary appears to do nothing.
A first real use: tracking PSD files and confirming they are managed
The README's worked example tracks Photoshop documents. Run it from inside a Git repository that is not already configured for LFS:
git lfs track "*.psd"The quotation marks matter. Without them the shell expands the glob before Git LFS sees it, and the pattern you register is whatever the shell happened to match. The command writes the pattern into `.gitattributes`, and the README states that after any `git lfs track` or `git lfs untrack` you must commit that file:
git add .gitattributes
git commit -m "track *.psd files using Git LFS"From then on the workflow is ordinary Git. Adding and committing a tracked file behaves as usual, and the push uploads the object separately. To check what is under LFS control, run `git lfs ls-files`, which prints a hash, an asterisk and the filename. The asterisk marks files whose content is present locally; a file that has not been downloaded shows differently, which is the fastest way to spot a clone that fetched pointers but not objects.
The README's push example shows the upload line before the Git ref update, which is the visible sign that the transfer stage ran. If you see the ref update without an upload line on a repository you expect to be using LFS, the pattern in `.gitattributes` did not match.
Migrating existing history, and why the rewrite is the risky part
`git lfs track` is not retroactive. The README says so directly: if large files are already in the repository's history, tracking them going forward leaves the old blobs in place. The tool for that is `git lfs migrate`.
git lfs migrate import --include="*.psd" --everythingThe README's warning is unambiguous: this rewrites history and changes all of the Git object IDs in the repository, exactly as the export version does. On a shared branch that means every collaborator's clone is now divergent, and any open pull request based on the old commits points at objects that no longer exist in the rewritten line. The command is the right tool for a repository you control end to end, and a coordination problem for one you do not.
The reverse operation exists for the same reason. If you decide LFS is not for you, `git lfs migrate export --include="*.psd" --everything` converts the repository back to plain Git, and it rewrites history in the same way. There is no in-place undo that preserves object IDs; the README does not document a rollback that avoids a rewrite. Plan the migration as a one-way operation on a clone you can throw away.
Where Git LFS is the wrong tool
The README points at a wiki page for known limitations rather than listing them inline, so the honest position is that the constraints are maintained separately from the code. Two constraints are stated in the README itself, though, and both matter.
The first is the Go API. The project contains a go.mod with a defined module path, and the header comment asks you not to import it. There is no stable API or ABI. If your plan is to build a service that reads or writes LFS pointers by importing the module, you are working against the project's stated intent and against a dependency that can break on any release.
The second is the server requirement. LFS objects live somewhere other than the Git object database, and that somewhere has to exist, be reachable, and have its own storage and access control. A repository with no LFS-capable remote will accept the pointers and fail the transfer. For a solo project with a handful of small binaries, that is infrastructure you did not need: Git's own handling may be adequate, and adding a second storage system to a one-person repository is a cost with no matching benefit.
The version floor is worth noting too. Current releases work with Git versions as early as 2.0.0, but the README recommends a recent Git for performance. A CI image pinned to an old Git will function and be slow.
Alternatives, licensing and the upgrade cost
The most direct alternative is not using LFS at all and keeping large files out of Git, storing them in an artefact registry or object store and referencing them from the repository. The difference is where the pointer lives and who resolves it: with LFS, Git's filter machinery resolves it automatically on checkout, and the README's example shows that a plain `git add` and `git commit` are enough. With an external artefact store, the resolution step is your build or deployment script, and nothing happens automatically on clone. That is more work per developer and less coupling to a server the Git host has to support.
Git-annex is the other common comparison in this space: it also keeps file content outside the repository, but it is a separate tool with its own command vocabulary rather than a filter registered through `git lfs install`, and it is not what the Git hosting platforms implement natively. If your remote already speaks the LFS batch API, LFS is the path of least resistance; if you need content-addressed storage with custom location tracking across many remotes, the trade-off shifts.
On licensing, the repository carries a LICENSE.md file and the metadata reports the licence as NOASSERTION, meaning no standard SPDX identifier was detected. Read LICENSE.md directly before you redistribute binaries or vendor the code. The README's release verification section is the other compliance-adjacent detail: releases are signed with the OpenPGP key of a core team member, the keys are retrievable from a tarball at a documented URL, and `sha256sums.asc` plus `hashes.asc` provide signed hashes for distributors. If your process requires verified artefacts, that path exists and is documented.
Upgrade cost is low by design. The client is a compiled binary, the default branch is main, and the most recent release listed is v3.8.0 from 2026-08-28, following v3.7.1 and v3.7.0. Because the module explicitly has no stable API, upgrades are binary swaps rather than dependency bumps. The cost sits on the server side: an LFS server that lags the client's protocol expectations is the failure you are most likely to hit, and the spec in docs/spec.md is what you check against.
Editorial conclusion
Adopt Git LFS if your repository carries binaries that every clone has to download: the README's own example tracks *.psd, and the pointer mechanism keeps the Git history small. Do not adopt it if you cannot run or pay for an LFS server, or if you need partial history rewrites to be safe on a shared branch, because git lfs migrate import rewrites object IDs. Before committing to it, verify three things on a throwaway clone: that git lfs install has written the filter and hook configuration, that your remote advertises the LFS batch API, and that git lfs ls-files lists the files you expect after the first push.
Frequently asked questions
What is Git LFS for?
It manages large files with Git by keeping the file content outside the Git object database and leaving a small pointer in its place. The README describes it as a command line extension and specification, and shows tracking *.psd files as the basic example.
Is Git LFS free to use?
The repository ships a LICENSE.md file, but the metadata reports the licence as NOASSERTION, so no standard SPDX identifier was detected. Read LICENSE.md before redistributing binaries or vendoring the code; the README does not state pricing for any hosted LFS service.
What are the limitations of Git LFS?
The README links to a wiki page for currently known limitations rather than listing them inline. It does state two constraints directly: there is no stable Go API or ABI and the module should not be imported, and LFS objects require a separate server to hold them.
How do I install Git LFS?
On Linux, Debian and RPM packages come from packagecloud via the instructions in INSTALLING.md. On macOS, Homebrew bottles are distributed; on Windows it ships inside Git for Windows, with Chocolatey as an alternative. Binary packages for Linux, macOS, Windows and FreeBSD are also published on the releases page, and their install script runs git lfs install for you.
How do I install Git LFS on Ubuntu?
Ubuntu uses the Debian packages available from packagecloud. The README points to INSTALLING.md for the Linux installation instructions rather than giving the commands inline.
How do I use git lfs migrate?
Run git lfs migrate import --include="*.psd" --everything from inside the repository to move existing large files in history onto LFS. The README warns that this rewrites history and changes all of the Git object IDs, and the export form reverses it with the same rewrite.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/git-lfs-git-lfs)