Version Control and Team Coding

Git Manual and Workflow

Danny August Ramaputra
HappyFresh Fleet Tracker

--

The FleetTracker project is being worked on by multiple contributors. A proper use of version control system is required to keep everything in track and manageable, we are using Git VCS. Hence, in this post, I will be sharing some useful and important Git commands that we will be using, also some guides on the workflow to be practiced.

Git?

Git is one of the most widely used VCS (version control system). It was first developed by Linus Torvalds, the creator of the Linux kernel. Its creation was really intended to ease the development of a project across multiple developers. With its nature of being CLI bases, it has a rather high learning curve, and here are some commands that you may need for using Git efficiently.

Useful Commands

git init

To first initialize a Git repository we can use init . This will initialize our project on the current directory when no argument is given, or on the given directory otherwise.

Usage:

git init

git clone

Or perhaps when we already have a project setup in a remote repository (e.g. GitHub, GitLab, BitBucket), we can simple clone them into our local repository, skipping the init command. And based on how we configure Git, it can be done either via SSH or HTTPS.

Usage:

git clone https://github.com/daystram/my-first-git-repo.git

git remote

When using Git, we are working on our local repository. Most of the time we will also be integrating our local repo with another repository being hosted somewhere remote, thus we call them remote repositories. There can be multiple remote repos, but the one we usually use it what is being referred (named) as origin.

Usage:

Adding a new origin remote.

git remote add origin https://github.com/user/project.git

Sometimes we want to change our remote repo URL.

git remote set-url origin https://github.com/user/newproject.git

git pull

When we have setup our remote repository, we can use pull to fetch and automatically merge changes in the remote repo into our local repository. Another similar command is fetch, with the only difference of it not automatically merging the changes, and keeping the remote changes in a separate branch. If we use clone, then the branches will have been set to track the associated remote branch, making it more convenient to pull changes.

Usage:

Pull changes in the master branch.

git pull origin master

Or if we have set the branch to track a remote branch we can not give arguments.

git pull

git push

This is the opposite of pull, here we are essentially integrating our local repository to the remote. It usually works with no problem, but if the current branch you want to push has another change in remote that you do not have in your local (usually doe to other people making changes in the remote), you must first do a pull.

Usage:

Push local changes in master branch to remote.

git push origin master

Push and also set it to track remote branch (upstream).

git push -u origin master

Push force, usually needed if we manipulate the commit history.

git push --force origin master

git branch

When working with Git, we usually work on separate branches for different feature, depends on the Git flow being agreed to be used in the team. When we make changes in these branches, the commits in each branches become independent, making is as if its a new environment. This makes it easy for multiple people to work together — by working on different branches.

Usage:

List currently available branches.

git branch

Create new branch, branching out from the current branch.

git branch new-branch-name

git checkout

When we want to move between branches, checkout is used. This is one the most commonly used command, especially when we are working in parallel on various branches. Note that you cannot change branches if you have uncommitted changes that are likely to conflict when changing branch, a solution is to commit them, or perhaps stash them.

Usage:

Checkout to another existing branch

git checkout another-branch

Checkout to a new branch (similar to git branch <branch_name>).

git checkout -b new_branch

git merge

After we are done making changes we merge the branches together. This process is done automatically by Git. Yet sometimes there are files being changed on both sides of the branch, this is when conflicts happen. If it does happen, the merging process is halted, and the user is let to choose which changes to keep by editing the marked conflicted files.

Usage:

Merging feature-branch to the current branch.

git merge feature-branch

git rebase

Another method to integrate changes is rebase. There is a specific use case when this is a better approach to use, let’s say we branch out from master into a new brach called new-feature. Then we created changes on our own branch, while another person have added their changes into master. And we need that person’s changes to be integrated into our branch. A simple approach to to merge master into our branch, but that would cause commit spaghetti, creating an unnecessary merge commit. By using rebase, in a sense we are moving the root of our branch to the end of the master branch, hence integrating the changes there into our branch. This is done by rewriting the commit history, hence is considered a destructive operation, use with caution.

Usage:

Rebase our branch with master as the base.

git reabase master

If any conflicts occur, the interactive guide will ease us to solve the problem.

And if we already have pushed our branch to the remote, and we made did a rebase, a force push is required.

git revert

You have made a change that you decide it was not needed, revert can be used. It will create another commit which will revert the changes back to the commit we specified. This is considered a safe operation as we are simply creating a commit to do the revert, not changing the commit history.

Usage:

Revert to the commit hash 45421146.

git revert 45421146

Revert to the last 4 commits.

git revert HEAD~4

git stash

Sometimes we create changes that we want to put aside for awhile, may that be to avoid conflicts when pulling changes, or to work on another part of the feature first, stash is the solution. Stash works like a stack, we push and pop changes into it. It can also sometimes be used to move the changes if we have made them on the wrong branch.

