OK, so you know how to set up your Git repo (Part 1), and you know how to use it (Part 2). This
final part will go over the workflow I use with my Unity projects & how to collaborate with other members of your team. Part 4 will show how to reuse code or other frameworks, and part 5 how to quickly start a new project with all your favourite settings & packages already installed.
Uni-Git Flow, an outline
Git (and other version control systems) have the idea of a ‘workflow’, a set of recommended guidelines for how to use Git in a team environment. Things like how branches should be arranged and how changes are shared and approved. Mine (which I hope to never call ‘Uni-Git Flow’ ever again), is very similar, but more specific to smaller game development teams.
The basic idea is to have a single master branch, one development branch per user, and feature branches when needed. The master branch (the default branch on a new repo) is used like it sounds. It holds the ‘official’ version of the project, releases should only be built from it, and new users will always branch from it. The master branch is never worked on directly, so developers instead work on their own personal userName_develop branch and merge their changes back into master as they reach milestones. Feature branches can be created by developers off their own develop branch so they can work on specific features separately. These will need to be merged back into their develop branch before being added to master.
This diagram may help explain. I start a new project on master. Before I make any changes, I checkout a new branch(1), eddie_develop, and do some work on it. After a commit, I decide I want my changes added to master, so I merge eddie_develop into it(2). (Note: in a team environment, you’ll want the team to review and approve these merges before they go through. More on this later). Another team member, Betty, can checkout her own betty_develop branch at any time. Again, when she wants her changes added to master, she would merge betty_develop into master(3). Now, If I want to work on the latest code, with Betty’s changes, I merge from master back into eddie_develop(4). You’ll want to do this before adding any changes back into master, to ensure that your changes still work with the latest changes from the rest of the team.
So, how do you make this work? (Git experts can probably skip ahead) First, each user will need to checkout their own develop branch:
Clone the project if you’re not yet a member. Then, while on the master branch (you will be by default), click Branch in the toolbar. Enter the name of your new branch (userName_develop), and click Create Branch. The branch will be made, and you’ll be switched to it automatically. To switch between branches, double click on one in the left hand list. SourceTree will warn you if there are un-committed changes on the current branch before changing to another (and deleting the changes).
Simply use the command git checkout -b userName_develop to create a new branch called userName_develop and switch to it. The -b flag makes sure the branch is created before switching to it (checking it out). At any time you can use the command git branch to list the current branches.
Merging changes from master
On your own personal develop branch, you can work as normal. Commit changes, revert, etc, etc. But what happens when a teammate has added a whole bunch of bug fixes to master, and you want to use them too? You need to merge master into your own develop branch.
Merging is something that is just so much easier in a GUI like SourceTree. First, you need to update your local master from the master in your remote repo. Switch to the master branch and click Pull. In the pane that slides down, make sure the remote branch to pull from is master, then click OK. If there have been any changes on the remote, they will now be in your local master, too. You can check whether any changes on master need to be merged from SourceTree’s ‘Log’ view. If the tip of the master branch is separate from your develop branch, then it’s “ahead”.
The following picture shows that master (and betty_develop) has “-bug fixes”, that are not yet in my own branch, eddie_develop. Note: the “1 ahead” on the master tip shows that my local master is ahead of the remote master by one commit, this shouldn’t happen if you follow the rules of Uni-Git. I haven’t (for demo purposes!). If you see “1 behind”, it means your master is behind the remote, so make sure you pulled properly!
To start the merge process, I make sure I’m on my own branch, eddie_develop, then click the Merge button in the toolbar. In the pane that slides down, I choose the commit (the tip of master I want to merge into my current branch, and click OK.
In many cases, this is it. A new ‘merge’ commit will be created on the tip of your develop branch, and a line will connect it to master to show that they have converged. However, you may get a popup warning you that there were conflicts in the merge. This is totally normal, and just means that Git couldn’t figure out how to combine the differing changes across both branches. In most cases, when different files or even different parts of the same file are changed, Git will resolve these for you. Sometimes it just needs some hints about which changes are the ‘correct’ ones before it can finish merging.
If you go back to SourceTree’s ‘Working Tree’ view, it will list all the files that are set to be committed as the merge. Any files with conflicts will be marked with a exclamation mark. By right-clicking them and looking at Resolve Conflicts, you can see the different options you have for fixing the issues. Resolve Using ‘Mine’ will just take the changes that are on your develop branch for that conflict and ignore master. Similarly, Resolve Using ‘Theirs’, will take master‘s changes and throw away yours. You’re also free to edit the file and fix the conflicts yourself. In this case just make your changes and save the file. You’d then choose Mark Resolved, to tell SourceTree that the file it has is ‘correct’. Choosing Launch External Merge tool will launch your systems own ‘diff’ program to help resolve manually (OSX’s FileMerge diff tool is pictured). Saving the file in a diff program and quitting will automatically mark it as resolved.
No matter how you resolve the conflicts and get rid of all the warning icons, you need to finish the merge by making the final merge commit. Click the Commit button as normal, and the commit pane will slide down. SourceTree will have handily filled out a commit message for you, and also made a fileName.orig file for each that had a conflict. These hold the original version of the file in case you resolved something you shouldn’t have. Click Commit to finish this awful business.
Yes, you can merge with the command line. No, it isn’t as pretty. First, make sure your master is up to date with the remote master branch. To do this, git checkout master, to switch to the master, then git pull. This will fetch any changes on master from the remote, then merge them into your local master. Switch back to your own branch with git checkout userName_develop. To check whether you need to merge, you can use the command git branch –no-merged, which will list all branches that have changes not yet implemented in your current branch. If master is on the list, use git merge master to merge the changes from that branch onto your own.
Hopefully, it will do the merge and just tell you that it made a merge commit. Done. However, as above, Git sometimes needs help deciding how to merge if there are conflicting changes in the same place of the same file.
If you need to resolve conflicts, use git status to see which files are still unresolved. A file with conflicting changes will be listed as unmerged, (or both added if a new file in both commits is named the same)
You need to open each of these files and merge the changes manually. Git will helpfully add some metadata to mark the conflicts in each file. Each conflict takes the form:
You need to choose which changes to leave, or how to combine them, then remove the meta data, as in my example. (Note: this meta data is still there when you merge using SourceTree, but it or an external diff program will interpret and hide these lines)
As with SourceTree, you can also use a diff program such as Apple’s FileMerge, see this Git tutorial for details. No matter how you resolve, you need to finish the merge by making a merge commit. To mark files as resolved, use git add as for a normal commit. After all files have been added, use git commit. Git will draft a commit message for you and remind you that it was a merge, but feel free to edit if you like.
Merging your changes into master
You may have noticed that I cheated a little in the last section. Merging changes from master into your develop branch is well and good, but how did those changes make it into master in the first place? The answer is both simpler and more complicated than when merging in the other direction. It’s easier, since master is never worked on directly, so there will never be any conflicts (well, as long as everyone follows the rules). It’s harder, because merging bad code into master can/will cause issues for the rest of your team. Luckily, Bitbucket comes to the rescue!
So lets say you have some amazing changes that you want to make available to the rest of your team. Before you do anything, MERGE FROM MASTER INTO YOUR OWN BRANCH!!!!!!! This makes sure that if the rest of your team has made any changes that would conflict with your own, you can catch them out and resolve them. So follow the instructions above and make sure your develop branch has merged with the latest master.
By merging from master, you’ve also ensured that your branch will merge with the remote master without any conflicts. If the merge was complicated, you should do a final test run to make sure nothing broke during the merge, but otherwise you’re ready to merge your develop branch back into the remote master, and make it available to the rest of the team
Technically, you’re now free to merge your develop into your local master, and push the changes to the remote master. But in a team environment this isn’t so polite. To make it easier for teams to approve changes that affect everyone, Bitbucket has a feature called a ‘Pull Request’. By making a pull request you’re proposing a merge. Team members have a chance to review the changes, and approve/deny each one. Once everyone has agreed that your changes are OK, you can go ahead and do the actual merge.
First, you need to make sure your local develop branch is pushed to the remote. Otherwise, Bitbucket won’t know your latest changes (Push in SourceTree, or git push on the command line). Then, go to Bitbucket and navigate to your repo. You can checkout how your branches link to each other on the Commits tab. On mine, you can see that the blue master branch hasn’t got any of the changes from the green eddie_develop branch. It also shows that master merges directly into the tip of eddie_develop, which means that there won’t be any conflicts when merging back into master.
To create the pull request, click Pull Request on the top right. In this window, select the source branch (your develop branch) and the destination (master). You can & should fill in a title and description, so your teammates can see at a glance what has changed in this update. Scroll down to see what changes are included in this pull request. Once you’re happy, click Create Pull Request, making sure that “Close Branch” is un-ticked. Request done!
The following screen is what the rest of your team sees when they want to check on your request. It lists all the changed files, then details the parts that have actually changed (red is the old version, green is the new). Team members can add comments, request changes, and ultimately approve or deny the request.
Once everyone’s happy with the changes, you (or a team leader) can accept the pull request into master by clicking the Merge button at the top right. A final confirmation will popup, again make sure that “Close Branch” is un-ticked, then click Merge. All done! To check, you can again go the commits tab, which will show your master (pictured now in blue) with a merge going into it from your develop branch.
And there you go, a robust workflow for working as a Unity dev power-team. I admit, it seems a little fiddly for constant work. But I promise if you follow these guidelines, it will save you massive headache later on. Depending on what stage in development you’re at, you probably don’t need to share your changes all that often. I’ve also given the most conservative option for code reviews. Internally, a small team can probably agree on what changes can be merged into master without having to wait for review, but this depends heavily on developer experience.
As an aside, some reasons that I prefer this method to some more generic workflows. First, this is mostly based off the feature-branch workflow, where each feature gets its own branch, and is merged and closed back into its develop thread. This method is perfectly valid, and if you’re comfortable with it, I’d go ahead and use that. Uni-Git is my attempt to simplify the workflow to its bare minimum, since otherwise no one will use it (especially important in an area where you’re probably working with at least a couple people who haven’t been programming for years) However, someone more comfortable is free to make their own feature branches off their own develop. For a similar reason, I recommend merging the latest master back into your own develop before making a pull request. This is so that conflicts are all worked out locally, and Bitbucket will be able to do the merge itself.
The most ‘unique’ rule is that each developer has their own branch. This is somewhat personal taste, but outside of major features or bugfixes, I tend to make a bunch of incremental changes in different areas as I go. This kind of work has far too much overhead to manage separately, so I find that keeping my local (and probably temporary) changes to myself keeps things a little saner.
One last thing regarding workflows. Git experts may notice I haven’t spent any time talking about rebasing, “keeping history clean”, or other fancy features. This is mostly because I couldn’t care less about that stuff, but also because you don’t need to know it to have Git work for you. Of course, it never hurts to learn more, so if you’re interested in Git-fu, there’s a HUGE amount of information floating round the web. Feel free to tailor this workflow to your own needs and skills, nothing suits everybody perfectly.
So that’s it for part 3. This took far longer than I expected, and I haven’t even gotten to the best parts yet (submodules and project forking). I’m sorry for being the blog version of the saw franchise (y’know, because there was one, then a trilogy, then a quintology…geddit? GETTTTTIT?), but hold on for the last couple bits of Git tips!