git-lfs interacts with case sensitivity across platforms poorly · Issue #6187 · git-lfs/git-lfs · GitHub
Skip to content

git-lfs interacts with case sensitivity across platforms poorly #6187

Open

Description

@doctorpangloss

Describe the bug

Git LFS pattern matching in .gitattributes is case-sensitive on Linux, but files may be pushed from case-insensitive systems (Windows, macOS) with varying case extensions. When checking out on Linux, files with uppercase extensions (e.g., .TGA, .PNG, .BMP) are not automatically fetched/smudged because the .gitattributes patterns use lowercase (e.g., *.tga).

To Reproduce

  1. On Windows or macOS, create and push files with uppercase extensions (e.g., texture.TGA)
  2. Ensure .gitattributes has lowercase patterns: *.tga filter=lfs diff=lfs merge=lfs -text
  3. Clone the repository on Linux
  4. Run git lfs ls-files - files with uppercase extensions show * (pointer, not downloaded)
  5. Run git lfs pull - uppercase extension files remain as pointers
  6. Unity (or other tools) fail to read these files: Could not create asset from .../T_Facade_02_N.TGA: File could not be read

Expected behavior

Either:

  • LFS pattern matching should be case-insensitive by default (or have an option for it)
  • git lfs pull should have a warning when patterns don't match due to case
  • Documentation should clearly warn about cross-platform case sensitivity issues

Workaround

Files can be pulled individually using the full path:

git lfs pull --include="path/to/file.TGA"

Or force checkout specific files:

git checkout HEAD -- "path/to/file.TGA"

Or add duplicate patterns with uppercase extensions to .gitattributes:

*.tga filter=lfs diff=lfs merge=lfs -text**Files affected in this case:**
- 29 `.TGA` files
- 5 `.PNG` files
- 2 `.BMP` files
*.TGA filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.PNG filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text
*.BMP filter=lfs diff=lfs merge=lfs -text

System environment

  • OS: Linux 6.14.0-37-generic (Ubuntu)
  • Files originally pushed from Windows/macOS (case-insensitive filesystems)

Output of git lfs env

git-lfs/3.4.1 (GitHub; linux amd64; go 1.22.2)
git version 2.43.0

Endpoint=https://[redacted]@[redacted].execute-api.us-west-2.amazonaws.com/lfs [redacted]
LocalWorkingDir=/home/administrator/Documents/lbxx-6.3
LocalGitDir=/home/administrator/Documents/lbxx/.git/worktrees/lbxx-6.3
LocalGitStorageDir=/home/administrator/Documents/lbxx/.git
LocalMediaDir=/home/administrator/Documents/lbxx/.git/lfs/objects
ConcurrentTransfers=8
TusTransfers=false
BasicTransfersOnly=false
SkipDownloadErrors=false
FetchRecentAlways=false
FetchRecentRefsDays=7
FetchRecentCommitsDays=0
FetchRecentRefsIncludeRemotes=true
PruneOffsetDays=3
PruneVerifyRemoteAlways=false
PruneRemoteName=origin
LfsStorageDir=/home/administrator/Documents/lbxx/.git/lfs
AccessDownload=none
AccessUpload=none
DownloadTransfers=basic,lfs-standalone-file,ssh
UploadTransfers=basic,lfs-standalone-file,ssh
git config filter.lfs.process = "git-lfs filter-process"
git config filter.lfs.smudge = "git-lfs smudge -- %f"
git config filter.lfs.clean = "git-lfs clean -- %f"

Additional context

This is a common cross-platform collaboration issue. Teams working across Windows/macOS (case-insensitive) and Linux (case-sensitive) will silently have broken LFS checkouts unless both cases are explicitly listed in .gitattributes.

Related issue: #6156
Related: #3433 - this probably introduced the bug

