Git is probably the first result people stumble onto when researching source control for their game development project.
As evidenced by my activity on this very website, I am very interested in Git. I have used it extensively both professionally as well as for personal projects including game projects. I have come to love its beautiful design, and hate its limitations. Because of those, I created GitCentral to overcome Git's shortcomings and create a better-suited source control workflow for game development.
This article is a good opportunity to examine the motivations behind creating GitCentral, and shed some light on why Git is both great, and difficult to use for game development.
If you're a programmer or aspire to become one, you need to learn Git now! Whether you want to participate in open-source projects, fork someone else's cool program, or just for your local hobby projects, Git is the tool you need. Git will be useful for you even in enterprise scenarios where you may use a different solution for the primary repository, and work on closed-source code. Many companies have started to use GitHub as a practical means to collaborate on proprietary code.
A complete list of features of Git can be found in the official documentation. Here we will focus on the major features that separate git from other source control solutions. We assume that all of them provide the most common source control features of versioning, collaboration, and data integrity.
Git was born out of the needs of the open-source community. These design intentions can be seen and felt in all the features and implementation of Git.
It uses a distributed model, which means there is no central server holding the source of truth. Instead, each developer holds an entire copy of the code and all the history. At their discretion, they may redistribute the code or its artifacts, or contribute their modifications to someone who does. Teams of maintainers curate popular repositories, and sometimes, a difference of needs or opinions leads to a fork which takes a life of its own.
In the most common form, the open-source distributed workflow can look like this:
Git uses a graph model to represent the history. Each change is stored as an object and a link to its parent. The beauty of this representation means that every change is potentially a branch. Git also provides a very powerful merge engine.
Those features encourage a liberal usage of branching and merging. This workflow is very practical for programmers and is a big driver behind Git's popularity. By comparison, branches and merges in older version control systems such as SVN were time-consuming and error-prone. They tended to be avoided or reserved for elite programmers. Not only the ability to branch and merge liberates programmers and allows them to experiment freely, it also encourages merging more often, which leads to smaller patches that are easier to review and maintain. The result is increased overall productivity, stability and maintainability.
Git is very fast at performing all the regular source control operations with a local repository. It's when working with a remote repository that its limits start to show. The implementation of the graph model requires the repository to be fully consistent with the revision from which the current changes are being built upon, in order to allow committing. In practice, this means before every commit, one must get all the updates from the remote repository. By contrast, other version control systems only require you to download the updates to the files you have changed. With Git you must download everything. Git is not particularly fast at file transfer either, but more on this later.
Git is primarily a command-line tool. This makes it harder to use for beginners and less technical users. Its model can be confusing and it can be hard to understand what is going on when something went wrong.
The steep learning curve has become somewhat of a meme, spawning many websites and tongue-in-cheek literature.
Git is a very powerful tool, but we should also recognize its flaws. I share the opinion of many that the commands and the user experience could be improved. Some commands such as checkout or reset perform far too many different actions which would have been better separated into unique verbs. Gitless is a notable attempt to provide a better user experience on top of Git, and this might be just enough for beginners to get into it.
Still, even with a lot of experience using Git and diving into the plumbing while working on GitCentral, I find myself often looking at the documentation, begin confused by the inconsistent semantics used, or facing undocumented behavior.
Git does not provide any Graphical User Interface. It is only a command-line tool. Here is an official list of third-party GUIs from the official website. However, they all seem to be lacking something when put to the test.
Personally, I have tried a couple of the major GUI tools and currently settled for SourceTree. Even then, I perform most of the actions using the command-line, which means the graphical tool has not provided me with a better alternative. You also have to add your own diff and merge tool. As I am used to Perforce in my professional life, I really miss some of the most advanced features which make me more efficient such as Time-Lapse View, though there is, in fact, a similar tool for Git. I'm sure some people are faster using only the command-line, but I am certainly less efficient and I would bet that the majority of developers are in the same category, especially the less experienced ones.
Having a graphical tool at your disposal to perform basic actions doesn't solve the learning curve entirely though. Git's underlying model and semantics are complex and hard to grasp for non-technical users. The best way to work with Git is through simple integrations, such as TortoiseGit which adds actions in the contextual menu of Windows Explorer. GitCentral does essentially the same from within Unreal Engine.
Overall, Git attracts for its features and its great model for programmers, but it is hindered by its complexity and user experience.
Game projects are rarely distributed efforts. More often than not, you want to collaborate with your entire team on a central repository. This goes against Git's model and creates very specific issues.
As we already mentioned, in order to share any change, you must first download all the latest updates from the central server. This is very sensible for code, as it forces you to test your changes with the latest version before committing. On game projects where large binary files may have been changed, this could mean waiting a very long time for the updates to download. If anyone pushed their work in the meantime, you have to do it all over again until you're fast enough to commit your changes. When the number of collaborators increases, it can become almost impossible to commit a change manually. This sort of "traffic jam" situation can be solved using a lot of extra tooling and automation, but it is an extra hassle that many other source control solutions don't have. Interestingly, open-source workflow generally does not suffer from this because relatively few maintainers work on the central repositories.
On top of this, you may not want to get all the latest changes yourself. The very latest may be unstable, which is a reality of most game projects. More importantly, it may require you to stop your work and start a long process of compiling and "baking" the new code and assets. With some game engines and enough changes to download, this could take hours, only to find yourself with a potentially unstable version of the game. The ability to push or pull one file at a time is therefore very important in game development scenarios. Of course, Git's approach is safer. Pushing only a few files without testing with the latest changes could create unexpected issues. However, depending on your iteration time, the tradeoff may be well worth it.
A more subtle issue is that the default function of git is to work against your local version of the repository. You will not see the changes made to a specific file on the central repository until you fetch the information and run specific commands. Since binary files cannot be merged, it is more important to know the status of a file when compared with its latest version on the remote repository, rather than the local one. When using Git traditionally, you will only see the conflict when you try to merge your changes with the remote changes, and it may already be too late at this point. File locking, as explained below, helps avoiding conflicts, but not entirely.
In essence, the workflow of Git does not match the collaboration model of the majority of game development teams. Game projects will find that they often have a better iteration time when using a centralized, file-based model. This is how many other version control solutions are implemented. Unfortunately achieving this workflow with Git is very complex and requires using many plumbing commands, as I did with GitCentral. You may be able to adapt to Git's distributed graph model, but the issues will start appearing when adding more people to the project, and the iteration time will be dramatically impacted.
Git's distributed model means that every developer has a copy of the entire repository and all of the history on their hard drive. As the project gets larger, this causes the repository to grow in size to the point where it is too large for the hard drives and too slow to download. Git was created to handle text files primarily, but in game projects, binary files will quickly grow a Git repository beyond reasonable limits.
To allow Git to handle binary files more gracefully, several extensions have been created. Git-LFS, backed by GitHub has emerged as the dominant solution for a couple of years now. Git-LFS is transparent and stores binary files remotely to avoid bloating the main Git repository and its history. This solves the size issue and doesn't create an additional layer of complexity for the user. However, Git-LFS is very slow when compared to other source control solutions. I could not find a good benchmark online, but it is very noticeable. It seems clear that the current design of Git-LFS and how it integrates with Git could be optimized for performance.
Git's scaling issues are well known and there are still many attempts to improve on it. In the future, those problems may be completely resolved, but as of this day, Git scales poorly with game projects especially when compared to some other solutions. Meanwhile, there are some workarounds.
File locking is the action of "locking" a specific file, which means no other user can modify it until the lock is released. This avoids conflicts, and is critical for game development where binary files cannot be merged.
Git-LFS provides a basic file locking implementation since the release of Git-LFS 2.0.0. Most of the hosting providers support Git-LFS and locking by now.
There are a few caveats to note:
- Only the files tracked by Git-LFS can be locked.
- Locking a file locks it for all the branches of the repository. This can prevent two people from working on the same file on different branches.
- The implementation of locking depends on your Git-LFS server, so your mileage may vary.
It is very important to note that two people may not work on separate branches at the same time. When working on a stabilization branch, say for a demo or a release, you may have to coordinate with others working on the next features in the master branch, and avoid using the lock at the same time. This can work with small teams, but the more people you add, the more this will slow you down and create issues. You may not anticipate it yet, but your source control workflow will get more complex over time, adding more branches for features, demos, releases and patches, and all those small problems at first will become worse.
In a game project, you often want to control who can access your files. You may even want some files to be read or written only by privileged users, for security or confidentiality reasons.
Because of its distributed nature, Git does not provide any access control out of the box. You must add it to your repository server. Luckily, virtually all the cloud hosting providers and self-hosted solutions offer private repositories and user management. However, I haven't found more fine-grained control such as per-directory or per-file permissions. You can often enforce additional rules, such as locking a branch, requiring a successful code review and automated tests before the change can be committed to the central repository. Generally, git offers less access control options than its competitors, but this is likely to improve in the future.
Of course, you can implement your dream workflow using git hooks, but this comes at a cost.
Git has access to the most comprehensive ecosystem of tools and integrations available. I did not attempt to make an exhaustive list as it would be too long and quickly outdated. Virtually every tool or technology you are likely to require integration with your source control will provide some level of support. Git is available for all desktop platforms used in game development (Windows, Mac, Linux)
The most notable integrations include:
- Game engines: Unreal has a Git integration by default, but GitCentral allows for a better workflow adapted to gamedev. If you decide to use Git and Unreal, then this will greatly improve your quality of life.
- Developer tools from IDEs like Visual Studio to DCC tools.
- Continuous integration tools such as Jenkins or the aptly named GitLab
- Productivity tools like Jira or Trello
- Communication apps (Slack, MS Teams...)
- Cloud hosting providers, these often provide extra tools on top of hosting the repository, including many of the above
Note that some of these integrations only work with GitHub, which can limit you if you're not hosting your project there.
There are also scripts to allow migrating to and from most version control solutions to Git. This can be useful to migrate, and if you ever need to replicate your history in another version control for some reason. Git-SVN is available out of the box. Perforce has various tools as well, but I'll get deeper into those when talking about Perforce in a future post.
Git is open-source and entirely free. However, there are costs to running a game development project with Git, as you still need to host your repository and its data in a shared location.
In a previous blog article, I compared the major Git hosting providers and their features. This should give you a good overview of the features and costs involved, and pointers to choose the best solution. Of course, you may host on your own hardware, which comes with its own time and financial costs.
Nonetheless, there are no license fees, which makes Git one of the cheapest and by far the most accessible source control solution for game development.
Git is here to stay and you will have to learn to work with it, whether you want it or not.
Thanks to its popularity, you have access to the biggest ecosystem of integrations and interoperability. You also have plenty of great cloud hosting options for your repository and your productivity tools.
Despite these great features, Git is not very well suited for game development. Its distributed model does not match the typical centralized workflow and does not scale very well: the larger your project gets or the more collaborators you add, the more you will run into issues. On top of this, it is one of the hardest source control solutions to learn. If your project has less technical people such as artists or designers, you will likely spend a lot of time helping them understand Git, and fix many mistakes made along the way. Git can be good enough for small-sized teams comprised of fairly technical people, and where the pace of change is slow enough to not create too many conflicts with binary files. As your project scales, you will be looking for a better solution.
On my personal game projects, I quickly encountered issues with Git's model and user experience. I decided to create GitCentral to solve them. It has since allowed me to collaborate in a convenient way with Git and Unreal Engine. Even so, GitCentral is best used on smaller projects, since the Git-LFS backend does not scale so well when your game project gets inevitably larger. In retrospect, I should have extracted the logic of GitCentral into a library from which to create several tools, such as a Windows Explorer integration, or allowing to integrate it in Unity and other common game development tools. Perhaps this will be a future project if there is demand.
In my professional life, when starting Darewise, we evaluated many options and finally opted for Perforce, which is the game industry standard, and what I have used in all of my previous positions. In the next article, I will show you what motivated this decision.