Quick answer: Use Git with LFS for small-to-mid indie teams and Perforce for large studios with massive asset libraries. Track all binary files through LFS, use file locking to prevent merge conflicts on assets, and establish clear ownership conventions for asset directories.
Game repositories are fundamentally different from typical software repositories. A web application might be a few hundred megabytes of source code. A game repository can easily reach tens or hundreds of gigabytes once you include textures, 3D models, audio files, cutscenes, and compiled assets. Standard Git was not designed for this. Without the right version control strategy, your team will face slow clones, impossible merges on binary files, and a repository that grows uncontrollably with every asset revision.
Git with LFS vs. Perforce
The two dominant version control systems for game development are Git (with Large File Storage) and Perforce (Helix Core). Each has distinct strengths depending on your team size and workflow.
Git with LFS extends Git to handle large binary files by storing them on a separate server and keeping only lightweight pointers in the repository. This keeps Git operations fast while supporting files of any size. Git LFS is free, integrates with GitHub and GitLab, and uses the same Git workflow your programmers already know. The tradeoff is that LFS has limited file locking support, clone times can still be slow for very large repositories, and artists unfamiliar with Git may struggle with the command line.
Perforce was built for large binary repositories from the start. It supports exclusive file checkout (preventing merge conflicts by design), streams-based branching that handles massive repositories efficiently, and a visual client (P4V) that artists find more approachable than Git GUIs. Perforce is the industry standard at large studios and integrates natively with Unreal Engine. The downsides are cost (free for up to 5 users, paid beyond that), self-hosting complexity, and a workflow model that differs significantly from Git.
Configuring Git LFS for Games
If you choose Git with LFS, proper configuration is critical. Create a .gitattributes file in your repository root that tracks all binary asset types:
# Textures
*.png filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
# 3D Models
*.fbx filter=lfs diff=lfs merge=lfs -text
*.blend filter=lfs diff=lfs merge=lfs -text
*.gltf filter=lfs diff=lfs merge=lfs -text
*.glb filter=lfs diff=lfs merge=lfs -text
# Audio
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
# Engine-specific
*.uasset filter=lfs diff=lfs merge=lfs -text
*.umap filter=lfs diff=lfs merge=lfs -text
*.import filter=lfs diff=lfs merge=lfs -text
Use git lfs lock before editing binary files to prevent other team members from making conflicting changes. Set up a pre-commit hook that warns when someone modifies an unlocked binary file. This is not as seamless as Perforce’s exclusive checkout, but it provides a safety net.
Branching Strategies for Game Projects
Game development branching differs from software branching because binary assets cannot be merged. A branching strategy that works well for code — like feature branches with pull requests — becomes painful when two artists modify the same texture on different branches.
For most indie teams, a simplified trunk-based workflow works best. Keep a single main branch that always represents the current state of the game. Artists commit directly to main (with file locking). Programmers can use short-lived feature branches for code changes, merging frequently. Avoid long-lived branches that diverge significantly from main, as the merge pain increases exponentially with time.
For teams that need parallel development streams — for example, maintaining a live service build while developing the next content update — use a two-branch model with main (current release) and develop (next update). Establish clear rules about which branch receives which types of changes, and merge from main into develop regularly to prevent drift.
Managing Repository Size
Even with LFS, game repositories grow over time as assets are revised. A texture that goes through 20 iterations stores 20 versions on the LFS server. For large projects, this can add up to hundreds of gigabytes of historical data.
Strategies for managing repository size include pruning unused LFS objects periodically, using shallow clones for CI/CD pipelines that do not need full history, and archiving old branches that are no longer active. If your repository exceeds what Git LFS handles comfortably, consider splitting assets into a separate repository or switching to Perforce for the asset portion while keeping code in Git.
Document your repository structure and conventions in a README that every team member reads on day one. Consistent file naming, directory organization, and commit message formats prevent the chaos that large game repositories tend toward without discipline.
Understanding the issue
Asset pipelines transform source content into runtime data. Each stage can lose information, change behavior, or introduce platform-specific variations. Bugs at this layer are often invisible until the cooked build runs.
Operational practices like this one tend to be most valuable when adopted before they're obviously needed. Studios that wait until a crisis to implement quality controls find themselves implementing under pressure, with less time to design well and more pressure to ship features. The practice ends up shaped by the crisis rather than by what would have worked best.
Why this matters
Process bugs are slower to surface than code bugs because they don't fail loudly. A team that handles bug reports poorly accumulates a backlog quietly; a team with the wrong triage taxonomy slowly loses the signal to noise ratio in their tracker. The cost compounds without being visible until something else exposes it.
The practice described here has both an obvious benefit (the one in the title) and several non-obvious ones. Teams that adopt it usually notice the obvious benefit first; the non-obvious benefits surface over time as the practice composes with other team habits. This is part of why adoption is hard - the upfront benefit isn't always commensurate with the upfront cost, but the long-term return is.
Putting it into practice
Measuring whether this practice is working requires honest data, not aspirational metrics. Pick a number that actually moves when the practice is followed (cycle time, fix rate, error count) and not one that moves with general activity (total commits, total bugs filed). The first kind tells you the practice is working; the second kind just tells you the team is busy.
Adopting a practice without measurement is faith-based engineering. Measurement makes it data-driven. The first metric you pick will be wrong; that's fine. Use it for a quarter, see what it actually tells you, refine. The third or fourth iteration of the metric is when it starts to be useful.
Adapting to your context
Adapt this practice to your studio's specific constraints. The shape that works for a 5-person team isn't the same shape that works for a 50-person team. The principle stays; the tooling and cadence change. Pick the variation that matches your scale.
Tailor this practice to your context rather than copying verbatim from another team's implementation. What's appropriate for a multiplayer-focused studio differs from what's appropriate for a narrative-focused one. The principles transfer; the specifics don't.
Long-term maintenance
The cost of operational changes is mostly the discipline to maintain them, not the engineering to set them up. The initial setup is a sprint; the ongoing review is a permanent meeting cadence. Plan for the meeting cadence; the setup pays for itself in a quarter.
The hardest part of operational changes isn't the change - it's the ongoing maintenance. Build the maintenance into existing rhythms: a quarterly retrospective, a monthly review, a weekly check. The cadence matters because human attention drifts; structure replaces willpower with habit.
Throughput considerations
Measure the throughput cost of new practices honestly. If you add a step to triage, that step has a per-bug cost. The cost is acceptable when the practice surfaces signal worth the cost; otherwise it becomes friction.
How to start
Before changing how your team works, gather baseline data on the current state. Without baselines, you can't tell whether your change made things better, worse, or simply different. Even rough measurements - 'we close about 20 bugs per week, sev-1 takes about 3 days' - are valuable as starting points for comparison.
Pilot the change with a single team or a single feature before rolling it out broadly. The pilot teaches you what implementation details actually matter; the broad rollout applies what you learned. Skipping the pilot means you discover the gotchas during the rollout, which is too late to redesign the practice.
Supporting tooling
Integrating this practice with existing tooling reduces friction. If your team uses Slack for communication, Jira for tracking, and CI for verification, the practice should plug into those tools rather than asking the team to adopt yet another. The lowest-cost variant is usually the one that doesn't introduce new tools.
When evaluating tools to support this practice, prefer ones that integrate with what your team already uses. A purpose-built tool may have better features, but adoption depends on the team using it consistently. The integrated tool that's used 95% of the time usually beats the best-in-class tool that's used 60% of the time.
Adoption pitfalls
Adoption pitfalls vary by team. Small teams struggle with overhead; large teams struggle with consistency; distributed teams struggle with communication. Anticipate the pitfall most likely to affect your team and design around it from the start.
Watch for the pattern where the practice 'almost' works - everyone says they're following it, but the metrics don't move. This is the most common failure mode: surface compliance without underlying behavior change. The fix isn't more documentation; it's making the practice's effect visible through tooling or rituals.
Communicating the change
When cross-team coordination is needed, name the owner explicitly. Practices without ownership decay; practices with a named owner persist as long as the owner stays engaged. Plan for ownership transitions in the same way you plan for code ownership transitions.
Communicating the practice externally - to candidates, to other studios, to the broader industry - reinforces it internally. Teams that talk publicly about how they work tend to do that work better. The act of explaining clarifies the practice for the team, and the external audience holds the team accountable to the public version.
Lock your binaries, keep your branches short, and never commit a 500MB PSD to regular Git.