Activity

  1. bk2204 commented on Jan 8, 2026

    @bk2204
    Member

    As a Git contributor, I think it's worth pointing out that this actually also affects all uses of patterns in .gitattributes, not just Git LFS. For instance, the following would also produce similar results to those you've observed on Windows and Linux:

    *.sh text eol=lf
    *.bat text eol=crlf
    

    foo.BAT would use CRLF on Windows but LF (or the configured default) on Linux or FreeBSD. Similar rules apply for working-tree-encoding and all of the other possible rules. Git LFS is not involved at all in this pattern matching.

    Because tools like git checkout actually cause Git to decide which files to invoke Git LFS on and Git LFS doesn't get invoked at all on those files unless Git decides it should, I don't think it would be possible to have a deliberately case-insensitive mode unless Git added support for it since it wouldn't work in all cases. Note that setting core.ignorecase is not recommended because that's supposed to reflect the actual state on disk, which means that Git works correctly even with case-sensitive Windows and macOS repositories and case-insensitive Linux repositories (all of which exist).

    I'll also point out that there are some cases in which case-insensitivity definitely breaks things. For instance, .c files are always C files, but on most Unix systems, .C files are C++ files and are compiled accordingly (GCC and clang have this behaviour).

    I don't want to speak for the Git LFS maintainers in deciding what the right decision is here, but just to mention a relevant detail about how Git is involved and how Git LFS is mostly a victim of what Git itself has decided to do.

  2. doctorpangloss commented on Jan 8, 2026

    @doctorpangloss
    Author

    I'll also point out that there are some cases in which case-insensitivity definitely breaks things. For instance, .c files are always C files, but on most Unix systems, .C files are C++ files and are compiled accordingly (GCC and clang have this behaviour).

    let's look on github for .C files
    https://github.com/search?q=path%3A%2F%28%3F-i%29%5C.C%24%2F&type=code
    795k files
    page after page, exactly zero are C++. using .C to mean C++, and then checking in those C++ files with LFS for some reason, is very HPE NonStop.

    IMO, the paths for lfs in .gitattributes should be evaluated case insensitively. That's always what the user intends. there can be a separate config to make it case sensitive.

  3. chrisd8088 commented on Jan 9, 2026

    @chrisd8088
    Member

    Hey, I'm sorry you're having trouble, and thanks for writing up the issue and proposing some options.

    We would be happy to review a PR to update our documentation, and our git-lfs-faq(7) manual page might be the best place for some notes about case-sensitivity. As well, anyone can contribute to our wiki, and the Limitations page there might be a suitable spot for some cautions about case-sensitivity.

    As @bk2204 noted, we're not able to affect the way Git applies attributes, and that's one of the key ways in which Git LFS is invoked by Git, so there are major limitations there in terms of how we operate on case-preserving but case-insensitive filesystems. We can illustrate this without Git LFS at all; for instance, on a macOS system with APFS in its default mode, by toggling core.ignoreCase we can still make Git ignore a foo.BIN file if the .gitattributes file only specifies *.bin:

    $ git init test
    $ cd test
    $ git config core.ignoreCase
    true
    $ echo "*.bin myattr" >.gitattributes
    $ echo foo >foo.bin
    $ git check-attr -a foo.bin
    foo.bin: myattr: set
    $ git check-attr -a foo.BIN
    foo.BIN: myattr: set
    
    $ git config core.ignoreCase false
    $ git check-attr -a foo.BIN
    # no output
    

    One option for users is to explicitly make their Git attribute patterns case-insensitive, e.g., *.[bB][iI][nN] or *.[tT][gG][aA]. Unfortunately we can't make this the default type of pattern created by the git lfs track command, at least not until the next major release of the Git LFS client, as it would be a backwards-incompatible change. However, if someone wanted to write up a PR to add a -i/--ignore-case option to the git lfs track command, we'd certainly consider it.

    Regarding the git lfs checkout and git lfs pull commands, I think one of the reasons you're noticing this issue is that in recent versions of the Git LFS client these commands make use of the git ls-files command and in particular its ability to list only the files matching a given attribute, in our case the filter=lfs attribute. This allows the commands to operate efficiently in very large repositories where users may have performed only a partial clone or a sparse checkout, since we don't have to iterate through all the files in the repository. It does mean, however, that we depend on the Git configuration and the settings in the .gitattributes files to correctly identify all relevant Git LFS files.

    I suppose one avenue which could be explored would be to run the git ls-files command twice when core.ignoreCase was set to false (as it typically is on a Linux system, for instance). Running it a second time with core.ignoreCase explicitly and temporarily set to true and then comparing the results with the first run would identify any files which might have unintentionally been skipped due to case-sensitivity differences between systems; these could then be listed in a warning message. If someone wants to draft a PR to implement this design (or another one) as an experiment, we'd of course take a look at it. We would want to be cautious, though, with regard to any potential performance penalty in a large repository.

  4. moved this to Optional Enhancements in Backlogon Jan 9, 2026
  5. added
    patches-welcomeThe core team will accept patches for this feature, but is not planning to implement it themselves.
    on Jan 9, 2026
  6. star-ga commented on Jan 10, 2026

    @star-ga

    Can confirm. This one keeps biting us too — .PNG/.TGA committed on Windows/macOS, then Linux doesn’t match *.png/*.tga and you get pointer files.

    I can write a docs PR with the bracket glob workaround (*.[pP][nN][gG], etc). OK?

  7. chrisd8088 commented on Jan 12, 2026

    @chrisd8088
    Member

    I can write a docs PR with the bracket glob workaround (*.[pP][nN][gG], etc). OK?

    That would be much appreciated, thank you!

  8. chrisd8088 commented on Jan 12, 2026

    @chrisd8088
    Member

    Regarding my other suggestions, it occurs to me that instead of trying to detect possible oversights due to case-sensitivity issues in the git lfs checkout or git lfs pull commands, the git lfs fsck command might be better suited to that activity, since users have no particular performance expectations of that command, and do expect it to make a thorough analysis of the current repository state.

    As well, if a -i/--ignore-case option were added to the git lfs track command, when that option was not provided and the user did not explicitly provide a mixed-case pattern, and the command was running on a case-insensitive filesystem, the command could output a message suggesting the use of the --ignore-option option if the user intended to share their repository with developers on case-sensitive filesystems.

    (Note that to detect a case-insensitive filesystem, I would recommend not simply checking the operating system type, since both APFS and NTFS can be configured in case-sensitive modes. Instead, we would want to perform a check similar to the one Git implements, where we would try to open the .git/config file as .git/CoNfIG and assume case-insensitivity if that succeeds.)

  9. star-ga commented on Jan 16, 2026

    @star-ga

    Building on the workarounds already discussed in this thread (e.g., adding duplicate uppercase patterns like *.PNG alongside *.png) and broader community practice, the core issue is that .gitattributes pattern matching is case-sensitive (consistent with Git’s index semantics). On macOS/Windows, users commonly commit assets with uppercase extensions (.PNG, .TGA, etc.), but on case-sensitive filesystems (Linux CI/build servers), patterns like *.png won't match .PNG, so Git LFS may leave pointer files instead of smudging content (e.g., Unity pipelines break).

    While duplicating patterns can work, bracket-glob patterns (e.g., *.[pP][nN][gG]) are more robust and portable: they are pure Git-supported syntax, avoid duplication, and behave consistently across all filesystems.

    For example:

    Current (breaks on Linux when files are .PNG):

    *.png filter=lfs diff=lfs merge=lfs -text
    

    Bracket-glob (works everywhere):

    *.[pP][nN][gG] filter=lfs diff=lfs merge=lfs -text
    

    This could be addressed incrementally without changing existing behavior or adding overhead to hot paths (pull/checkout):

    1. Docs (low risk / immediate): formalize the portable bracket-glob recommendation, e.g. *.[pP][nN][gG], *.[tT][gG][aA], with a short cross-platform note in git-lfs-faq(7) under Tracking and Migration.
    2. git lfs track --ignore-case convenience option: git lfs track --ignore-case "*.png" writes the bracket glob in .gitattributes (pure Git syntax; no new LFS matching logic; consider for a major version to avoid compatibility issues).
    3. Diagnostics in git lfs fsck: opt-in checks suitable for CI such as case-collisions and “risky patterns” (*.png but .PNG present), with flags like --json and --strict.
    4. Case-insensitive filesystem detection: probe-based (like Git’s core.ignoreCase detection) rather than OS guessing — resolve the gitdir, then test opening the config file with alternate case.

    Happy to submit a small docs PR first to git-lfs-faq(7) (addresses part of #6187) if that helps move things forward.

  10. chrisd8088 commented on Jan 17, 2026

    @chrisd8088
    Member

    Thanks for summarizing the discussion in this issue, @cputer. I think you've identified all the salient points and ideas!

  11. doctorpangloss commented on Jan 19, 2026

    @doctorpangloss
    Author

    okay, i think another POV is, if i write git clone with a default lfs configuration / a valid one from the POV of git lfs, and it succeeds with error code 0, but my directory is populated with pointer files that also do not get checked out with git lfs pull (or whatever), i hate to use chatgpt turns of phrase here haha, but: that's a serious bug. it's not a small bug or a documentation fix.

    the reason i ticketed this is because it affects backups. the whole point of git is that git clone does what it says it does or fails. this is a really bad failure. it has to be fixed in git too, or git lfs has to become part of git, or i don't know - there has to be a configuration that will raise an error in the scenario i reported, and it probably has to be the default configuration

  12. chrisd8088 commented on Jan 20, 2026

    @chrisd8088
    Member

    I understand your concerns, @doctorpangloss, even if I don't fully share them.

    We've described already some of the historical and contemporary limitations under which the Git LFS project operates, so I won't repeat them again here.

    I would urge you to contact the Git mailing list to discuss how Git applies its attribute patterns in case-insensitive filesystems, since that's at the root of this issue. That would also be the right place to open a discussion as to whether Git would ever absorb the Git LFS project, but I suspect you'll be advised that the Git project is not interested in doing so.

    the reason i ticketed this is because it affects backups. the whole point of git is that git clone does what it says it does or fails

    A plain git clone command is certainly a reasonable way to download a regular Git repository's history and contents for backup purposes. However, it's important to note that a git clone operation effectively performs both git fetch and git checkout operations, and while the former may download the full history of a repository, the latter creates a working tree from a single commit on a single branch. Because Git LFS is invoked during the checkout phase, not the fetch phase, it can only download the objects for that single commit.

    To make a backup copy of a repository and all of the related Git LFS objects, you need to run a command like git lfs fetch --all after cloning. This is especially true if you've made a bare clone with git clone --bare, since then no checkout phase is performed at all.

    The git lfs fetch --all command will retrieve Git LFS objects for pointers even if those pointers don't match any filter=lfs Git attribute patterns, unlike git lfs pull and git lfs checkout. Therefore Git LFS objects for files which don't match any patterns due to case-sensitivity differences between filesystems will still be fetched. We can illustrate this difference:

    Details
    $ git init src
    $ cd src
    $ git config --local core.ignoreCase true
    
    $ git lfs track "*.bin"
    $ echo foo >FOO.BIN
    $ git add FOO.BIN .gitattributes
    $ git commit -m FOO
    
    $ echo bar >bar.bin
    $ git add bar.bin
    $ git rm FOO.BIN
    $ git commit -m bar
    
    $ cd ..
    $ git clone src dst
    $ cd dst
    $ git config --local core.ignoreCase false
    $ git lfs ls-files
    7d865e959b * bar.bin
    
    $ git checkout HEAD^
    $ git lfs ls-files
    b5bb9d8014 - FOO.BIN
    
    $ git lfs pull
    $ git lfs ls-files
    b5bb9d8014 - FOO.BIN
    $ find .git/lfs/objects -type f
    .git/lfs/objects/7d/86/7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730
    
    $ git lfs fetch --all
    $ find .git/lfs/objects -type f
    .git/lfs/objects/b5/bb/b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
    .git/lfs/objects/7d/86/7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730
    

    If you then include the .git/lfs/objects directories in your archive of the repository, you would have a backup which contains all the Git LFS objects as well as the Git contents.

    Now if you wanted a git clone operation to automatically download all related Git LFS objects, you'd need to advocate for Git to introduce a post-fetch hook (a topic which has come up in the discussion of PR #6171), so that Git LFS could be invoked even after a bare clone and would always know the full history of everything that had just been fetched.

    However, not all users would view such an automatic download of Git LFS objects as a feature. For a repository with many large Git LFS objects throughout its history, a git clone operation may still be fast and cheap, but fetching all the related Git LFS objects, including those which are no longer referenced by any pointers in the tips of any branches, could be slow and expensive.

    The conversation in #5969 also touched on some of these points, in case that's helpful.

  13. added a commit that references this issue on Jan 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementpatches-welcomeThe core team will accept patches for this feature, but is not planning to implement it themselves.

    Type

    No type

    Projects

    • Status
      Optional Enhancements

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions