A close up of a text description on a computer screen, by Yancy Min on Unsplash Photo by Yancy Min on Unsplash

A Basic Git Workflow for Small Projects

We are a small team of engineers that work mainly on small projects or proof of concepts based on research results. Each project is generally small in scale, with one or two engineers assigned to it, and an engineer is often assigned to two or more projects at a time, sometimes collaborating with researchers or developers from other teams.

In the past, we have had problems with version control and how we track changes. Some projects have ended up as a mess of branches and merges all over the place, making it impossible to go back and follow its history or identify when changes were made. Others have not had enough structure to it, with most of the work done on the master branch with insufficient commits, causing problems when we need to identify where a bug was introduced and how to roll it back.

Because of this, we are trying to define a proper Git workflow to minimize these issues going forward. The goal is a workflow that isn’t over-complicated, as the projects are generally small, while still allowing collaboration and giving enough structure and a clean history to easily go back, find when changes were made, and roll back as needed.

There is not really anything special about our approach, and depending on team and project structure it will definitely have to be adapted. As with all choices, each decision come with downsides, but we are sharing it in case it can help others get some ideas of their own.

1. Create a branch off of master for each distinct feature or bug fix

This might seem obvious in many ways, but don’t work directly on master, and don’t add multiple features or fixes in one branch. Doing so makes it more difficult to identify what changes has been made and when.

Also, don’t create a branch off of another feature or bug fix branch to work on something related unless absolutely necessary. If it is related enough it can be kept in the same branch, and if it isn’t related enough it can be its own branch off of master. If absolutely necessary to split the branch, rebase them back together again when done.

That said, when two engineers are working on a branch in parallel, their work should be split into separate branches to avoid clashes. When done, they should be rebased back into one branch, as we don’t really need that history to be kept.

2. Test on the branch before merging it with other code

Depending on how much tests you have available, this might be more or less complicated to do, but at the very least some tests should be run for sanity checks on the code. It will be a lot more work to revert the changes if the issue is found after merging.

3. When done with a branch, perform an interactive rebase to clean it up

While developing, there are naturally changes back and forth, and there tends to be a lot of minor commits along the way. Cleaning it up with an interactive rebase helps increase the readability of the history, for example by combining minor commits into larger more descriptive ones and reordering to make the process easier to follow.

4. Merge branches into master, don’t rebase it

Leaving the branch in the history makes it stand out as a feature or bug fix, and the merge commit explains what happened in the master branch.

Make sure the merge commit message is succinct but descriptive of the changes. The guiding rule is that it should only be necessary to dig into the branch commits to understand how it was changed, not what was changed. Anyone should be able to understand the whole process of changes and added features by just following the commit line for Master.

5. Don’t be afraid of creating a pull request, even if just for requesting input

A pull request can serve two purposes: reviewing and approving a change before merging it into the codebase, or to get input on the code. The second purpose is severely underutilized, at least by us.

For most projects we don’t strictly require pull requests for approvals, but sometimes use them to get more eyes on the code if it is an especially complicated change. However, it can be highly valuable to just get input while a change in in progress as well, rather than single-handedly tackling it.

This is especially true when you have people working in different locations, but it can be value in all situations to allow people to look at it when the time suits them. While a pull request is active, several people can suggest and add changes to the same branch. Just make sure to clean everything up with an interactive rebase afterwards, as it can easily become disorganized.

6. Don’t be afraid of pushing work-in-progress branches to the upstream repository

Some engineers, me included, tend to hold their code close to their chests until they are happy with it and comfortable to share it with others. However, we all know that work-in-progress code rarely looks good, and we don’t need to be afraid of showing it.

Having it available upstream enables others to both get updates on changes that impacts their own work, or they might notice something and be able to give feedback.

7. Use the -‎-rebase option on your pre-push pulls

The default merges your changes with any upstream changes, which often causes a merge commit. Using the -‎-rebase option, like “git pull -‎-rebase origin master”, avoids this to create a cleaner history. You might want to change the git config by using “git config -‎-global branch.autosetuprebase always” to do this by default.

8. Once changes are merged and pushed upstream, do not rebase unless absolutely necessary

Once a change is done and upstream, other users might be working off of the same structure, so rebasing it will break the history from their perspective. This means that they will have to either recreate the same changes manually, or drop everything and restart from a clean copy.

This also means that when performing a pull request, perform any rebases before submitting the pull request. After submitting, other people will be interacting with your commits, so rebasing it will complicate it for them. Any changes to the branch while it is being reviewed should be done by merging, not rebasing. As mentioned above, after the changes are confirmed it should be rebased to clean up the commit history into a consistent branch.


References

https://www.atlassian.com/git/tutorials/comparing-workflows

https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow

https://git-scm.com/docs/git-pull

https://git-scm.com/docs/git-merge

https://git-scm.com/docs/git-rebase