Git Tutorial #4
Branches and Merging
Branches! They've been mentioned in every single tutorial so far, but we still don't know exactly what they are. We will finally be learning about them today, and since we grabbed a GUI program in Part #3, working with them should be a breeze.
In fact, I think I can keep the theory at a minimum and straight up show you branches in action. For now, let's simply remind ourselves what we know so far: We know that branches can be used to develop features separately from the main code, and that every repository has at least one branch - the default main or master branch.
But to show you anything, we'll need a new branch, first. They can be created under the Current branch top tab.
Simply click the New branch button, which will prompt you to input the new branch's name and notify you where the branch is created from. Branches do not start from scratch, they continue from a point in another branch. For us, this point is "First GUI Commit." on the main branch.
With a new branch which I've called sidebranch created, GHD switches to it automatically, putting its name as Current branch. New branches do not typically have any changes, so we're left with the familiar No local changes message.
Let's do some changes. I've created a new text file yet again, this time called branch_file.txt. It will be essential to our demonstration.
With the file ticked to be staged, we can create a new commit like we normally would. Notice how the blue button now reads Commit to sidebranch.
And with that, here is what example_repository currently looks like.
Here we're about to finally see branches in action.
After clicking on the Current branch tab once more and selecting main as our branch, branch_file.txt is gone.
Why? Because repository has the state according to the branch we're on - it is very similar as checking out to a commit hash, like we did in Part #2.
Now, let's say that we've successfully developed a feature in a different branch, and we would like to insert it into the main workspace, the main branch. Enter merging.
Merging is the process of moving changes of one branch into a different branch. This is commonly done using a merge commit, a commit that looks like any other and simply records the changes caused by the merging process.
To start, we once again follow the Current branch tab, and Choose a branch to merge into main.
We select sidebranch. Bottom notification shows that this merge will consist of just one commit, that being "branch_file.txt created.".
And that's it, the branches have been merged - it shows a confirmation message that reads "Successfully merged sidebranch into main" that I've been too slow to screengrab, but don't worry, we'll have one more chance to see it (spoiler, I was prepared that time).
Both branches now point to the same commit. If we change sidebranch further and try to merge it again, the merge will happen from here, not the commit from which the branch was created, meaning the "branch_file.txt created." commit (which has been merged in already) will not be part of that merge.
If we take a look inside the folder, we will now find branch_file.txt present, and in the background in GHD, the updated History tab can be seen.
You might've noticed that there isn't actually any merge commit in the history, just the "branch_file.txt created." commit from sidebranch. Surprised me too - I thought a merge commit would always be created, but it's not.
However, I'm sure it's not created because the changes are compatible. In other words, there are no conflicts that we would have to solve.
Merge conflicts happen when the changes being merged are incompatible. This occurs when a file is changed in both branches, and Git is unable to decide what should be included in the final merged version - the version from the original branch, the version from the merged branch, or some combination of both.
We will now intentionally get into a situation like this, so I can show you what merge conflicts look like and how to solve them. Let's start off by creating another branch, fittingly named anotherbranch.
Here is the window that prompts you for the branch name! I've described it when creating sidebranch, but haven't shown an image.
With anotherbranch created and automatically switched to, I will now modify branch_file.txt and commit it. New text mentions that the change was done inside this branch, and commit holds the message of "Change in anotherbranch."
Switching back to the main branch, I will now open branch_file.txt yet again. In the main branch it still has the original message, and I will modify it here as well, this time to mention the main branch.
And commit it, of course. "Change in main." is the message.
We now go ahead and try to execute a merge of anotherbranch into main. However this time, there is not a green tick at the bottom, but a warning exclamation point.
It mentions 1 conflicted file, and we will have to resolve this conflict to be able to finish the merge.
When we click the blue button, we will be presented with a window that shows all the conflicted files.
There are multiple options available to us to solve conflicts, already mentioned earlier on. The default Open in editor option isn't available to us, because a default editor has to be set in GHD's Options under Advanced, which I haven't done. So, what about other options?
The straightforward ones are Use the modified file from [place], which will simply keep the version of the file from the selected branch. If we chose from main, branch_file.txt will contain the "This change was done when on the main branch." line after the merge. If from anotherbranch, the file will have the "A change we have done on anotherbranch." line.
If we want to customize the merge, for example if we want to keep both changes, we will have to specify it through the Open with default program option, basically the equivalent of Open in editor.
This will open a temporary version of the file, which we can modify to determine what we want the result to look like. Here's how the file looks when it opens:
Here, every changed segment of the file will be highlighted, starting with <<<<<<< HEAD (where HEAD represents the branch we're merging into) and ending with >>>>>>> anotherbranch (or any other name of the branch being merged.
The segment is split by a ======= dividing line. Area between HEAD and the dividing line shows what the changes look line in the branch being merged. Area between the dividing line and the [branch name] shows the changes in [branch name]'s version of the file.
To solve the merge, we have to replace every segment with what we want the result to be. I want to keep both lines of text, so I'll edit it as such, and save the file.
After the file is saved, we can go back to GHD, and we will be met with a pleasant surprise.
A green tick, the good kind.
With No conflicts remaining, we can Continue merge, which will finish the merging process.
Yay, I was fast enough to grab the confirmation message that time! "Successfully merged anotherbranch into main".
If we click over to the History tab, we will be able to do a final revision of our repository so far. I think it looks pretty cool!
Going in the list bottom to top, that being oldest to newest, here are all the commits inside the main branch listed:
- Our very first commit!, created in Part #1
- (Our second commit. would be here, had we not reset it during Part #2)
- First GUI commit., from Part #3
- branch_file.txt created., commit that has been merged into main from sidebranch
- Change in anotherbranch., done in anotherbranch but present in main's history because of a merge
- Change in main. follows, being only two minutes younger than Change in anotherbranch.
- Merge branch 'anothebranch', the merge commit resulted from merging anotherbranch into main
And that's all for this part of Git Tutorials. Time for the usual summary of what we have gone over:
- What branches are
- How to create new branches and switch between them
- How merging works and what are merge commits
- What merge conflicts inside conflict files are
- How to resolve merge conflicts
A pretty short summary for a very long tutorial. As aforementioned, Part #3 and this tutorial were originally on one absurdly long page, and I've decided to split the two.
Next time, there will hopefully be no more splittage, as we start working with GitHub and uploading our repository to the internet!