Usage:

Stash changes.

git stash push

Unstash and apply changes.

git stash pop

View current stash stack.

git stash list

git status

This is commonly used command that is very simple in practice, it shows the current changes made to the current branch.

Usage:

git status

git reset

At times we make mistakes, making commit we should have not made. To undo such mistakes, we can use reset. Be very careful, this is also a destructive operation. With reset, we can manipulate the commit history as needed.

Usage:

Reset to the most recent commit, leaving current changes.

git reset

Undo last 2 commits, move changes back to staging area.

git reset HEAD~1 --soft

Remove (delete!) last 2 commits.

git reset HEAD~1 --hard

Workflow

To achieve maximum productivity in using Git, a proper workflow is required. What we use in this project is very similar to the feature branch workflow, where different features are being developed separately in their own branches and the changes are being merged and propagated up from user story branch, then staging, then, master.

A slight difference that we implement in our team is the user story branch. We do not think it is efficient to have a user story branch as in most cases, there are many user stories which has the same code implementation, we can use a single branch for it. Due to this reason, we used this branch for the backlog item instead, making it more general and reduces confusing and repeated branches.

Branch Naming

There will be branches as follows:

  • master
  • staging
  • us-<backlog_number>_<backlog_name>
  • <developer_name>/<feature_name>

-*have decided to place feature branched in the developer’s branch -namespace, to make it easy in finding who’s working on which feature.

Merge Requests

Merging into upper branches, and change promotions are documented and done via GitLab merge requests. This allows other developers to view changes, review them, and comment further on it before merging it to upper branches. master, staging, and us-* branches are protected from developers, they are not allowed to push there right away, preventing mistakes and enforces the use of merge requests.

Resolving Conflicts

Conflicts occur when we attempt to merge two branches which both make changes on the same file. Most of the times, Git is smart enough to be able to merge them automatically, but when conflicts happen we need to resolve them manually, by choosing what to keep and what to change.

The most common case of conflicts is when we are merging feature branches into a single development branch. Multiple contributors may be modifying same files which results in the conflict. There are various ways to tackle this common issue.

Our team frequently rebase their feature branches to retrieve updates from the parent development branch. This will ensure that the changes within the parent branch will be integrated into their branch from the start, where if there are changes to the same file they’re working on, they can resolve the differences, thus avoid conflicts when merging later — this is why we rarely face merging conflicts. Such extensive example of frequent rebasing is on this MR https://gitlab.cs.ui.ac.id/it-project-kki/2019/happyfresh-fleet-tracker/merge_requests/3.

Rebasing from parent development branch

In cases when conflicts may occur is when they have forgotten to rebase, or when rebasing will cause too much of an effort to go through. Before we attempt to merge our completed feature branch into the parent development branch, they have to merge that parent into their feature branch. This will pull the changes and integrate it into their feature branch — avoids conflict errors when GitLab auto-merges them. An example of this is in this MR https://gitlab.cs.ui.ac.id/it-project-kki/2019/happyfresh-fleet-tracker/merge_requests/39. Note that there is a merge commit “Merge branch …” in the end, unlike rebasing which does not have this commit.

Merging from parent development branch

If there are no conflicts, then it all will be merged flawlessly. If there are conflicts, the merging process will pause, and let us fix the conflicting areas. The sections of the code which needs to be resolved usually has >>>>>>> , =======, and <<<<<<<. These are the ‘flags’ which denote the areas in conflict.

// ... code not in conflict
// ... code not in conflict
>>>>>>> HEAD
changes made in our current branch
// ... code in conflict, alternative 1
=======
changes from the source branch, e.g. origin/dev
// ... code in conflict, alternative 2
<<<<<<< origin/dev
// ... code not in conflict
// ... code not in conflict

Using a text editor, we can choose which alternative we are taking, or perhaps making further changes to integrate both alternatives. Make sure that these ‘flags’ are removed before completing the merge. If we are using proper IDE or text editors with VCS support, they usually offer faster methods to choose the alternatives while being laid out more interactively.

Once done with merging them, simply add them back to stage, git add the_file_name then simply git commit.

Cold- and Hot- Fixes

There can be some changes needed for a quick fix on the master or staging branch, this is where cold- and hot- fix comes in.

A coldfix is usually used then a user story merge into staging that is rejected by the client during sprint review. This is done by branching out from staging, reverting the user story (i.e. rollback) and merging it immediately back to staging.

A hotfix is usually used when a bug is found in the master branch. To quickly fix this production issue, a branch is made from master, fix is made, and merged back into master.

That’s It!

Wrapping it up, that is all that I can show you about common Git commands, and how we implement an efficient Git workflow in our group. The implementation is not perfect but we have tried our best to stick to the plan.

Thank you for reading!

--

--