GitHub Desktop for Unity - Part 3

GitHub Desktop for Unity — Part 3: How to resolve merge conflicts

This article is a part of the series:
Using GitHub Desktop for Unity collaboration

In the second part of this series, we explore a very common problem that teams usually run into when collaborating on GitHub Desktop—merge conflicts. These can cause significant delay to your work, as you are forced to handle them when they occur before progressing; and it can take a fair bit of time and skill to solve them.

Resolving them incorrectly can also cause work progress to be lost.

Hence, in this article, we’ll be covering what they are, how you can avoid them, and how you can resolve them.

  1. How merge conflicts happen
    1. How project history is maintained
    2. Enter the merge conflict
  2. Fixing your merge conflicts
    1. Merging C# scripts
    2. Prefabs and Scene files
    3. Merging other files
    4. Discarding one of the versions
    5. Resolving deleted files
  3. Preventing merge conflicts
    1. Always remember to Fetch Origin before working
    2. Avoid working on the same files at the same time
    3. Communicate with your teammates
  4. Wrapping Up

1. How merge conflicts happen

To understand why merge conflicts happen, it is best to first understand how Git does its version control magic.

a. How project history is maintained

When you are working on a project in Git, all changes made to the project are arranged in a linear fashion.

Github's linear way of adding changes
Linearly means that the changes are arranged one after the other.

Hence, whenever you make changes to the project and push them onto GitHub, Git will try to add your changes to the end of the chain.

A diagram showing how the most recent commit is added to the end of the list of changes.
Your change will be added at the end of the chain when you commit and push.

However, keeping this chain linear is not always possible. You may run into scenarios where:

  1. You make changes to your existing copy of the project without remembering to do a Fetch origin (i.e. what Git calls making a pull), which fetches any updates that other team members have made to the project since the last time you accessed it.
A screenshot of the github desktop topbar with the fetch origin button highlighted.
The Fetch origin button.
  1. You have finished your latest set of changes, and are trying to push the project—only to find that someone else has pushed a new set of changes while you were working.
Github requires you to fetch and pull if any changes were made after you've committed.
Github requires you to fetch and pull if any changes were made after you’ve committed.

In such cases, Git will put the incoming changes before your own, as shown in the diagram below.

A diagram showing your changes being applied after the change you've pulled and getting merged with it.
The pulled changes will be applied first. After that, Github will try to merge both changes before you can push.

And if both the incoming changes and your own set of changes affect the same file in your project, Git will automatically merge them together. For example, these 2 scripts:

class Example {
    void Hi() { print("Hi"); }
}
class Example {
    void Bye() { print("Bye"); }
}

When merged, will become:

class Example {
    void Hi() { print("Hi"); }
    void Bye() { print("Bye"); }
}

b. Enter the merge conflict

Merge conflicts happen when Git is unable to automatically merge your files in different changesets, because both changesets modify the same section:

class Example {
    void Hi() { print("Hi"); }
}
class Example {
    void Hi() { print("Bye"); }
}

In such a case, the merge will include both versions of the code in conflict, with symbols delineating them:

class Example {
<<<<<<< HEAD
    void Hi() { print("Hi"); }
=======
    void Hi() { print("Bye"); }
>>>>>>> your-version
}

This will be done to all conflicting files, and GitHub Desktop will issue you a nice little warning after that. When this happens, your Unity project will become logjammed—you will be unable to run it, and your conflicting Scenes / Prefabs will not be accessible. This is because the extra <<<, >>> and === symbols introduced into the conflicting files break your scripts, Scenes and Prefabs.

Below is a video that documents this wonderful process:

The process of encountering a merge conflict. (Yes, it does a little bait-and-switch at first telling you everything’s resolved!)

2. Fixing your merge conflicts

When merge conflicts are found in your project, a pleasant popup like the one shown below appears, and you will have to resolve all of the merge conflicts in the listed files before you can continue working on your project, and you have no choice in this matter.

The popup that appears showing you the files with Merge Conflicts.
The popup that appears showing you the files with Merge Conflicts.

Fixing your merge conflicts entails open your conflicting files in a text or code editor, and picking 1 of the 2 conflicting options. Using the example above, it means picking one of the options below:

class Example {
<<<<<<< HEAD
    void Hi() { print("Hi"); }
=======
    void Hi() { print("Bye"); }
>>>>>>> your-version
}
class Example {
<<<<<<< HEAD
    void Hi() { print("Hi"); }
=======
    void Hi() { print("Bye"); }
>>>>>>> your-version
}

…or merging both options in a unique manner.

class Example {
<<<<<<< HEAD
    void Hi() { print("Hi"); print("Bye"); }
=======
    void Hi() { print("Bye"); }
>>>>>>> your-version
}

Depending on the file that is conflicting, you will have different merge options.

a. Merging C# scripts

If the conflicting files are C# scripts, you will have the option to open them using Visual Studio’s Merge Editor.

If you have any other supported code editor installed (e.g. Visual Studio Code, Notepad++), Github Desktop may not show Visual Studio as a merge option. In such cases, if you’d still like to use Visual Studio, you can go open up the conflicting script in Visual Studio and click “Open Merge Editor” on the header above.

The header showing the Open Merge Editor Text
Click ‘Open Merge Editor’ in the conflicting C# script to open the Merge Editor.

I recommend just sticking with the Merge Editor since it allows you to easily compare and pick the lines to merge.

After opening the conflicting script in Visual Studio, you’ll be presented with three windows:

