Should Have Been Pointers
Encountered N files that should have been pointers, but weren't - files committed in full instead of as Git LFS pointers, and how to convert them.
Files That Should Have Been Pointers
Symptom
The clone works, but Git LFS prints a warning:
Cloning into '<REPO>'...
Encountered 2 files that should have been pointers, but weren't:
test.psd
test2.psd
Right after the clone, git status already lists the files as changed, and they stay that way:
Changes not staged for commit:
modified: test.psd
modified: test2.psd
The push that brought the files in printed no error: git.eu does not check LFS pointers when it receives a push.
Cause
A .gitattributes rule sends the path to Git LFS (filter=lfs), but the commit holds the full file instead of the small LFS pointer. This happens when:
- the file was committed before the
.gitattributesrule existed; - the commit was made on a machine without git-lfs, or where
git lfs installnever ran; - the history was imported from another forge or repository that did not store the file in Git LFS.
Git LFS expects every blob under such a rule to be a pointer like this one:
version https://git-lfs.github.com/spec/v1
oid sha256:<64 hex characters>
size <bytes>
The blob is the full file instead, so Git LFS warns at checkout. Its clean filter turns the file on disk into a pointer that differs from the committed blob, so git status reports it as modified.
The files on disk are complete and nothing is lost. The server holds no LFS object for them, and every new version makes the repository bigger.
Diagnose
1. Check that a rule sends the file to Git LFS:
git check-attr -a -- test.psd
filter: lfs in the output means the rule covers the file.
2. List the files Git LFS tracks:
git lfs ls-files
The affected files are not listed at all. A listed file shows * (object present locally) or - (pointer only, object not downloaded): both are healthy.
3. Find every file that should be a pointer:
git lfs fsck --pointers
It prints one line per affected file and exits with status 1:
pointer: unexpectedGitObject: "test.psd" (treeish 22a1c46…) should have been a pointer but was not
pointer: unexpectedGitObject: "test2.psd" (treeish 22a1c46…) should have been a pointer but was not
Fix
Pick one option and fix every branch that holds the files. Options A and B add a commit; older commits keep the full files.
Option A - Renormalize (recommended)
This step runs the LFS clean filter on the files again, so the index holds pointers. It adds a new commit and does not rewrite history. test.psd and test2.psd stand for your files.
git add --renormalize test.psd test2.psd
git status
git commit -m "Convert test.psd and test2.psd to Git LFS pointers"
git push
git status now lists the files as staged (modified:). To cover every file that a .gitattributes rule matches, run git add --renormalize . instead. The push uploads the LFS objects:
Uploading LFS objects: 100% (2/2), 358 KB | 0 B/s, done.
Option B - Migrate without rewriting history
This command turns the named paths into pointers and creates the commit itself, so do not run git commit afterwards. History is not rewritten.
git lfs migrate import --no-rewrite test2.psd
git push
It prints migrate: checkout: ..., done. and adds the commit test2.psd: convert to Git LFS.
- The
.gitattributesrule must exist first. Without it the command stops withNo Git LFS filters found in '.gitattributes'(exit status 2). - Give at least one path. Without one it stops with
Expected one or more files with --no-rewrite. - Every run adds one more commit, even an identical one: run it once, with every path in the same command.
Option C - Purge the files from history (⚠ rewrites history)
Use it only when the full files must leave the history, for example to shrink the repository.
This converts the files in every commit of every local branch, then force-pushes the result:
git lfs migrate import --everything --include="*.psd" --yes
git push --force-with-lease origin main
migrate: Rewriting commits: 100% (2/2), done.
migrate: Updating refs: ..., done.
Uploading LFS objects: 100% (1/1), 51 KB | 0 B/s, done.
+ 9b5abe9...1f4d751 main -> main (forced update)
Push every other rewritten branch and tag the same way (git push --force-with-lease --all origin, then git push --force-with-lease --tags origin).
Verify
git lfs fsck --pointers
git lfs ls-files
git lfs fsck --pointers prints Git LFS fsck OK, and git lfs ls-files lists each file, for example 72c7c7a20a - test.psd. A fresh clone prints no warning.
After Option A or B, older commits still hold the full files: git lfs fsck --pointers HEAD~3..HEAD still reports them in that range. Only Option C removes them.
Prevent
- Install git-lfs and run
git lfs installon every machine that commits to the repository. - Commit the
.gitattributesrule (git lfs track "*.psd") before the first large file. - Run
git lfs fsck --pointersin CI, so a full file is caught before it spreads.