This post is part of a series:
In the previous post, we have seen how to create commits. And in this post, we are going to see how to work with branches in Git.
Creating a new Branch
Let’s say we want to keep developing our Tic-Tac-Toe game and we want to create the logic for the actual game (coding session 4 in “code_snippets.py”). Therefore, we want to create a new branch so that we don’t mess around with the stable version of our code in the master branch.
See slide 1
The actual command to create a new branch is simply “git branch”. And after that, we can specify the name of the branch. In this case we called it “dev”. So, this is our development branch.
If we run “git log” again, then we can see that we have now two branches. And they are both pointing at the most recent commit.
See slide 2
So, in order to make use of our new “dev” branch, we need to check it out.
See slide 3
By running the above command, we can switch from the “master” branch to the “dev” branch.
See slide 4
We can also verify that by looking at the name in the parentheses. Namely, it changed from “master” to “dev”. So, accordingly, “HEAD” is now pointing at “dev”.
See slide 5
And now, we are able to work on our code without messing with the stable version of “master”.
So, as said before, let’s now create the logic for the actual game. Therefore, let’s create a new file and let’s call it “game.py”. And here, we simply import the functions that we have created in “helper_functions.py”. And with those functions we can code the actual game itself.
See slide 6
So, let’s now test it out and see if it works.
See slide 7
And, as we can see, it is working. So, let’s now save our progress into a commit. Therefore, let’s first run “git status” again.
See slide 8
And as expected, “game.py” is listed as untracked. But additionally, a “__pycache__” folder is also listed. This was automatically created into our folder when we ran the “game.py” file.
See slide 9
What that folder is for exactly, is not really of interest to us. We only know that it is not necessary for our project. So accordingly, we would like Git to simply ignore this folder. Therefore, we create the so-called “.gitignore” file and list that folder in there.
See slide 10
And if we now run “git status” again, we can see that the “__pycache__” folder is not listed as untracked anymore.
See slide 11
Instead, “game.py” and “.gitignore” are listed. So, let’s add them to the staging area to create our commit.
See slide 12
Here, instead of listing the actual file names, I simply stated a “.” after “git add”. This is a shortcut and will simply add all the files in the working directory, that are untracked or modified, to the staging area.
See slide 13
So now, we are finally ready to create the commit.
See slide 14
And if we now run “git log” again, then we can see that our dev branch is one commit ahead of our “master” branch.
See slide 15
And now, let’s say that we are satisfied with the code for the game and we think that it is stable. So, accordingly we would like to update our master branch so that it includes the code for the game as well. So, we want to merge in the dev branch into master.
Therefore, we first need to checkout the “master” branch again.
See slide 16
If we now run “git log” again, then we can see that “HEAD” is pointing at “master”.
See slide 17
And since we have currently checked out “master”, our project is accordingly in the state of the “define determine_game_status()”-commit. Because of that, the “game.py” and “.gitignore” files aren’t present in the folder.
See slide 18
So now, let’s merge in the “dev” branch into the “master” branch.
See slide 19
The actual command is simply “git merge”. And after that, we specify which branch we want to merge into the currently checked out branch. So, in this case we want to merge “dev” into “master”.
So, let’s run this command:
See slide 20
As shown in the output, this type of merge was a fast-forward merge. And now, let’s run “git log” again.
See slide 21
And, as we can see, “master” is now at the same commit as “dev”. So accordingly, our folder now includes again the “game.py” and “.gitignore” files.
See slide 22
So now, we have updated our “master” branch to include the changes we have created in our development branch.
And referring back to the “3 Areas” from the first post of this tutorial, here is a visual summary of what we have done in this example:
See slides 23-29
Three-way Merge without a Merge Conflict
So now, let’s see another example of merging two branches (coding session 5 in “code_snippets.py”). Therefor, let’s say we look at our “helper_functions.py” file and we realize that there is some repetitive code in the “determine_game_status” function.
See slide 30
Namely, for each block where we check if there is a specific type of winner (horizontal, vertical or diagonal), we always create a variable called “player”. And after that, we check if all the elements of a particular list are equal to that “player” and we also check if the “player” is not equal to zero. So, since this code is basically always the same, we want to refactor it into its own function.
And like before, to not mess around with our main line, we want to do that on another branch. But this time, we are not going to use the “dev” branch. So, let’s delete that.
See slide 31
Instead, we are going to create a new branch called “all_the_same”. So, we use a so-called topic branch.
See slide 32
And, as you can see, here we used a different command than before to create the branch. Namely, by using the “-b” option of the “git checkout” command, we can create a new branch and immediately check it out. Since this is a very common process, Git created that short-cut. This way, we don’t have to run the two commands “git branch all_the_same” and “git checkout all_the_same” to achieve the same thing.
Okay, so now, let’s say we refactor the code as follows:
See slide 33
And then, we create a commit.
See slide 34
If we now run “git log”, then we can see that the “all_the_same” branch is one commit ahead of master.
See slide 35
So now, let’s say we are not entirely sure if we want merge in those changes into “master” or if we just keep the code as it was despite the repetitive parts. And then, let’s also say that we suddenly realize that we forgot to check for one specific game status, namely a draw.
This is something that we definitely need to add to the “determine_game_status” function. Therefore, we are going to make the changes directly to the “master” branch (side note: We could also make the changes on another topic branch. But for simplicity, we are going to do them directly on “master”).
So, let’s check out master.
See slide 36
And now, we are going to add the check for a draw (side note: since we are on branch master, the “all_the_same” function is gone and we have the repetitive code again).
See slide 37
And since we now also consider a draw, we also need to adjust the print statements in “game.py”.
See slide 38
So, let’s now check if the game detects a draw.
See slide 39
And, as we can see, it works. So, let’s commit the changes.
See slide 40
So now, we have created one new commit on “master” and one new commit on “all_the_same”. So, those two branches have now diverged. And we can see that by using another option of “git log”, namely “--graph”.
See slide 41
So now, let’s say we decide to merge in the changes of the “all_the_same” branch into “master”.
See slide 42
Looking at the graph of the commit history, we can see that, this time, the branches have diverged. So, the merge is going to be a three-way merge. Therefore, if we run the above command, Git automatically opens a new tab in the editor with a pre-written commit title.
See slide 43
Here, we can add a more detailed commit message if we want to. Then, we simply close the tab. And with that Git creates a new commit.
See slide 44
So, let’s run “git log” again
See slide 45
And, as we can see, a new commit was created that merged the two paths together. So now, we don’t need the “all_the_same” branch anymore. So, let’s delete it.
See slide 46
If we now run “git log” again, we can see that the branch is gone.
See slide 47
And that concludes the second example for merging branches. So, again referring back to the “3 Areas” from the first post of this tutorial, here is a visual summary of what we have done in this example:
See slides 48-54
Three-way Merge with Merge Conflict
So now, let’s look at a last example for merging branches (coding session 6 in “code_snippets.py”). Therefore, let’s say, while running the game, we realize that there is a bug. Namely, when a player doesn’t play a valid position, e.g. “6, 4” on a 3x3 board, we get an error and the game stops.
See slide 55
To solve this bug, let’s create a new topic-branch called “valid_move”.
See slide 56
Then, let’s say we start coding and write a couple of lines.
See slide 57
While doing that, we suddenly have an idea for a better name for the “change” function. Namely, since the player is actually making a move, we would like to rename it to “make_move”. And because this is only a small change which is quick and easy to do, we decide to halt the implementation of the check for the valid move and go back to “master” to change the name of the function.
But what do we now do about the progress that we have already made so far on the “valid_move” branch? Well, we can simply commit it and indicate somehow in the commit message that the work is still in progress.
See slide 58
And later on, when we come back to the “valid_move” branch, we can simply keep working on the code and then we amend that commit.
So now, let’s go back to “master”.
See slide 59
Then, we rename the “change” function in “helper_functions.py” and “game.py”.
See slides 60-61
And then, we commit that change.
See slide 62
And if we now run “git log”, then we can see that the paths have diverged again.
See slide 63
So, let’s now go back to the “valid_move” branch and keep working on the implementation of the check if a move by a player is valid or not.
See slide 64
Here, we can then finish up our code (and obviously the name of “make_move” is now “change” again since we only renamed it on the “master” branch).
See slide 65
So now, let’s check if it is working.
See slide 66
And, as we can see, it is working. So therefore, we now want to commit these changes. But, what we don’t want to do, is to make a new commit after the “**WIP**”-commit. Instead we want to alter that particular commit. And we can do that by using the “--amend” option of the “git commit” command.
See slide 67
If we now run “git log” again, we can see that the “**WIP**”-commit was replaced by our new commit.
See slide 68
So, this is how we can make use of the “--amend” option to temporarily save some changes that we haven’t finished yet. We can also just use it, for example, if we realize that we made a typo in our commit message. However, you can only use the “--amend” option for the most recent commit. So, you can’t amend any of the earlier commits. (side note: You can also use “git stash” to temporarily save some unfinished work. You can read more about it here).
Okay, so now let’s say that we are satisfied with the changes that we have made on this branch. And therefore, we want to merge them into our “master” branch.
See slide 69
But, as we can see, this time Git couldn’t automatically merge the branches because there is a merge conflict in “game.py”. So now, as Git states, we need to fix that conflict and then commit (the name in the parentheses has also changed from “master” to “master|MERGING”).
But, before we fix the merge conflict, let’s first see why the conflict happened in the first place.
See slide 70
And as one can see, we changed the same line on both branches, namely where the “game_board” variable is created. On the “master” branch we changed the name of the function. And on the “valid_move” branch we actually didn’t change the line itself, but we overall changed the code around it. And since Git can’t possibly know which lines from either of the branches we want to keep, delete or alter, it marked the place of the conflict for us in “game.py”. So, let’s have a look at that.
See slide 71
The way that Git marked the place of conflict is by using “<<<<<<<”, “=======” and “>>>>>>>” (side note: I’m using VS Code which additionally creates the green and blue highlighted background). So, if you have a larger file, you can simply search, for example, for a bunch of “smaller than” signs to quickly find the place where the conflict is.
So, what do these signs mean? Well, the equal signs simply separate the changes from the two branches.
Then, after the “<<<<<<<” signs it states “HEAD”. So, the lines from that until the “=======” signs are the lines from “HEAD”. And since “HEAD” is currently pointing at “master”, this means that those lines are from the “master” branch. So, this is the line where we simply changed the name of the “change” function to “make_move”.
And after the “>>>>>>>” signs it states “valid_move”. So, the lines from the “=======” signs until the “>>>>>>>” signs come from the “valid_move” branch where we added the check if a player’s move is valid or not.
So now, we have to manually resolve this conflict. Meaning, we have to decide which changes we want to implement into our code. And actually, we want to keep both changes, the renaming of the “change” function and the check for a valid move.
So, in order to do that, we can simply rename “change” to “make_move” in the “valid_move”-section. And then, we delete the one line from the “HEAD”-section. And lastly, we then also delete the “<<<<<<< HEAD”, “=======” and “>>>>>>> valid_move” lines.
See slide 72
This is what the code should look like if we want to include the changes from both branches.
So now, we can save the changes and commit them (side note: here we need to run “git commit” without the “-m” option).
See slide 73
This will open a new tab in the editor for the commit message.
See slide 74
And again, there is already a pre-written commit title. So, let’s keep it like that and close that tab to create the commit.
See slide 75
And if we now run “git log” again, we can see that a new commit was created and that the branches are merged. The name in the parentheses has also changed back from “master|MERGING” to just “master”.
See slide 76
So now, let’s delete the “valid_move” branch.
See slide 77
And that concludes the third example for merging branches. So, again referring back to the “3 Areas” from the first post of this tutorial, here is a visual summary of what we have done:
See slides 78-83
So now, have seen three different examples of merging branches, namely a fast-forward merge, a three-way merge without merge conflict and a three-way merge with merge conflict.
See slide 84
So, that’s how we can use branches with Git. And here are the commands that we have used so far.
See slide 85
And in the next post, we are going to cover how to work with a remote repository.