git and git-flow, a guide

edrpls
GumGum Tech Blog
Published in
27 min readSep 5, 2019

As with most tech companies nowadays, at GumGum, we use Git for source control, specifically, the git-flow branching model. It gives us flexibility to work on features and bug fixes independently, without affecting production, staging, or someone else’s code, and while also setting conventions that speed up opening and reviewing Pull Requests.

This post does not intend to explain git’s concepts in depth. Instead, it will be a step-by-step guide on how my team relies on git and git-flow, with examples from things we do everyday and things that may come up every now and then to surprise you.

Who is this post for?

This guide aims to be helpful to those:

  • unfamiliar with git or the git-flow branching model
  • familiar with git on graphical user interfaces, but not on the command line interface
  • new to agile development

Why git-flow?

Using a branching model like this one may seem overkill for small projects or when working as the sole developer of a product, but it becomes particularly useful when different people collaborate in the same project and in many different features. The model becomes even more helpful when you need to develop and test new features while making sure production code will be as stable as possible.

By using this branching model, we can develop new features in isolation, without running the risk of pushing something to the wrong branches. Of course, it is also recommended to use a Continuous Delivery tool (like Drone) to handle deployment to production instead of just git-flow, but it is a great start.

I would recommend reading in depth about the model, but for now, suffice it to say that the branching convention we use goes like this:

  • master: Used for production, it should be the most stable branch. Merges commits from hotfix, develop and release branches.
  • develop: Development branch, has the newest fixes and features. Merges commits from bugfix and feature branches.
  • feature: These branches are short lived as they only exist while a specific feature is being developed. Usually does not merge any commits unto it, unless there is another feature derived from it.
  • hotfix: Used to fix pressing issues that are found in production. Hotfix branches merge directly to master.
  • bugfix: Used to fix bugs found that either exist in develop but have not made it into production, or that do exist in production but can wait until the next release. These branches merge into develop.

Additionally, there are support branches, but we rarely use these, as most tasks fit either into bugfix, hotfix or feature branches.

Prerequisites

  • Install git brew install git in Mac OS
  • Install git-flow-avh brew install git-flow-avh in Mac OS. Make sure to install from the gitflow-avh repo, as the original repo is not maintained.

By the way, it is not necessary to install the git-flow CLI tool in order to follow the branching model, but doing so makes things much easier, as it keeps track of branches automatically for us and provides many helpful commands to speed things up.

The Guide

This guide creates a trivial project that will help us cover many scenarios likely to arise when working on a distributed team and how to use git to successfully version our files.

It is split into two parts, with this first part covering:

* Configuration:
* Creating SSH keys
* Configure user email and name
* Save time and conflicts with rerere
* Starting the repository
* Checking the repo status
* Adding files
* Fixing bad commit messages
* Publishing
* Initializing git-flow
* Cloning a repository
* Creating a new feature
* Publishing a branch with git-flow
* Finishing a branch
* Rebasing the branch
* Closing a branch
* Making a release
* Git tags

The second part, covers:

* Working on an Epic
* Dealing with subtasks
* Stashing work in progress
* Preventing conflicts with `--onto`
* Fixing bugs in production (hotfix)
* Solving conflicts
* Undoing `git add`
* Rolling back changes
* Navigating git's history

Configuration

Creating SSH keys

We use a SSH key-pair to authenticate commits from your computer. If there is no current key-pair in your computer, it can be created by running:

ssh-keygen -t rsa -b 4096 -C "your.email@example.com"

Follow the instructions. All defaults should be fine, although you may want to add a password to the key-pair for extra security, and make sure to remember it!

If you used the defaults, the new key-pair should be stored in ~/.ssh/. In there, you should find two files:

~/.ssh
├── id_rsa
└── id_rsa.pub

id_rsa is your private key, it must NEVER be shared publicly, or people could sign their commits as you and push malicious code to the repositories you have access to.

id_rsa.pub is your public key. This file can be uploaded to any services you may want to push code to, and here are specific instructions for some of the most popular services:

Name and email

Before git allows you to push to any repository, it requires that you configure your email and name. This information will be used to sign your commits in a human readable way.