The Visual Studio Merge Editor Interface
The Visual Studio Merge Editor interface.
  1. The left window shows what the code on the remote repository looks like (Remote)
  2. The right window shows the code that you previously tried to push (Local)
  3. The bottom window shows you what the merged code will look like (Result)

The only windows you’ll need to edit in are the Remote and Local ones. The Result window just shows you what the merged code will look like.

Lines that have conflicting code will have a checkbox at the side, which you can tick to keep that line of code to merge. You can either tick the boxes on the Remote, Local, or both sides depending on the code you’d like to keep. This will update the result window at the bottom so you’ll know what lines of code will end up in the merged script.

Click the checkboxes to keep/discard lines

Once you’re done comparing and picking the lines of code, check that the code in the result window is what you’d want to merge. If everything looks okay, you can click the “Accept Merge” button on the top to get the merged script, which will hopefully resolve the conflict.

b. Prefabs and Scene files

Merge conflicts in Prefabs and Scene files are harder to resolve as these files usually contain thousands of lines of text in a format known as YAML. The text stores information about all the GameObjects and their attached components in both your Prefabs and Scenes.

You can click the coloured text below to view the text contents of a prefab and a scene. We’ve put them in a dropdown for reasons you’ll see later…

To resolve these merge conflicts, you will be prompted to open up the conflicting Prefab or Scene file in a code editor (though using Notepad or TextEdit works just as well). Unlike Visual Studio’s Merge Editor though, you’ll need to manually remove the merge symbols yourself in your Scene or Prefab files.

Another option you can try is Unity’s built-in YAML merger, though it is not 100% guaranteed to properly merge any conflicting scenes/prefabs you may have.

Word of Warning

When fixing merge conflicts with Scenes and Prefabs, it is possible for the merge to break the files. This is because it is possible for the same GameObjects to have different IDs across different projects. Hence, when merging, you may select a changeset that has a different ID from what your Scene or Prefab needs. This can lead to Scenes with missing GameObjects or Prefabs, or Prefabs that do not show up.

c. Merging other files

If you have other kinds of files that conflict (e.g. ScriptableObject asset files), the process for resolving the conflict will be similar to resolving the conflict in Prefabs and Scenes.

d. Discarding one of the versions

Sometimes, instead of automatically merging the files, GitHub Desktop allows you to either choose one or the other to keep instead. If this happens, you can expand the dropdowns on the merge conflict popup and choose one of the two options:

The dropdown showing the different keeping options
  1. Use the modified file from main – Keep and push the file on your local repository (i.e. replace the file in the remote repository with your changes)
  2. Use the modified file from origin/main – Keep the file on the remote repository (i.e. your changes get discarded)

e. Resolving deleted files

It is also possible to run into merge conflicts because one changeset modifies a file that another changeset deletes. In such an instance, you will simply be prompted to either keep or remove the file.

The prompts where you can either keep or discard the file.
  1. Use the modified file from main – Push the file on your local repository (i.e. creates that file/folders on the remote repository)
  2. Do not include this file on origin/main – Does not push the file on your local repository to the remote repository (i.e. Discards the file you changed)

3. Preventing merge conflicts

Prevention is better than cure—merge conflicts can be a headache to deal with, so it’s best to not encounter them in the first place.

Here are a few ways to prevent merge conflicts from popping up.

a. Always remember to Fetch Origin before working

A screenshot of the github desktop topbar with the fetch origin button highlighted.
Before opening up your Unity project, remember to fetch (and pull if needed) from origin!

Since merge conflicts can be caused by forgetting to update your project before working on it, you should try to—y’know… never forgetting to do a Fetch origin before you work on your project.

Too many hours have been wasted on resolving merge conflicts, simply because people forget to click on Fetch origin!

b. Avoid working on the same files at the same time

Merge conflicts happen when we need to merge files, and this only happens when multiple team members are working on the same files at the same time. To avoid merge conflicts, we therefore have to avoid modifying the same files at the same time. This is especially important for Scene files and Prefabs, since they are more difficult to merge.

One simple practice you can consider to minimise the chance of members working on the same file is to assign people who need to work at the same time to work only on specific folders. For example, if team member A is assigned to work on the scripts in the UI folder, then no other teammate should modify files in that folder until A has committed his work, and everyone else has pulled his edits to it.

c. Make use of branches

If different team members are developing different features at the same time, and may need to modify the same files, you can have one or a few of them develop on different branches.

This is only applicable for development of features over a longer-term timeframe. For example, if a few team members are developing the game’s weapon system while the rest of the team are bug fixing the core combat system, it will be a good practice to develop the weapon system on a separate branch.

Once the weapon system is finished, you will do one single merge to combine it back into the primary branch. By organising things this way, you avoid having to deal with dozens of merge conflicts that the new weapon system may cause.

A diagram showing how branching and merging a branch works
Branches are useful in preventing merge conflicts, as those working on separate branches are unable to touch each other’s code.

d. Communicate with your teammates

Professional game development teams often use chatgroups like Slack or Discord to coordinate their work so that they avoid running into merge conflicts—there is nothing worse than grappling with a difficult merge conflict when you are crunching for a release date.

4. Wrapping Up

A successful merge conflict resolution
Hopefully you were able to see this popup!

Merge conflicts can range from minor inconveniences to major headaches. With proper communication between your teammates however, you should be able to collaborate together without ever running into a merge conflict.

If you’ve got any tips to share on how you can avoid merge conflicts, or are running into merge conflicts yourself and need help, feel free to comment down below! Thanks for reading!