Working with Git is easy. At the same time, it has enough depth and features to let you screw up your codebase at a considerable level. That's why the web is full of Git tutorials, blog posts and articles. That's also why a bunch of smart people have come up with certain Git workflows to avoid finding yourself lost in a sea of unmanageable changes. Let's dig into one of them.
As every decent Version Control System, Git has three core functionalities: store content, track changes to that content, and distribute the content and change history to possible collaborators. The way those changes are stored and organized is entirely up to the developer. This is going to be a very opinionated post about how to organize your Git workflow. I will explain how one these already existing workflow works, and introduce some small rules and changes on top of it. Said workflow is, obviosuly, Gitflow, created by Vincent Driessen.
From this point onwards some basic Git knowledge will be assumed, tasks like initializing a repository, commiting changes, branching... Should these concepts sound alien to you, I highly recommend you check out these links:
Core concepts by example (By Github):
https://guides.github.com/activities/hello-world/
Read about the most common Git commands. Kinda like a cheatsheet (By Bitbucket):
https://confluence.atlassian.com/bitbucketserver/basic-git-commands-776639767.html
This covers the basic fundamentals of how Git works. A bit of theory (By Git, actually):
https://git-scm.com/book/en/v2/Getting-Started-Git-Basics
Feel ready now? Perfect, let's continue!
Please bear in mind that I will not use a pure implementation of Gitflow, but rather one based on it that has been working pretty well for me and the teams I have been a part of. If you want to read about the actual Gitflow workflow you can find links at the end of the article.
Once you look at it hard and close enough you will realize that Gitflow is nothing more than a branching model for Git. It defines a set of rules about the kind of branches to set up and how the merges between them should work. The main idea behind it is to divide your branches into 2 main categories plus 3 supporting ones:
- master
- develop
- feature
- bugfix
- hotfix
The two main branches, master
and develop
, are intended to keep track of your project's history. The master
branch stores the release history, so it should only get updated once there are enough changes to justify a new release. The develop
branch is intended as an integration branch for all your feature
branches and to keep track of all the actual changes to the codebase.
The first step then is to create a complementary develop
branch from master
.
$ git init
Initialized repository in ~/code/my-project/.git/
$ git add -A && git commit -m "Initial commit"
...
$ git branch develop
$ git branch -a
develop
* master
TODO: add diagram
Since the two main branches are used just for history tracking, we need somewhere else to do the actual work. That's what the feature
branches are for. Every new feature to be developed in the project has to be worked on in a feature
branch, the work commited as often as possible, and once it is finished merged back into develop
. This forces the team to make every feature as independent from the others as possible.
Once you have enough feature
branches finished and merged into develop
to be considered a new stable point, or release, develop
should be merged into master
and that merge commit given a tag, this tag usually being a semver version. feature
branches should never interact directly with master
.
TODO: add diagram
Now, when you are working on a feature
branch, you should commit as much as logically possible, but, once you merge a feature back into develop
it's highly recommended to use the --squash
option: $ git merge --squash <feature-branch-name>
. This allows you to merge all changes from the feature
branch into develop
as a single commit. Doing so ensures a clean commit history on develop where every commit is related to a single feature. If you want a more detailed log in the develop branch you can ommit the --squash
option. In either case, after the merge, the feature branch should be deleted.
# Currently in a branch where several commits were made, and it's ready te be merged into develop
$ git checkout develop
$ git merge feature/my-kinda-cool-feature --squash
These two last categories are intended to tackle the same issue: bugs. But the workflow and use cases are slightly different. The bugfix
branches work the same as the feature
ones, only instead of developing new features you just fix bugs. On the other hand, the hotfix
features are intened to fix critical bugs detected in production, and as such, have the privilege to work around the regular branching flow. These are the only branches that come directly from master
and are merged back into it, so the critical bug is dealt with as fast as possible. After that the hotfix
should be merged into develop
as well so life is good again.
While merging features locally and pushing the merge upstream might fit perfecty well in your workflow, there is a pretty good chance that you are already using some web based platform to manage your remote repositories (Github, Bitbucket, Gitlab...) and that said platform has Pull Requests as one of it's features.
Pull Requests are a way of wrapping your merges around a nice UI and offer a place to comment or discuss the changes introduced by the new branch before merging it. The actual interface might vary from platform from platform but the basics steps are the following:
- Push your branch upstream
- Open a new Pull Request from your branch to the destination branch (usually develop).
- Someone else reviews the changes implemented, might request new changes.
- Once there's a consensus between the pull requester and the reviewers the Pull Request is accepted and the branch merged.
This is a Good ThingTM because it encourages the team to review each others code and have an open conversation about the work being done.
If you want to read further about Pull Requests, the three main Git online platforms offer a rather good documentation about their Pull Request process:
- Github https://help.github.com/articles/about-pull-requests/
- Bitbucket https://www.atlassian.com/git/tutorials/making-a-pull-request
- Gitlab https://docs.gitlab.com/ee/gitlab-basics/add-merge-request.html
The basic workflow is as follows:
- Initially, create two branches:
master
anddevelop
.
- Create
feature
branches fromdevelop
. - Work on a
feature
branch, commit as frequently as possible. - Merge/Pull request back into
develop
. - Once enough
feature
branches have been merged intodevelop
, mergedevelop
back intomaster
and give it a version tag.
- Create
bugfix
branch fromdevelop
. - Bugfixin' time!
- Merge/Pull request back into
develop
.
- Create
hotfix
branch formmaster
. - Bugfixin' time!
- Merge back into
master
. - Merge into
develop
.
$ git init # Initialices the repository
Initialized repository in ~/code/my-project/.git/
$ git add -A && git commit -m "Initial commit" # Creates the fist commit
...
$ git branch develop # Creates the develop branch
$ git branch -a # Lists all the current branches
develop
* master
$ git checkout develop # Change to develop
$ git checkout -b feature/n001-oauth-login # Creates and checkouts to new feature branch
# ...
# Work is done, commits are made.
# ...
$ git add -A
$ git commit -am "Add new login logic"
$ git push --set-upstream feature/n001-oauth-login
# At this point some else reviews the feature branch and approves/rejects it.
# After the pull request is approved, either the reviewer or yourself merges the feature to develop
$ git checkout develop # Change back to develop
$ git pull # Update develop with the already merged feature changes
$ git checkout develop # Change to develop
$ git checkout -b bugfix/n002-malformed-jwt # Creates and checkouts to new feature branch
# ...
# The bug is fixed, commits are made.
# ...
$ git add -A
$ git commit -am "Fix JWT generation"
$ git push --set-upstream bugfix/n002-malformed-jwt
# At this point some else reviews the bugfix branch and approves/rejects it.
# After the pull request is approved, either the reviewer or yourself merges the bugfix to develop
$ git checkout develop # Change back to develop
$ git pull # Update develop with the already merged bugfix
$ git checkout master # Change to master
$ git checkout -b hotfix/n003-database-connection-broken # Creates and checkouts to new feature branch
# ...
# The bug is fixed, commits are made.
# ...
$ git add -A
$ git commit -am "Fix database credentials retrieval"
$ git checkout master # Change back to master
$ git merge hotfix/n003-database-connection-broken
$ git push
$ git checkout develop
$ git merge hotfix/n003-database-connection-broken
$ git push
Welp, this workflow is really designed around the idea that you have a product for which you make a new release every couple weeks, so if that's not your case this model might hurt your development speed or add unnecesary complexity.
https://datasift.github.io/gitflow/IntroducingGitFlow.html
https://hackernoon.com/gitflow-is-a-poor-branching-model-hack-d46567a156e7
https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow
https://www.atlassian.com/git/tutorials/making-a-pull-request
https://nvie.com/posts/a-successful-git-branching-model
https://medium.com/@willhayjr/the-architecture-and-history-of-git-a-distributed-version-control-system-62b17dd37742
https://evilmartians.com/chronicles/git-push---force-and-how-to-deal-with-it
https://chris.beams.io/posts/git-commit
https://maryrosecook.com/blog/post/git-from-the-inside-out
https://csswizardry.com/2017/05/little-things-i-like-to-do-with-git
https://www.endoflineblog.com/gitflow-considered-harmful