Git LFS and file locking for Unity, Unreal, and any project with multi-gigabyte assets.
git lfs install once per machine.git lfs lock <path> so teammates can't create merge conflicts on binary assets. Locks warn in the Git LFS client by default; a repository admin can switch on server-enforced locking in repo settings.Game projects live and die by binary assets — textures, meshes, audio, video, and packaged engine data — that are often many gigabytes in size and don't diff or merge like source code. Storing them directly in Git bloats every clone and makes history unusable. Git Large File Storage (LFS) replaces those large files in your repository with small text pointers, while the actual bytes are stored separately and streamed on demand.
UnityFreak hosts LFS objects on object storage with a zero-egress model, so LFS bandwidth is unmetered, subject to fair use — you only think about storage, not download costs. This matters most for Unity and Unreal projects, where a single asset (a video capture, a baked lighting cache, a set of source-art files) can easily exceed what a normal Git blob should hold.
If you created your repository from the Unity or Unreal Engine template, its .gitattributes already tracks the common binary extensions for that engine (textures, meshes, audio, video, and engine-specific formats like .uasset/.umap or .unitypackage). You don't need to configure tracking rules yourself — just install LFS locally and work as normal:
# one-time, per machine
git lfs install
# clone or work as usual
git clone <clone-url>
git add .
git commit -m "Add source art"
git pushOn push, any file matching a tracked pattern is uploaded to LFS storage automatically and only its pointer is stored in the Git history. If your project uses a binary type that isn't already tracked, add a line to .gitattributes, for example:
*.myext filter=lfs diff=lfs merge=lfs -textTracking a new pattern does not convert files that are already committed. A rule applies to files you add or change from then on. Matching files that are already in the repository history stay ordinary Git blobs and keep counting towards its size. Moving them into LFS means rewriting history (git lfs migrate import --include="*.myext") and force-pushing, which changes every commit from the first match onwards, so agree it with your team first. UnityFreak repeats this reminder wherever you add a pattern: when you edit or create a .gitattributes in the browser, and in the desktop app's LFS panel.
New to all of this? Creating a repository shows a short guided setup: your template and its LFS patterns, a one-click access token and a credentials check, your first commit, and a fresh clone that proves your large files are stored in LFS.
Binary assets can't be merged the way text can — if two people edit the same texture or scene at once, one person's work is lost. UnityFreak supports Git LFS file locking so teams can claim exclusive edit rights on a binary asset before working on it:
# claim a lock before editing
git lfs lock Assets/Art/Character.psd
# release it when you're done
git lfs unlock Assets/Art/Character.psdOnly extensions marked lockable in .gitattributes can be locked — the Unity and Unreal templates mark the usual binary-workflow types (art, audio, video, and engine packages) as lockable out of the box.
A repository admin can release someone else's lock with git lfs unlock --force — useful when a teammate forgets to unlock a file before going offline. Locked files show a lock badge next to their name in the repository's file browser, along with who holds the lock, so you can see at a glance what's currently claimed without running any commands. Repository admins can also review and force-unlock every active lock from the repository's Settings page.
Out of the box a lock is advisory: the Git LFS client tells you a file is claimed, but UnityFreak does not stop anyone from pushing a change to it. A repository admin can turn on enforced locking under Settings → LFS file locking. While it is on, the server refuses a change to a file that someone else has locked:
git push over HTTPS or SSH whose new commits change the file, or that moves an existing branch or tag backwards or sideways in the file's history (for example a force-push or reset back to an older version, even one a release tag still has). The push is rejected and the output names the locked files and who holds each lock. Catching up is allowed when the version of the file your branch ends up with descends from the version it had: git pull, rebasing onto the latest changes, or fast-forwarding a branch to newer content. It is refused when your branch has its own earlier commit that edited the file and the version you end up with does not descend from that commit (for example your branch edited the file before it was locked and the holder has since changed it on the main branch): ask the holder to unlock the file, then push. Deleting a branch or tag and creating it again at an older commit does not get around this either;In-browser conflict resolution only accepts files that are in conflict. It is refused when it gives a locked file content that matches neither branch, or when it takes the target branch's version of a locked file that undoes a newer edit your source branch carries; taking the target's version as a catch-up (what a plain git pull does) is allowed. A pull request merge is stricter than a push: it looks at the whole change that lands, so someone who does not hold the lock cannot merge a pull request that carries the holder's edit of the file (ask the holder to merge it, or to unlock the file). A rebase merge is checked commit by commit, like a push of the replayed commits would be, and so is a pull request from a fork that is merged: a commit that edits a locked file is refused even if a later commit reverts it. An auto-merge that a lock blocks stays armed and is checked again after a lock is released or the lock settings are relaxed (the pull requests a lock was last seen holding back, up to 200 per repository), every 10 minutes, once an hour for the 200 oldest armed pull requests of the repository, and when a new CI status or review arrives. A pull request that was blocked before blocked pull requests were recorded can therefore wait up to an hour after the unlock.
Whoever holds a lock can always change that file. Admins can narrow enforcement to path patterns such as *.psd or /Assets/Art/; with no patterns, every lock is enforced. The settings page lists exactly which write paths are checked.
What enforcement does not do:
Hero.psd and hero.psd can be two files and two locks). But a change to a file whose name differs from a locked path only in the case of ASCII letters (Art/HERO.psd while Art/hero.psd is locked) counts as a change to the locked file, whether it comes in by a push, a web edit, a pull request merge, auto-merge, the merge queue or conflict resolution, because a checkout on such a file system would overwrite the locked file. Names that differ only in the case of non-ASCII letters, or in Unicode normalisation (an accented letter as one character or as a letter plus a combining mark), are not matched: lock a path exactly as it is committed./, no . or .. parts, no backslashes, no tab, newline, carriage-return or NUL characters, and nothing that is not valid UTF-8. Other paths are refused when you lock them. A lock protects exactly the file it names: locking a directory protects nothing (lock the files in it, or use enforced path patterns). A repository holds at most 10,000 locks and one person at most 2,000 of them.LFS storage is billed by plan, pooled across all repositories owned by the same account or organization. Bandwidth for LFS uploads and downloads is unmetered on every plan, subject to fair use.
What fair use means. There is no bandwidth charge and no daily download allowance, and ordinary team and CI work sits far inside the limits. Fair use exists to stop hot-linking and abuse of public repositories: if an account or network requests download links at a rate far beyond normal use, new links can be rate-limited (the response tells the Git LFS client how long to wait) or, after a review, restricted. A restriction stops new download links straight away; a link already issued keeps working until it expires, because the bytes come straight from object storage.
The default thresholds. They apply per signed-in account, or per network address for signed-out visitors (an IPv6 address counts as its whole /64 range, so everyone behind one shared address shares one budget). The windows roll over the last minute and the last hour; there is no calendar-day, calendar-month or per-plan allowance. The figures count download links requested, sized by the files they were issued for, not measured transfer.
| Who | Files per minute | Data per minute | Files per hour | Data per hour |
|---|---|---|---|---|
| Signed-in account | 30,000 | 256 GiB | 300,000 | 4 TiB |
| Signed-out visitor (per network address) | 3,000 | 32 GiB | 30,000 | 256 GiB |
These sit far above ordinary team and CI work. When a limit is reached, the response tells the Git LFS client how long to wait, and the wait ends as soon as enough of your earlier requests have aged out of the window. We may tune the thresholds as we learn from real use.
| Plan | LFS Storage | Bandwidth |
|---|---|---|
| Free | 2 GiB | Unmetered, subject to fair use |
| Pro | 25 GiB | Unmetered, subject to fair use |
| Team | 100 GiB, pooled across the org's repos | Unmetered, subject to fair use |
| Enterprise | Custom / unlimited | Unmetered, subject to fair use |
Adding storage. Storage past your plan is sold in blocks of 25 GiB at $2 per month ($24 per year) each, on any plan, from your billing settings. Bandwidth does not change what you pay: it is unmetered, subject to fair use, whatever the plan.
How the free allowance compares. The Free plan's 2 GiB of LFS storage is smaller than what some hosts include. For example, GitHub's documentation lists 10 GiB of LFS storage and 10 GiB of bandwidth a month in GitHub Free (read on 2026-10-24; source; other hosts and plans differ, and these terms can change). If a repository holds under that much and rarely moves, that free plan may suit you better. We have not changed our free allowance or our prices for this. What we offer differently is unmetered bandwidth, subject to fair use, and the asset workflows on this page: file locking, Unity and Unreal templates, and in-browser previews of images, audio, video and 3D models. We make no claim about our storage prices relative to other hosts.
Individual objects are limited to 5 GB each. If a push would put a repository over its plan's quota, the push is rejected with a quota-exceeded error before any objects are stored — upgrade your plan or remove unneeded LFS objects to free up space. Paid plans can also enable auto-purchase in their billing settings to automatically buy more storage the moment a push needs it, up to a cap you set, instead of being rejected. You can track a repository's current LFS usage against its quota from the repository's Settings page at any time.
When a repository is imported by URL from another host, the import brings over your commit history and LFS pointer files straight away. The LFS bytes live on the original host's LFS storage, so a background job that starts automatically after the import fetches them onto UnityFreak.
You can track the job under the repository's Settings page, LFS section. The job fetches every LFS object referenced by the imported history from the original source — including from a private source, using the credentials you gave at import time (kept encrypted for at most 6 hours) — and stores it on UnityFreak, so clones and checkouts work without reaching back to the original host.
If some objects cannot be copied, the repository reads Partial and says how many objects (and bytes) are missing, why, and which files, so you know before you switch over. An admin can then press "Retry LFS copy", supplying a fresh access token if the source needs one. Only a Complete repository is ready to be your source of truth.
The repository's Settings page (and its home page once the copy has finished) shows a migration readiness report: a pass or fail for the Git history, the LFS objects and the object and byte totals, plus the things an import cannot carry over, and the concrete next steps for anything that is not done. See Import a Repository.
Files tracked by LFS preview in the browser, on the file page and in commit, compare and pull request diffs: images (with a checkerboard behind transparency), audio, video (.mp4, .m4v and .webm, streamed from storage so even a large capture plays without downloading it first) and 3D models (.glb, .gltf, .obj, .fbx, up to 50 MB when tracked by LFS, 10 MiB when stored directly in Git). A preview that cannot load says "The preview couldn't be loaded" (or "This preview link expired while the page was open." when its download link ran out), with a button to try again and a link to download the file, rather than showing a broken player, and never claims the file does not exist.
3D models can be driven without a mouse: focus the preview, then use the arrow keys to rotate, plus and minus to zoom and 0 to reset, or use the buttons under it. A "Download instead" link is always visible, and the viewer stops drawing while it is scrolled out of view or the tab is in the background.
Multi-file models load too. A .gltf with its .bin buffers and textures, or an .obj with its .mtl library and textures, is previewed from the files in the same repository at the same commit, found by the paths written in the model (relative to the model file). To keep this safe, links to other websites, absolute paths and paths that climb out of the repository are refused with a clear message, and a model may pull in at most 64 files, 100 MB in total and textures up to 4096 by 4096 pixels. Each of those files is limited to 25 MB when it is tracked by LFS and to 10 MiB when it is stored directly in Git (larger ones are reported as too large to preview). Your access to the repository is checked for every one of those files, and nothing you are signed in with is ever sent to the storage service.
Compressed models are not previewed. A glTF or GLB that uses Draco, KTX2 (Basis) or meshopt compression needs decoders the in-browser preview does not include, so it says so and offers the download instead of showing an empty viewer.
Previews of LFS files use download links that are valid for 5 minutes. When a link runs out while the page is open (or partway through a longer video), the preview renews it by itself, up to 3 times, announces it to screen readers, and carries on from the same video position. If that does not work the preview shows "Reload preview"; pressing it fetches a fresh link in place, without reloading the page, losing your scroll position, or moving the 3D camera or the video you were watching. If you no longer have access to the file, it tells you so instead.