In software development, version control is absolutely critical. Without a solid version control system, you’re just undermining all the hard work that goes into any development project.
At XSolve, we’ve been using Git for version control for several years now.
Having tested it in a variety of scenarios, including back-end and mobile apps, and thanks to our close collaboration with clients, we’ve developed our own best practice ways of working with Git.
In this post, we’ll show you the standard Git-Flows we’ve created and now use as standard.
On the internet, you can find various methods of working with Git. In most of them, there are three basic branches containing particular code versions:
- master branch: including stable branches or, depending on the contract with the client, branches published for acceptance tests;
- develop branch: this might also be called the integration branch; new and completed functionalities created during sprints are added here;
- feature branch: including the code for new functionalities or those that are still being built; this sometimes also includes completed functionalities which have not yet been assigned to an edition.
Below, you can see one of the most popular Git-Flows (this is actually our sample working method)
Git is not everything
Of course, working with Git is not enough. The process must result in code of the highest quality, with the developed functionality fully meeting the restrictive requirements. We use the following six criteria for quality:
The code which is built during the creation of a new functionality is actually delivered based on the principles described in Robert C. Martin’s book. Additionally, for each technology, we’ve developed our own extensions which are based on Clean Code and SOLID principles, etc. What’s more, we’ve got our own internal wiki, where all JAVA, PHP, or mobile teams note down their own Clean Code practices developed over the years.
Definition of Done
All developer teams have their own Definition of Done (or DoD), which decides whether a newly created functionality has been built the right way or not. Of course, depending on the nature of the project, these definitions may differ. For example, it’s obvious that Android or iOS apps which do not connect to the internet will not need server deployment but rather testing on selected mobile phones. In some projects, acceptance tests on the part of the client are an important aspect, as well as fulfilling the conditions of code quality metrics, e.g. the minimum percentage of particular test coverage for the whole application.
Every functionality completed by a developer must be reviewed and evaluated by other developers. No code change can be introduced without being verified by another developer.
Each functionality has to be tested by a tester.
Practically every app created at XSolve contains automated tests. They frequently identify errors which cannot be detected during manual tests. The project and the client themselves determine how many automated tests need to be implemented.
Every project which communicates with external systems needs integration testing. Various teams, depending on the technology, use software to simulate external systems.
How it looks in theory
In the Java team, we’ve developed our versions of the three branch types:
- master – containing the app version which is presented to the client and then uploaded to the production server;
- develop – the branch containing new functionalities which have not been uploaded to the production server;
- feature branch – in Java, we don’t use the feature or hotfix prefixes, instead we name this branch after the JIRA task, e.g. iso23; each feature branch is merged to the develop branch.
All versions of the application are tagged with a unique version number once they have been uploaded to the production server.
Some tasks are highly complicated, requiring several people to work on them at the same time. Let’s imagine we have to create a big indivisible functionality. Adam takes care of the database, Peter takes the server, and Anna is in charge of the front-end. The whole is treated as one large story. In this situation, we create a new branch which we call a sync branch. Out of this branch, Adam, Peter, and Anna will build their own small branches and they will add their changes to it. After completing the whole work, everything is added to the main develop branch. Before that happens, though, the develop branch code must be accepted by the sync code. If any conflict occurs, it must be solved by the sync branch. Finally, the whole project can be merged into the develop branch.
How it looks in practice
Assuming you’ve already created your repository, now you need to create the branches. In our process, we start with only the master branch. The first step is to create the develop branch and then switch to it at once.
$ git checkout -b develop
Switched to a new branch ‘develop’
At the level of the develop branch, we now create a new feature branch, called feature-123, for the proj project.
$ git checkout -b proj/feature-123
Switched to a new branch ‘proj/feature-123’
Now, as an example, let’s add a test file: test.txt.
$ git status
On branch proj/feature-123
(use “git add <file>…” to include in what will be committed)
nothing added to commit but untracked files present (use “git add” to track)
Next, ask Git to add the new file to the tracked list…
$ git add test.txt
…and then commit the newly created file to the local repository.
$ git commit -m ‘[feature-123] This is a test file’
[proj/feature-123 a299e93] [feature-123] This is a test file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 test.txt
Supposing that the new file fulfills the Definition of Done and we’ve completed the work in this branch. Before creating a new branch in the GitHub repository, we merge it with the develop branch.
$ git merge develop
All changes in the feature-123 branch have an up-to-date version of the develop branch. If any conflicts occur now, they must be solved by the feature branch. Now that the functionality is complete, we send it to the GitHub repository.
$ git push origin proj/feature-123
Counting objects: 12, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 284 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local objects.
* [new branch] proj/feature-123 -> proj/feature-123
Finally, we insert a Pull Request for another developer to verify the code. This step is realized in the web interface at github.com.
If the code has been successfully verified by another developer, the change is added to the develop branch.
We’re constantly improving our skills and thanks to using Git to adopt various approaches to our work, we’re analyzing and implementing better and better solutions. It’s not enough just to use Git or branches based on the best practices, the solutions must be ideal for each individual project.
Here at XSolve, we would really encourage you to employ the Definition of Done, Code Review, and a series of manual and automated tests run on continuous integration tools. by taking measures like this, you will ensure that the software you’re creating is of the highest quality.