Last chapter, we discovered Issues and used them to plan our project. We also learned how to link our commits to issues, so that we can follow each change in our project. Our way of work was simple: choose an issue, make a commit that can resolve it, and push to GitHub. The issue was then resolved and closed. But this way of work is not very adapted in most real-world projects; the potential of screw-ups is too high.
What if you need more than one commit to resolve an issue? What if other team members pushed a commit that contained changes to the same files you were working on? How to make sure that the pushed commits really resolve the issue? All of these are part of the reasons why making direct changes to the project is not advised, even if you work alone.
As we said in the last chapter, closing an issue by keywords in the commit message is cool, but you should be very careful with it. Only you have seen your work, and it might not resolve the issue. or it might introduce new bugs in the project. That’s why it is better for someone else to review your code before accepting the changes.
It’s that part that we are going to talk about in this chapter. First, you will be introduced to the most common GitHub workflow (how most teams work on GitHub), and then we are going to learn about the concept of Branches.
But before we begin this chapter, here’s a little thing that you should always remember: “You will make mistakes. A lot of the time. So you must make sure to use as many safeguards as possible.” Let’s go!
GitHub workflow
In this section, we will talk about the most common way that developers use GitHub. Keep in mind that each team has its own way of doing things, but each of these ways of working is inspired by the basic workflow that we are going to present.
Remember the little fact about making mistakes? This omnipresent possibility of mistakes is why you need to follow this GitHub workflow, so even if mistakes happen, you isolate its repercussion in a controlled manner. Our way of work from the previous chapter was to commit everything directly to the main project, and this is very dangerous. The main project is most of the time the “production” line, the version that the clients see and use. So, this version must be very clean and should be always exploitable. If any error makes its way to the main version, the clients will experience bugs and it will disrupt every team member.
One way to resolve this issue is to create a copy of the main project and work on this clone. Each change you make to this copy will not affect the main project, so none of your mistakes can impact clients. And when you (and other people) are perfectly sure that the changes to made resolve the issue, you can reproduce those changes in the main version.
Those copies of the main project are called Branches, and the concept of reproducing changes into another branch is called Merging. You can make as many branches as you like, and you can trade commits between them. When you first create a repository, Git creates a new branch for you; it’s called “master.” Most developers put their main or production version in master and only recreate changes there when they are absolutely sure that it’s okay to do so.
Just like tree branches, Git branch can have many ramifications, meaning that you can even create new branches from branches other than master, even it’s difficult to maintain such architecture. Most of the time, you will create a branch when working on an issue and delete it after the issue was resolved.
To put all this into perspective, we are going to learn about the default or common GitHub workflow. As you know, everything should begin by an issue. We already covered this last chapter so you are already familiar with this. So, we are going to talk about each of the next steps of the workflow.
When you are going to resolve an issue by making code changes, you should first make a copy of the current working version of the project: create a new branch.
Then, as usual, you make your changes and commit the state of the project. You can make any number of commits as you need; it won’t affect the main branch. You can also push your commits to GitHub so your code can be seen.
Then, you link your branch to the master one, so others can compare the changes and review your code. This link is called a Pull Request: you are requesting that your commits be applied to the master branch.
Other team members can then review your code and make comments about it on GitHub. You then push more commits addressing those comments until all problems are solved.
If every party (developers, managers, testers, or clients) agrees that your changes are okay and resolve the issue at hand, the pull request is accepted. This means that every commit you made on your branch will be applied to the master branch. You can then delete the branch you created.
And that’s it! You might wonder how is it different from directly pushing in master. It’s very different because mistakes and omissions are caught before applying the changes to the production version; this means that the number of production bugs is reduced to a minimum. It also makes it possible for various members of your team to review to changes before they are applied, which is the standard way of work in most tech companies. Bundling the changes into one pull request also solve the problem about multiple people pushing commits solving different issues at the same time. It keeps the history log clean.
You might be tempted to open pull requests only when you feel that you are done with your work. Unless the work you did was very small and straightforward, don’t wait long before opening a PR. By working a PR early in your development, you can receive feedback before making too many changes. It is very useful for beginners especially because following the wrong path from the start will take a long time to correct and you would wish that you were told the correct way earlier. Opening a pull request doesn’t mean that the work is done; it just means that you are thinking about applying commits from a branch to another.
Note
As previously established, you can create branches from any branch and open pull requests to it. It’s not only reserved for the master.
As you can see, we can create branch from any branch in our project. Git created a branch called master for us at the initialization of the repository. We then can create more branches (e.g. a bugfix branch or a feature branch) to introduce changes in the master branch.
Branches
As we said earlier, branches are the main feature behind code reviews. You have to work on your own branch before publishing your work, so that it won’t be bothered by other people’s changes. Put simply, a branch is just your own independent copy of the project at a certain time. Let’s see how they work and let’s create and delete some.
The logic behind branches is simple: take the current state of the project and make a copy of it. In this copy, you can make your changes without impacting other people. You can use branches to have distinct channels of distributions or just to try new things with the project.
When creating a repository, you get a branch by default: master. When working on very small projects, this branch is enough; but most projects need more branches to get the best results. First, they need a production branch, where clients can get the last stable version of the software; this is the master branch. The production branch is only updated when the project is sure to be stable as this is the release branch. Then, there is the development branch, where all the progress is recorded and all the commits tested. You will mostly work on the development branch as it is where most of the fun is. Finally, there is the short-lived patching branches which you will create to hold your commits before merging them to the development branch. Those patching branches live and die with a pull request; you create one when you are solving an issue and delete it afterward.
Production branch, where you will release stable versions of your project
Development branch, where you will test your latest version
Patching branch, where you will work on your issues
Unless there is a VERY urgent major problem that needs solving immediately, you will never commit directly to the production or the development branch. To update those branches, you will use pull requests so that the changes will be reviewed and tested. There are some companies where every developer just commits directly to the development branch, but this is very counterintuitive because if a bug is discovered, they won’t know which commit introduced it. Also, it forces the developer to push “one-do-it-all” commits, which is an anti-pattern. Do-it-all commits are commits that try to resolve many issues at the same time, for example, a commit that fixes a bug and introduces a new feature at the same time. This practice is often caused by the laziness of developers as they don’t want to create a new branch for another issue. This creates very bad pull requests and makes it difficult to track the progress of the project. It also creates a big challenge for the testers as they don’t know which version is the stable one. It’s an all-around bad idea; don’t do it even with your small projects. It may seem tiring to create and delete branches all the time, but it is the best workflow when working with Git.
The one thing to remember about Git branches is that they just are simple references to commits; that’s why creating and deleting them is so fast. Remember when we talked about how Git stores its commits in chained links? Well, a branch is just a reference to one of those commits. A commit contains information about the author, the date, the snapshot, and, most importantly, the name of the previous commit. The name of the previous commit is called parent and every commit except the first one has at least a parent. Thus, each commit is linked to the previous one so that we can recreate the change history of the project.
For now, you only have the default branch called master and it references the last commit of your project. To create a new commit, Git checks where is the reference and uses the info in that commit to build the link between the new commit and the previous referenced one. So, each time you commit, the reference moves to the new commit and the cycle continues. Thus, a branch is just a reference to a commit that is designed to be the parent of the next one.
But how does Git know on which branch are we one? Well, it uses another reference called HEAD that references the current commit. If you are on a branch, HEAD references the last commit of that branch. But if you are checking out a previous version (like we did when we used “git checkout <commit_name>”), the HEAD references that commit, and you are in a state called “detached HEAD.”
Caution
Just like human bodies, never be in a state of “detached HEAD” if you can avoid it. It is a very dangerous situation to find oneself in.
For most situation, you can think of HEAD as the reference to the current branch, and every commit you create will use the last commit in that branch as a parent.
No parents: The very first commit
One parent: Normal commit in a branch
Multiple parents: A commit created by the merge of branches
Creating a branch
After you execute that command, you will notice that nothing has changed in your project. That’s because creating a branch is just about creating a reference to the last commit of the current branch and nothing else. To begin working with a branch, you have to switch to it.
Switching to another branch
You will notice that we still are on the master branch because we haven’t made anything other than creating a branch. Now let’s switch to it.
Note
Like when we navigated between versions, you can’t switch branches if you have uncommitted changed files. Commit before you move. Or use a technique called “stashing” that we will see in later chapters.
EXERCISE: CREATE A TESTING BRANCH
Go back to the master branch
Create a new branch named “testing”
Switch to the new branch
Tip
To immediately switch to a new branch after creating it, use the option “-b” with the git checkout command. For example, “git checkout -b testing” is the same as “git branch testing” and then “git checkout testing.”
Deleting a branch
You had fun creating the testing branch? Good. It’s time to delete it because we already have a testing branch: develop. That’s where we will merge our patching branches and all the testing will be done there.
You can delete a pushed branch, meaning a branch that is present on the remote repository, by checking “delete branch after PR merged” when creating a Pull Request. This will delete the remote branch but your local branches will be unchanged. You will have to delete your local branches manually.
You will see that the last commit name and the branch name is the same; this is because we haven’t made any commit in our branch. You will also see on the history log where the branches are originating from. In this example, the develop branch originates from the 80f145c commit; it’s the branch’s parent.
Merging branches
We talked a lot about merging branches in this chapter but we haven’t made a single merge. Let’s change that.
Let’s imagine that you want to improve the README file of the project by adding a few information. This task is already listed in our GitHub issues so no problem about that. The next step is to create a new branch from the development branch so we can merge them later. You have to create a new branch from the develop branch instead of the master because we won’t touch the master branch until everything is properly tested. If everything is clear and clean, we will merge the development branch into the master branch.
Perfect! Now we have a branch named “improve-readme-description” that originates from the develop branch. We like branches so much that we created a branch from a branch!
Tip
Use the option “--oneline” when using git log to get a prettier result.
As you can see in the figure, HEAD now points to the last commit of our new branch; it means that every commit we will create will have that as a parent. You will also notice that the master and develop branch didn’t change; that’s because we only worked on our newly created branch.
Let’s recheck the git log to have a clearer idea of what happened. You will get a similar result to mine after executing “git log --oneline” that is shown in Figure 11-10.
Congratulations on your first merge! It won’t be so easy next time (hint: merge conflicts, they appear when the same line of code has been modified in different commits)
Pushing a branch to remote
Branches are not only made for working locally, you can also publish them to the world by pushing them to the remote repository. For example, let’s push our development branch to GitHub so everyone can see our progress.
As you can see, there is a little difference in the result: it gave us a link to create a pull request, that is, ask for permission to reproduce the commits on develop to master. Take note of the link because we are going to learn about Pull Requests in the next chapter. ☺
It is all about branches for now. You now know how to create, merge, and delete them. And most importantly, you have a basic knowledge of the GitHub workflow: create a branch, work on that branch, and create a pull request.
Now, you may ask yourself: “But didn’t you promise us code reviews and pull requests? Did we even use the workflow?” You are absolutely right. We didn’t use the workflow because we used the direct approach: directly messing with the branches. In a real-world project, you won’t commit and push directly to the master or the development branch like we did earlier. Instead, you will use Pull Request to merge branches together. That way, your work can be reviewed by your coworkers before you can merge them to the development or master branch.
Summary
This chapter dealt with what makes Git a powerful tool for project management: branches. Branches are necessary in a fast-paced development as you will probably work on many issues at the same time. Keeping all those changes in the same place is a recipe for disaster. For example, you need to start in a clean environment to fix a bug or introduce a feature; trying to do both at the same time will seriously increase the risk of introducing more bugs.
The main takeaway of this chapter is the importance of using a workflow when developing with Git. And those workflows all use branches to separate the different types of work necessary for a clean issue resolution.
We’ve seen how to create, check out, and delete a branch. Now, let’s learn more about Pull Requests and Code Review, so we can propose changes in our master branch!