Git configurations can be either:

  • global, if you intend to use the same email and name for all repositories in your computer
  • local, if one of your repos has any special requirements or you want to set different configurations for each directory with a repository
# configure the email
git config --global user.email "your.email@example.com"
# configure user name
git config --global user.name "Your Name"

Save time and conflicts with rerere

When rerere is enabled, the first time a conflict is resolved the result is recorded. If the conflict ever comes up again, git will know to apply the recorded resolution, saving us time and effort.

Enabling:

git config --global rerere.enabled true

The resolutions cache can be cleared altogether by running:

git rerere clear

Or per file with:

git rerere forget path/to/file

Read more about rerere.

Starting our repository

For the purposes of this guide, the repo will live on GitLab and we will use its issue tracker as if we were working in a real project with tasks someone assigned to us or our team members.

The first task we get is to set up the directory and the git repository. For this, we will create a directory inside ~/Projects/.

cd ~/Projects # Or wherever you store your projects
mkdir git-guide
cd git-guide

Now let’s make this a git repo by running:

git init

This will create the .git/ hidden directory. All of git's configurations for the project will live here. You usually don't have to interact with it, but there are some times you may want to, for example, when a branch changes name and you want git-flow to track the new name.

Running git init also created the master branch which is the default branch and the one we are currently standing on.

git status

We can check the status of the directory and its files by running git status. There is nothing in our directory, so the output should look like this:

On branch masterNo commits yetnothing to commit (create/copy files and use "git add" to track)

The first line tells us the branch we are standing on. The second one will only be seen now, as there is literally nothing in the repo. Lastly, git will list any changes pending, that is any files deleted, modified, or created.

Adding files to git

Let’s create our first file, just so we have something to commit:

touch README.md

Now let’s see what git status has to say:

On branch masterNo commits yetUntracked files:
(use "git add <file>..." to include in what will be committed)
README.mdnothing added to commit but untracked files present (use "git add" to track)

The status has changed… now we see that there is a new file, but it is untracked, so any changes made to this file would be permanent and we wouldn’t be able to go back to previous versions. Let’s fix that by adding the file to git:

git add README.md

Running git status will now show:

On branch masterNo commits yetChanges to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md

We see that the file is ready to be committed. This will allow git to track any changes made to it. Now the next thing we have to do is commit our changes.

Create a commit

In simple terms, a commit is the bundle of data that includes all of the changes we want to make. In other words, it creates a “step” or “point” in history that we can use to come back to and to compare with other points in history.

Lets commit our file so that we can complete our first task:

git commit -m "Create the REDAME (#1)"

The commit command will take everything ready to be committed and create this new "point" in history.

The -m flag lets us pass a short for message so that we can easily see what is included on this commit.

There are three important things to note:

  1. Separate subject from body with a blank line
  2. Limit the subject line to 50 characters
  3. Capitalize the subject line
  4. Do not end the subject line with a period
  5. Use the imperative mood in the subject line
  6. Wrap the body at 72 characters
  7. Use the body to explain what and why vs. how
  • We are linking to the ticket in GitLab by using # + ticket number (#1), this makes it easier for everyone looking at the commit to quickly check the ticket and scope of it. Bitbucket and GitHub have similar features.
  • We committed a typo! (REDAME)

Fixing bad commit messages

So, we made a mistake, but thankfully, we caught it before it was published. To fix this, we can run:

git commit --amend

This command will open the default terminal editor (in my case Vim) with the commit message and the body (if any). It will look something like this:

