How to Make Conflict-Free Git Trees? (Part 2)

April 9, 2015

In the first part we started exploring Git tree planting and spoke about how to make easy-to-use and elegant trees. In this article we’ll discover a cheat-list of rules and practices that we as an app development company use in our work. These tips will be useful to you regardless of what kind of Git branching strategy you’ll choose.

Versions’ names

Whatever strategy you use, when you make releases you add tags (at least, people say that it is worth it to do so). Tags contain the number of the current version of your product. This version should be brief, but contain the largest possible amount of information.

Of course, this is not a new problem, so there is a well known solution: semantic versioning. It is worth mentioning that these rules were written for API versioning. Including three segments in the version number is suggested:

<major version>.<minor version>.<patch version>
  • The major version increases when a new version of the API is not compatible with the old one (code that worked with API 1.x, very likely will not work with API 2.x).
  • The minor version increases when more big features are added to the program, but backward compatibility is maintained.
  • The patch version increases when corrections are published.

You may notice that these rules are not well suited for the case of mobile app development, so we changed them. We use the following scheme:

<major version>.<minor version>.<patch version>.<build number>-<buildtype>
  • The major version changes if the application architecture changes and it affects all the components in the app.
  • The minor version increases with the addition of new features.
  • The patch version is increased when you add some bug fixes or patches.
  • The build number is used to distinguish the assembles collected from the same code, but in a changed environment (for example, you changed the settings of your Jenkins tasks).
  • Finally, the build type is used to designate keys that are used in the assembly (debug, dogfood, release, etc.)

Rules for working with feature branches and history changing

Whatever strategy you choose, you will use feature branches. We have developed a small set of rules for working with them:

  • One feature branch should contain only one feature.
  • You should strive to make not very large branches (if the pull request contains more than 1000 lines of code, it is unlikely that someone will be very attentive to such changes).

In addition, commits should be as atomic as possible in feature branches – they should carry a single logical scope of changes.

An example of a poor log:

443e51f First 1.5 operations of 3-step process
ba08771 Second 1.5 operations of 3-step process
d58b327 Ololo I’m too lazy to make a good commit message for these changes
df8cf79 Fix for the 2nd operation of 3-step process

An example of a good log:

443e51f 3-step process
ba08771 Documentation for 3-step process

In order to make a readable and moderately detailed story, Git provides tools for history changing.

The first tool is amend – which changes the last commit. When you are just starting to work on the changes, you make the first commit (for example, interface, implementation of which will solve the problem).

Then you add the new changes, but instead of creating a new commit you execute command:

git commit —-amend

This command does not create a new commit, but changes the previous one and allows you to change the commit message.

This solution has one drawback – you lose a story within a single commit. In other matters, this disadvantage is not so critical, if you work in the IDE; usually they have a local story that best suits such small and recent changes.

Of course, when you are working on changes, you can see the system on a large scale and it is difficult to immediately identify a sufficiently large number of changes to commits. That is why Git will help you again if the story you received as a result does not satisfy you; you can use squash (documentation on it ). It is a tool to merge multiple commits into one.

Let’s say, when you are ready to do a pull request of your feature branch, you see its log:

ce6578a OAuth screen <– commit in the master
443e51f First 1.5 operations of 3-step process <– first commit in the feature branch
ba08771 Second 1.5 operations of 3-step process
d58b327 Ololo I’m too lazy to make a good commit message for these changes
df8cf79 Fix for 2nd operation of 3-step process

You do not like it and you realize that it needs a little change.

Then you find a commit from which you have branched off (for example, it was commit ce6578a OAuth screen) and enter the following command:

git rebase -i ce6578a

Git will open a text editor for you and you will see the following:

pick ce6578a OAuth screen
pick 443e51f Registration process with validation <– new commit message
squash ba08771 Second 1.5 operations of 3-step process
s d58b327 Ololo I’m too lazy to make a good commit message for these changes
s df8cf79 Fix for the 2nd operation of 3-step process

The same file contains a cheat sheet with the actions that you can perform with commit. Originally, the word “pick” is written in front of all the commits, which means: “use the commit without changes.” You can write next to the commit “squash” or just “s”. This means that you want to glue this commit to the previous one in the commit history.