Create the REDAME (#1)# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Mon Jul 29 20:51:55 2019 -0500
#
# On branch master
#
# Initial commit
#
# Changes to be committed:
# new file: README.md
#

We can just fix the typo, save and exit.

Running git status now looks like this:

On branch master
nothing to commit, working tree clean

Now, if we want to see the commit history, we can run git log, which will look something like this:

commit e11813e202f305b6204ff39d8cba4a27be7adc24 (HEAD -> master)
Author: Eder Sanchez <email@example.com>
Date: Mon Jul 29 20:51:55 2019 -0500
Create the README (#1)

We have the first step, and now are almost ready to publish this and close the ticket.

Publishing a repository

As mentioned before, we are using GitLab, but the publishing instructions are pretty similar for every platform. GitLab gives us three options to upload code for the first time to our repo:

  • Create a new repository
  • Push an existing folder
  • Push an existing Git repository

We already have a folder and a git repo initialized, so we’ll go with the third option, “Push an existing git repository”. These are GitLab’s instructions:

git remote add origin git@gitlab.com:edr1/git-guide.git
git push -u origin --all
git push -u origin --tags

Now if we take a look at the master branch, we can see our commit listed, and hovering over the ticket number shows a link and tooltip with the issue title!

GitLab snippet with the commit and tooltip with the issue title

Great! We can close our first ticket, and move on to the next one!

Initialize git-flow

Our second ticket requires us to create the development branch. This branch will be used as the starting point for every new feature the project requires. It also helps us make sure the project is working fine before releasing anything to production (master).

We could just create the new branch by running git branch develop, but that would not give us all the advantages that git-flow has, like taking care of tracking and merging releases and feature branches for us. We would have to do it all manually, and why do it ourselves when we already have a tool that can do it for us?

Git-flow initialization is necessary whenever we clone a repo and want it to follow this branching model. Let’s start git-flow by running git flow init inside our project's root directory. You can use all the default values for the different questions:

git flow initWhich branch should be used for bringing forth production releases?
- master
Branch name for production releases: [master]
Branch name for "next release" development: [develop]
How to name your supporting branch prefixes?
Feature branches? [feature/]
Bugfix branches? [bugfix/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
Hooks and filters directory? [/home/eder/Projects/git-guide/.git/hooks]

We should be standing now on the develop branch. To close the ticket, we have to push it to the repo, but we cannot just git push as the branch doesn't exist in the GitLab repo yet.

To push a new branch, we need to add the --set-upstream flag and specify the remote we want to use, in this case origin:

git push --set-upstream origin develop

Now the develop branch exists in the repository and our team can clone the repo to start working on the actual features.

Cloning a repository

Now that we have our develop branch set, other developers can use it as their starting point for new features, but first, they need to be able to clone our repository into their machines. Each person needs their own credentials already set up so that they can push to the repo, as well as installing git and git-flow-avh.

All services provide a HTTPS and SSH links and even a button to copy whatever version you choose to your clipboard. We will use the SSH version since we are already using SSH keys.

GitLab’s SSH and HTTPS URLs for this project

Assuming the repo does not exist in our system yet, we would need to run git clone URL-YOU-WANT-TO-USE on the directory where we want the project to live:

git clone git@gitlab.com:edr1/git-guide.git

If there are any errors, it would be a good idea to verify that either the repo you’re trying to clone is public or that an admin has given you access to the repo.

After successfully cloning the repo, we should be standing on the master branch and since this is a new clone of the repository, we need to initialize git-flow-avh here too by running:

git flow init

Remember that for this project we are using git flow’s defaults, but your specific project may have different branch names. It all depends on the conventions used by your team. Some teams might use staging instead of develop or productioninstead of master.

Great! Now everyone on the team can clone the repo and be ready to create new features.

Creating a new feature

We can get to actually “work” in our project instead of setting stuff up!

Our first feature (and third ticket) is…

🥁🥁🥁🥁🥁 🥁🥁🥁🥁🥁 🥁🥁🥁🥁🥁

To create an index.html

In fact all of our commits and features will happen within this single file. As mentioned in the introduction to the guide, our project is really trivial, but enough to showcase the workflow we use.

Let’s get right to it by creating a feature branch so that we can work in isolation without affecting any other branches. git flow’s command line tool provides an easy way to open the feature branch and keep track of its origin and any other branches that may come out of it.

git flow feature start create-index-file

The output should look like this:

Switched to a new branch 'feature/create-index-file'Summary of actions:
- A new branch 'feature/create-index-file' was created, based on 'develop'
- You are now on branch 'feature/create-index-file'
Now, start committing on your feature. When done, use: git flow feature finish create-index-file

We can start to see the advantages of using this tool now. As the tool tells us, it created a new branch ‘feature/create-index-file’ and moved us there, so we are already standing on the new branch. Additionally, it tells us that when we are ready to merge it, we can run git flow feature finish create-index-file and git-flow will make sure to merge it into its base branch (develop) and close the current branch (unless we tell it not to).

Note: In Bitbucket, you can also add the ticket number to your branch so that Jira updates the tickets automatically, so if our Bitbucket ticket is GG-3, we would have named our branch GG-3-create-index-file. On GitHub and GitLab, we can link to issues on commits and Merge Requests.

The ticket requirements are:

There should be an index.html file in the root directory, it should be a valid html5 document and include:
- head with project title
- header with project title
- h1 with project title
- content section with “Coming soon” text
- footer with current year hardcoded, #4 will handle this dynamically

The task at hand is quite simple and could be done in minutes, but let’s imagine for a moment that we are extremely limited in time for this project, and we need our team to see the progress we are making, as someone else might need to continue the work.

We will split into different tasks to showcase how git can help us track relevant changes to our projects.

We can see each of these requirements as a subtask. With this in mind, we create the index.html file with our favorite editor. I'm using vim with snippets from UltiSnips to save time. The snippet looks like this:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
body
</body>
</html>

This is a valid HTML document, in a file named index.html and, while it might not have everything the ticket needs, it is the first step towards getting there. Given our extreme time limitations, we're going to stop right here for the day, at least in this universe where we have a minute to work on every task.

Running git status tells us that we have a new untracked file:

On branch feature/create-index-file
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.html
nothing added to commit but untracked files present (use "git add" to track)

We get a hint to use git add to start tracking changes to our new file. Now, it is important to note that unlike the version tracking on, say Google Docs, git's tracking is not automatic, we have to tell it what changes are important for the project, kind of like putting bookmarks that we can go back to, into our project's history. Let's track the index.html file just like we did with the README.md.

git add index.html

Now let’s give our commit (the “bookmark”) a relevant name describing the change and linking to our GitLab ticket:

git commit -m "add index.html #3"

Finally, let’s push today’s change:

git pushfatal: The current branch feature/create-index-file has no upstream branch.
To push the current branch and set the remote as upstream, use...

Oops, that didn’t work. We cannot push our branch since it does not exist in the remote repository yet. Normally, we could run git push --set-upstream origin the-name-of-our-branch, but git-flow gives us an easier way with the publish command.

Publishing a branch with git-flow

git-flow provides the publish command for every type of branch that it supports. Since we are using a feature branch, we can run:

git flow feature publish

Output:

Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 16 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 415 bytes | 415.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for feature/create-index-file, visit:
remote: https://gitlab.com/edr1/git-guide/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fcreate-index-file
remote:
To gitlab.com:edr1/git-guide.git
* [new branch] feature/create-index-file -> feature/create-index-file
Branch 'feature/create-index-file' set up to track remote branch 'feature/create-index-file' from 'origin'.
Already on 'feature/create-index-file'
Your branch is up to date with 'origin/feature/create-index-file'.
Summary of actions:
- The remote branch 'feature/create-index-file' was created or updated
- The local branch 'feature/create-index-file' was configured to track the remote branch
- You are now on branch 'feature/create-index-file'

The “Summary of actions” from git-flow tells us that a remote branch was created, our local branch is set to track the remote branch, and we are currently standing on said branch - feature/create-index-file.

This is pretty much the same as pushing with --set-upstream but easier to remember and comes with a nice summary of what happened.

It is important to remember that publishing is only necessary the first time we want to push the branch to the repository. Any subsequent commits can be pushed withgit push.

Now let’s continue with the rest of our subtasks, and try to create a commit for each one of them.

Three weeks later

Finishing a branch

*takes code out of the oven*

We finished the subtasks, and our git log now looks like this:

commit 46620a5f108fde440ba9d631e3bfcb1106ade3bc (HEAD -> feature/create-index-file, origin/feature/create-index-file)
Author: Eder Sanchez <email@example.com>
Date: Fri Aug 30 18:13:05 2019 -0500
add footer with current yearcommit af5e5658b254c2551c452a6ed99dbef9935e690a
Author: Eder Sanchez <email@example.com>
Date: Fri Aug 30 18:11:44 2019 -0500
add coming soon messagecommit 9bdcd1837528095575bbcc03ef7714841db70aa8
Author: Eder Sanchez <email@example.com>
Date: Fri Aug 30 18:11:13 2019 -0500
add project name to headercommit 7725be0b60bac08217a0b387f3133f44076d93b4
Author: Eder Sanchez <email@example.com>
Date: Fri Aug 30 18:01:57 2019 -0500
add project name to head tagcommit 5154bbadaeb645b005749ff747331981cb64008e
Author: Eder Sanchez <email@example.com>
Date: Fri Aug 30 17:17:49 2019 -0500
add index.html #3commit e11813e202f305b6204ff39d8cba4a27be7adc24 (origin/master, origin/develop, master, develop)
Author: Eder Sanchez <email@example.com>
Date: Mon Jul 29 20:51:55 2019 -0500
Create the README (#1)

And our finished html file:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Learning Git Flow</title>
</head>
<body>
<header>
<h1>Learning Git Flow</h1>
</header>
<section>
Coming soon!
</section>
<footer>
2019
</footer>
</body>
</html>

At this point, we need to open a Pull/Merge Request wherever our repository is stored, in this case, GitLab, to get the branch reviewed by our peers, the code finally merged to develop, and eventually released to production.

Approving your own PRs

Our “teammates” have approved our branch and we are ready to merge it to develop! …or almost ready that is. If we look back at the branch’s history, we see that there are five commits just for this branch, and while they are in fact relevant for this specific feature branch, not all of them are relevant for the bigger picture. In the future, when we look at our project’s git history, we want to be able to see the important features and changes once they are complete, not every single change that was required to make it happen.

Squashing commits

git provides the rebase command, which helps synchronize our branch with its base branch. This is really helpful when the base branch received updates while we were working on our branch. Word of advice, though, running this command will rewrite the git history, which will change the id of commits and potentially their contents. This will affect everyone with a local copy of the branch. Because of this, it is better not to rebase the master and develop branches, as these are shared by everyone in the project.

If we were to run git rebase, the branch would be synchronized with stage and our commits. Had there been any other commits on develop, the ids for our commits would have changed, but we would have still have five commits.

This is where the interactive rebase comes in. We run it by adding the -i flag to the rebase command. By the way, git flow also provides some aliases for rebasing - git flow [branch-type] rebase [-i]. For our case: git flow feature rebase -i.

Running the interactive rebase will open the terminal editor (in my case Vim). Its content should look like this:

pick 5154bba add index.html #3
pick 7725be0 add project name to head tag
pick 9bdcd18 add project name to header
pick af5e565 add coming soon message
pick 46620a5 add footer with current year
# Rebase e11813e..46620a5 onto af5e565 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

The first part (the one without any #comments), is the list of our commits. Let’s dissect the three parts that make each line:

pick 5154bba add index.html #3

Think of each part as a column, with the first one being pick.

This column is for the special commands used on interactive rebase. By default it is set to “pick”, which means that we would like to preserve this commit. More on this later.

The next column has a series of numbers and letters. This is the hash for our commit, and we can see it as the commit’s “id”. The actual hash is much longer 5154bbadaeb645b005749ff747331981cb64008e, but since hashes are so unique, we can use a small part of it 5154bba instead of the whole thing.

On the third column we see a familiar face, this is the name we gave to our commit, and is there to help us differentiate between them.

Finally, the last part is just a big block comment, with a short recap of what we are looking at, and even more important, a list of commands that we can use. As you can see, there are many different commands, and each one allows us to perform a different action over our commits. We can change the name of the branches with reword, merge commits into the previous commit to remove any sign of their existence with fixup, change something inside an already existing commit with edit, and so much more, but right now, we only care about squash.

“Squashing” a commit, means that we want to use it, but we don’t want it to be its own commit, instead, we want it to be merged into the previous commit, and optionally preserve the squashed commit’s name as a comment in the final commit that we want.

For our specific case, we would like to have just a single commit with a name that represents the whole feature instead of the five we have, so how do we achieve this?

We have to modify the command column on every commit that we want to affect. Ours would look end up looking like this:

reword 5154bba add index.html #3
squash 7725be0 add project name to head tag
squash 9bdcd18 add project name to header
squash af5e565 add coming soon message
squash 46620a5 add footer with current year

Or even better, we can use the shorthand for each of these commands:

r 5154bba add index.html #3
s 7725be0 add project name to head tag
s 9bdcd18 add project name to header
s af5e565 add coming soon message
s 46620a5 add footer with current year

Note that we could have left the first commit (5154bba) as “pick”, but we would like to rename it to the original ticket we had. Also note that modifying the commit name here would have no effect. That will happen later. The first step is to set the commands we want to apply on rebase.

Now that we set the commands that we want to apply, we have to save and close this file. git will automatically guide us through the next steps.

Now we should be looking at something like this:

add index.html #3# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Fri Aug 30 17:17:49 2019 -0500
#
# interactive rebase in progress; onto e11813e
# Last command done (1 command done):
# reword 5154bba add index.html #3
# Next commands to do (4 remaining commands):
# squash 7725be0 add project name to head tag
# squash 9bdcd18 add project name to header
# You are currently editing a commit while rebasing branch 'feature/create-index-file' on 'e11813e'.
#
# Changes to be committed:
# new file: index.html
#

As always, git gives us a hint of what is going on. It tells us to enter the commit message. Since we are currently at the rewordstep that we set earlier, this is where we edit it to what we want to be set in our git's history. We will change the first line to the ticket's name and also put the number between parenthesis:

Create the index.html page (#3)

Then we save and close this file. To continue with the next steps, we should now be looking at something like this:

# This is a combination of 5 commits.
# This is the 1st commit message:
Create the index.html page (#3)# This is the commit message #2:add project name to head tag# This is the commit message #3:add project name to header# This is the commit message #4:add coming soon message# This is the commit message #5:add footer with current year# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Fri Aug 30 17:17:49 2019 -0500
#
# interactive rebase in progress; onto e11813e
# Last commands done (5 commands done):
# squash af5e565 add coming soon message
# squash 46620a5 add footer with current year
# No commands remaining.
# You are currently rebasing branch 'feature/create-index-file' on 'e11813e'.
#
# Changes to be committed:
# new file: index.html
#

Again, git gives us some hints. We are looking at the combination of the five commits in our branch. The first line without comments is the name of our commit. Everything else is the names of the rest of the commits, but now as a message. They are not individual commits anymore, as they all have been merged into one.

Here we could edit anything we want, from changing the name again, to editing any of the commit messages or even removing them altogether. They could be helpful for someone looking at our repo in the future, so we will leave it as is, and save and close the file.

We should see a message saying Successfully rebased and updated refs/heads/feature/create-index-file. If we look at our log:

commit bc19014813e6e883825c7d4f5b4a517546d04bb5 (HEAD -> feature/create-index-file)
Author: Eder Sanchez <email@example.com>
Date: Fri Aug 30 17:17:49 2019 -0500
Create the index.html page (#3) add project name to head tag add project name to header add coming soon message add footer with current yearcommit e11813e202f305b6204ff39d8cba4a27be7adc24 (origin/master, origin/develop, master, develop)
Author: Eder Sanchez <email@example.com>
Date: Mon Jul 29 20:51:55 2019 -0500
Create the README (#1)

There are now just two commits, and the other four appear as comments in our second commit bc19014.

Finally, we have to synchronize our remote branch with our local branch, but running git push will not work:

To gitlab.com:edr1/git-guide.git
! [rejected] feature/create-index-file -> feature/create-index-file (non-fast-forward)
error: failed to push some refs to 'git@gitlab.com:edr1/git-guide.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Remember what we said about how rebase modifies the git history? Now our remote has five different commits while our local branch has only two. Since we rebased, our local and remote branches have different histories. Our local branch is the one with the data how we want it to look, so we will force push into the remote with:

git push --force or git push -f for short

As the name implies, doing this forces the repository to take in whatever we are pushing and to overwrite whatever it currently holds. Again, it is important to limit doing this only to feature, hotfix, bugfix and support branches, as doing it on master and develop will affect everyone who using the repository.

Note that Github, Bitbucket and GitLab provide ways to perform something similar through their user interfaces, but doing it through the command line interface gives us much more control over what we want to include in our commits.

Our remote and local branches are now in sync and we are ready to close our branch.

Closing a branch

We can delegate the process of merging to git-flow by running:

git flow feature finish

Note that github and gitlab users may want to add the -k flag to preserve their branches, as git flow's default behavior is to delete the merged branches, making issues appear as closed rather than merged. It is easily fixed by adding -k

Summary of actions:
- The feature branch 'feature/create-index-file' was merged into 'develop'
- Feature branch 'feature/create-index-file' has been locally deleted; it has been remotely deleted from 'origin'
- You are now on branch 'develop'

This time, git flow merged our feature branch into the base branch (develop), deleted both the local and remote branches (use -k to preserve them), and finally, it took us back to develop. If we run git log here, we should only see two commits:

bc19014 (HEAD -> develop) Create the index.html page (#3)
e11813e (origin/master, origin/develop, master) Create the README (#1)

Note that the hash for our second commit is unlike any other when we had five commits because we rewrote the history. By the way, this time I’m using git log --pretty=oneline --abbrev-commit to see a shorter version of the log.

Now that we stand in develop, the only thing left is to push our changes with git push.

With our “Coming Soon” page, we are ready to make our first release.

Making a release

It may not look like much, but index.html is just the start for our imaginary company. At least, people will know that something will come soon, but first, we have to release our code to production. To do this, we will create a release branch by running:

git flow release start 1.0.0

Where 1.0.0 is our branch's name and also the first version of our project.

Now, git flow gives us not only the Summary of actions, but also Follow-up actions:

Summary of actions:
- A new branch 'release/1.0.0' was created, based on 'develop'
- You are now on branch 'release/1.0.0'
Follow-up actions:
- Bump the version number now!
- Start committing last-minute fixes in preparing your release
- When done, run:
git flow release finish '1.0.0'

Now we are standing on a new branch named release/1.0.0. At this point, we can bump any versions we may need, for example usually we run npm version patch/minor/major to get npm to automatically bump the version in package.json. We could also make any finally adjustments to our project, maybe fix some typos or stuff like that.

We don’t have much here, and in fact we want everything from develop merged into master, so we can go ahead and run the command to close the release branch:

git flow release finish

This will open the terminal editor with something like this:

Merge branch 'release/1.0.0'
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

Git Tags

A git tag is a reference in git that points to a specific point in git’s history. We can see it as a snapshot of a specific version of our project.

You can go ahead and edit the predefined message or just leave it as is, save and close. Another terminal editor will open, this time prompting us to write a message for the current tag (1.0.0). Try to write something that helps understand what’s being released, for example: “add initial index.html with ‘coming soon’ message”.

After writing the message, save and close. Once again, the editor will open, but this time, it prompts us to write a message for the merge from 1.0.0 to develop. You can either edit it or leave it as is, save and close.

Summary of actions:
- Release branch 'release/1.0.0' has been merged into 'master'
- The release was tagged '1.0.0'
- Release tag '1.0.0' has been back-merged into 'develop'
- Release branch 'release/1.0.0' has been locally deleted
- You are now on branch 'develop'

The summary now tells us that the release branch was merged into master, and said release was tagged as 1.0.0, so that tag will always point to the same thing. The branch was then merged into develop, then the local branch was deleted, and we are now back in develop.

The only thing left for us to do is to push to both master and develop, and to also push our tag:

git push # since we are already in develop
git checkout master # change to master
git push # push master
git push origin 1.0.0 # push tag

Now we can take a look at our repository’s tags, and we’ll find 1.0.0 there.

The repository’s tags

This release marks the end of the first part of our git and git flow guide.

Conclusion

git and git flow are excellent tools to track changes to our projects while also allowing us to go back and forth to different versions, and allowing everyone working on the same repository to have their individual sandbox where they can do whatever they like.

These tools can be overwhelming at first, but after getting to know their commands, we can unleash their potential and maintain a healthy codebase and project. Even with everything we’ve learned so far, we still have a long way to go to fully take advantage of these tools and how they fit into a larger team.

Tune in for the next part, where we will learn how to solve conflicts introduced by working on the same files as other team members, learn how to work with Epics, how to prevent conflicts when rebasing by using --onto, stash things for later, and much more!

References

We’re always looking for new talent! View jobs.

Follow us: Facebook | Twitter | | Linkedin | Instagram

--

--