Once all the necessary commits are tagged you save the file and close the text editor. However, Git re-opens the text editor, this time in order to enable you to edit the commit message of the commits that appeared after merging.

After you save and close the file, the log is as follows:

ce6578a OAuth screen
433f5af Registration process with validation

Protection of the master branch

But there is one problem with the history changing: if you have ever sent your commits to the server, the server after changing the story doesn’t get a new branch, because the very branch changed its structure and the server does not know how the old and the new structure can be merged into one. That is why you have to use a fly force. Using this flag tells Git something like this: “if any problems appear, please solve them in my favor – delete ‘disturbing’ commits and keep my local commits on the server.”

This is dangerous because there can be a situation in which your local master branch has become old (you worked on your feature for a couple of days) and at this point you:

git push -f

This command sends all of your local branches (which have relevant remote branches) to the server and the current master on the server will be overwritten by your older version. This is very dangerous!

The first thing to do to avoid a similar situation is to clearly specify the branch:

git push -f origin mybranch

In this case, you risk only your own branch, which is likely the one you are working with.

But human beings are fallible and sometimes you may forget to specify a particular branch. That is why we have to protect ourselves. Git comes to help us again. It includes so-called hooks–scripts requested before or after certain actions (commit, push, change history). These scripts are stored in any Git-repository folder .git/hooks. Initially this folder is filled with examples of similar scripts (files with names like *.sample).

In addition, the scripts themselves can perform actions, they can also influence the actions of Git. For example, if the script exits with non-zero code, the action that took place before script was called is canceled.

In order to protect the master from the force push we need to add hook, which is called when you try to make push (because it should be called pre-push.sh) and ends with a nonzero code if the flag force is used, and any changes affect the master branch. An example of such script can be found here.

And in aim, to add it to your repository, just make the following command curl at the root of your repository.

There is an opinion that the use of force flag on a regular basis (not as an exceptional case) is unnecessarily risky. Our team, when we choose between two evils–bad history or the risk from force push — we choose the latter one, but try as much as possible to protect themselves.

Commit messages

Accurately formed commit messages are very important to create a truly readable and easy-to-use history. In our current project, we adhere to the rules set out in the article.  It’s a wonderful article!

Here is a brief summary of the rules from this article (that we slightly modified):

  • Separate the subject from the body with a blank line.
  • Limit the subject line to 50 characters.
  • Capitalize the subject line.
  • Do not end the subject line with a period.
  • Wrap the body at 72 characters.

By obeying these rules, you receive commit messages as follows:

Commit message title

Optional commit message
multiline body

The names of branches

You should also pay attention to naming branches, as when a few people are working on a project, there can be many branches. We need to do it in a way that allows us to easily distinguish branches. The name of a branch should be brief and concise. We use the following pattern for the names of branches:

<team member name> / <feature or fix name>

There are such titles:

misha / iss123_auth_logic

If the branch corresponds to the task in the task-tracker, we add its number to the beginning of the name.

If there are two people who work as a team with the branch, we write both names:

mishsash / iss124_auth_animations

Test automation

In our projects, we try to stick to TDD, but the tests are worthless if you do not run them in time 🙂 The best time to launch them is before you merge them into a master. However, in practice, people often forget to make tests before a pull request (especially if minor changes are made). That is why the process should be automated.

We run tests automatically when we create a pull request. For CI, we use Jenkins, and store code in GitHub, that is why for automation we use plugin.

Emoticons

If you use GitHub, and you want to add some fun to your log, you can add… pictures! This practice was started by the guys that make GitHub Atom.

As a result, you will receive something like this:

3

In addition, it is not only fun, it seriously improves readability of the log. Anyway, you should develop your own set of rules about how to name commits according to their content.

Console

Although this is my personal opinion, I’ve come across the same idea several times on the Internet: Git should be used from the terminal. Git was created by people who literally live in the terminal, that is why it is best suited for terminal.

Although, if you do not have a sufficient number of commits in the Linux core, and you do not use vi as the main working tool, you can significantly simplify your life with the help of add-ons to your shell. So, for example, there are additions to zsh that make your console look like this:

4

In addition, over all Git tools the console is one of the most cross functional and popular tools and you will eventually realize it if you are constantly working with Git.