Advanced Git concepts for Developers

Tushar Roy
15 min readApr 16, 2021

Good Commit Messages

Commit messages are important means of communication between team members and for the lifecycle of the teams and projects since they include the context on which they were created. By inspecting the project history we can find out why some decisions were made when they were made.

Format of making a commit

Summarize changes in around 50 characters or less. Detailed explanatory text. In some contexts, the first line is treated as the subject of the commit and the rest of the text as the body. The blank line separating the summary from the body is critical(unless you omit the body entirely);Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how. Are there side effects or other unintuitive consequences of this change? Here's the place to explain them.
Further paragraphs come after blank lines.

- Bullet points are okay, too

- Typically a hyphen or asterisk is used for the bullet, preceded
by a single space, with blank lines in between, but conventions
vary here

If you use an issue tracker, put references to them at the bottom,
like this:

Resolves: #123
See also: #456, #789

The seven rules of a great Git commit message

Keep in mind: This has all been said before.

  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

1. Separate subject from body with a blank line

Firstly, not every commit requires both a subject and a body. Sometimes a single line is fine, especially when the change is so simple that no further context is necessary.
2. Limit the subject line to 50 characters
50 characters is not a hard limit, just a rule of thumb. GitHub’s UI is fully aware of these conventions. It will warn you if you go past the 50 character limit. Example:

3. Capitalize the subject line

This is as simple as it sounds. Begin all subject lines with a capital letter.

4. Do not end the subject line with a period

Trailing punctuation is unnecessary in subject lines. Besides, space is precious when you’re trying to keep them to 50 chars or less.

5. Use the imperative mood in the subject line

The imperative mood just means “spoken or written as if giving a command or instruction”. Example:
Clean your room , Close the door

Writing this way can be a little awkward at first. We’re more used to speaking in an indicative mood, which is all about reporting facts. That’s why commit messages often end up reading like this:

  • Fixed bug with Y
  • Changing behavior of X

Here’s a simple rule to get it right every time.

A properly formed Git commit subject line should always be able to complete the following sentence:

  • If applied, this commit will your subject line here

For example:

  • If applied, this commit will refactor subsystem X for readability
  • If applied, this commit will update getting started documentation
  • If applied, this commit will remove deprecated methods

Notice how this doesn’t work for the other non-imperative forms:

  • If applied, this commit will fixed the bug with Y
  • If applied, this commit will changing behavior of X

6. Wrap the body at 72 characters

Git never wraps text automatically. When you write the body of a commit message, you must mind its right margin, and wrap text manually.

The recommendation is to do this at 72 characters so that Git has plenty of room to indent text while still keeping everything under 80 characters overall.

7. Use the body to explain what and why vs. how

Give a context about the changes in your commit description, restrain from describing how and explain what and why.

Introduction to Semantic Versioning

Semantic versioning (also referred to as SemVer) is a versioning system.
Semantic Versioning is a 3-component number in the format of X.Y.Z, where :

  • X stands for a major version.
  • Y stands for a minor version.
  • Z stands for a patch.

So, SemVer is of the form Major.Minor.Patch.

  • Bump the value of X when breaking the existing API.
  • Bump the value of Y when implementing new features in a backward-compatible way.
  • Bump the value of Z when fixing bugs.

Git Feature Branch Workflow

The core idea behind the Feature Branch Workflow is that all feature development should take place in a dedicated branch instead of the master branch. This encapsulation makes it easy for multiple developers to work on a particular feature without disturbing the main codebase. It also means the master branch will never contain broken code, which is a huge advantage for continuous integration environments.

How it works

The Feature Branch Workflow assumes a central repository and master represents the official project history. Instead of committing directly to their local master branch, developers create a new branch every time they start work on a new feature. Feature branches should have descriptive names, like animated-menu-items or issue-#1061. The idea is to give a clear, highly focused purpose to each branch. Git makes no technical distinction between the master branch and feature branches, so developers can edit, stage, and commit changes to a feature branch.

In addition, feature branches can (and should) be pushed to the central repository. This makes it possible to share a feature with other developers without touching any official code. Since master is the only “special” branch, storing several feature branches on the central repository doesn’t pose any problems. Of course, this is also a convenient way to back up everybody’s local commits. The following is a walk-through of the life-cycle of a feature branch.

Start with the master branch

All feature branches are created of the latest code state of a project. This guide assumes this is maintained and updated in the master branch.

git checkout master
git fetch origin
git reset --hard origin/master

This switches the repo to the master branch pulls the latest commits and resets the repo's local copy of master to match the latest version.

Create a new-branch

Use a separate branch for each feature or issue you work on. After creating a branch, check it out locally so that any changes you make will be on that branch.

git checkout -b new-feature

This checks out a branch called new-feature based on master, and the -b flag tells Git to create the branch if it doesn’t already exist.

Update, add, commit, and push changes

On this branch, edit, stage, and commit changes in the usual fashion, building up the feature with as many commits as necessary. Work on the feature and make commits like you would any time you use Git. When ready, push your commits, updating the feature branch on Bitbucket.

git status
git add <some-file>
git commit

Push feature branch to remote

It’s a good idea to push the feature branch up to the central repository. This serves as a convenient backup when collaborating with other developers, this would give them access to view commits to the new branch.

git push -u origin new-feature

This command pushes new-feature to the central repository (origin), and the -u flag adds it as a remote-tracking branch. After setting up the tracking branch, git push can be invoked without any parameters to automatically push the new feature branch to the central repository. To get feedback on the new feature branch, create a pull request in a repository management solution like Bitbucket Cloud or Bitbucket Server. From there, you can add reviewers and make sure everything is good to go before merging.

Resolve feedback

Now teammates comment and approve the pushed commits. Resolve their comments locally, commit, and push the suggested changes to Bitbucket.

Merge your pull request

Before you merge, you may have to resolve merge conflicts if others have made changes to the repo. When your pull request is approved and conflict-free, you can add your code to the master branch. Merge from the pull request

Pull requests

Aside from isolating feature development, branches make it possible to discuss changes via pull requests. Once someone completes a feature, they don’t immediately merge it into master. Instead, they push the feature branch to the central server and file a pull request asking to merge their additions into master. This gives other developers an opportunity to review the changes before they become a part of the main codebase.

Code review is a major benefit of pull requests, but they’re actually designed to be a generic way to talk about code. You can think of pull requests as a discussion dedicated to a particular branch. This means that they can also be used much earlier in the development process. For example, if a developer needs help with a particular feature, all they have to do is file a pull request. Interested parties will be notified automatically, and they’ll be able to see the question right next to the relevant commits.

Once a pull request is accepted, the actual act of publishing a feature is much the same as in the Centralized Workflow. First, you need to make sure your local master is synchronized with the upstream master. Then, you merge the feature branch into master and push the updated master back to the central repository.

Gitflow Workflow

Gitflow Workflow is a Git workflow that helps with continuous software development. The Gitflow Workflow defines a strict branching model designed around the project release. This provides a robust framework for managing larger projects.

Getting Started

Gitflow is really just an abstract idea of a Git workflow. This means it dictates what kind of branches to set up and how to merge them together. We will touch on the purposes of the branches below. The git-flow toolset is an actual command-line tool that has an installation process. The installation process for git-flow is straightforward. Packages for git-flow are available on multiple operating systems. On OSX systems, you can execute brew install git-flow. On windows, you will need to download and install git-flow. After installing git-flow you can use it in your project by executing git flow init. Git-flow is a wrapper around Git. The git flow init command is an extension of the default git init command and doesn't change anything in your repository other than creating branches for you.

How does it work?

Develop and Master Branches

Instead of a single master branch, this workflow uses two branches to record the history of the project. The master branch stores the official release history, and the develop branch serves as an integration branch for features. It's also convenient to tag all commits in the master branch with a version number.
The first step is to complement the default master with a develop branch. A simple way to do this is for one developer to create an empty develop branch locally and push it to the server:

git branch develop
git push -u origin develop

This branch will contain the complete history of the project, whereas master will contain an abridged version. Other developers should now clone the central repository and create a tracking branch for develop.

Feature Branches

Each new feature should reside in its own branch, which can be pushed to the central repository for backup/collaboration. But, instead of branching off of master, feature branches use develop as their parent branch. When a feature is complete, it gets merged back into develop. Features should never interact directly with master.
Note that feature branches combined with the develop branch is, for all intents and purposes, the Feature Branch Workflow. But, the Gitflow Workflow doesn’t stop there.

Feature branches are generally created off to the latest develop branch.

Creating a feature branch

Without the git-flow extensions:

git checkout develop
git checkout -b feature_branch
When using the git-flow extension:git flow feature start feature_branch

Continue your work and use Git like you normally would.

Finishing a feature branch

When you’re done with the development work on the feature, the next step is to merge the feature_branch into develop.

Without the git-flow extensions:

git checkout develop
git merge feature_branch

Using the git-flow extensions:

git flow feature finish feature_branch

Release Branches

Once develop has acquired enough features for a release (or a predetermined release date is approaching), you fork a release branch off of develop. Creating this branch starts the next release cycle, so no new features can be added after this point—only bug fixes, documentation generation, and other release-oriented tasks should go in this branch. Once it's ready to ship, the release branch gets merged into master and tagged with a version number. In addition, it should be merged back into develop, which may have progressed since the release was initiated.

Using a dedicated branch to prepare releases makes it possible for one team to polish the current release while another team continues working on features for the next release. It also creates well-defined phases of development (e.g., it’s easy to say, “This week we’re preparing for version 4.0,” and to actually see it in the structure of the repository).

Making release branches is another straightforward branching operation. Like feature branches, release branches are based on the develop branch. A new release branch can be created using the following methods.

Without the git-flow extensions:

git checkout develop
git checkout -b release/0.1.0

When using the git-flow extensions:

$ git flow release start 0.1.0
Switched to a new branch 'release/0.1.0'

Once the release is ready to ship, it will get merged it into master and develop, then the release branch will be deleted. It’s important to merge back into develop because critical updates may have been added to the release branch and they need to be accessible to new features. If your organization stresses code review, this would be an ideal place for a pull request.

To finish a release branch, use the following methods:

Without the git-flow extensions:

git checkout master 
git merge release/0.1.0

Or with the git-flow extension:

git flow release finish '0.1.0'

Hotfix Branches

Maintenance or “hotfix” branches are used to quickly patch production releases. Hotfix branches are a lot like release branches and feature branches except they're based on master instead of develop. This is the only branch that should fork directly off of master. As soon as the fix is complete, it should be merged into both master and develop (or the current release branch), and master should be tagged with an updated version number.

Having a dedicated line of development for bug fixes lets your team address issues without interrupting the rest of the workflow or waiting for the next release cycle. You can think of maintenance branches as ad hoc release branches that work directly with master. A hotfix branch can be created using the following methods:

Without the git-flow extensions:

git checkout master
git checkout -b hotfix_branch

When using the git-flow extensions:

$ git flow hotfix start hotfix_branch

Similar to finishing a release branch, a hotfix branch gets merged into both master and develop.

git checkout master
git merge hotfix_branch
git checkout develop
git merge hotfix_branch
git branch -D hotfix_branch
$ git flow hotfix finish hotfix_branch

The overall flow of Gitflow is:

  1. A develop branch is created from master
  2. A release branch is created from develop
  3. Feature branches are created from develop
  4. When a feature is complete it is merged into the develop branch
  5. When the release branch is done it is merged into develop and master
  6. If an issue in master is detected a hotfix branch is created from master
  7. Once the hotfix is complete it is merged to both develop and master

Git Open

Installation

Basic install

The preferred way of installation is to simply add the git-open script somewhere into your path (e.g. add the directory to your PATH environment or copy git-open into an existing included path like /usr/local/bin).

Install via NPM:

npm install --global git-open

Usage

git open remote-name  branch-name 
# Open the page for this branch on the repo website
git open --commit
git open -c
# Open the current commit in the repo website
git open --issue
git open -i
# If this branch is named like issue/#123, this will open the corresponding
# issue in the repo website
git open --print
git open -p
# Only print the url at the terminal, but don't open it

git merge vs git rebase

git merge and rebase basically combine commits, the difference lies in how they are processed.
The git merge command lets you take the independent lines of development created by git branch and integrate them into a single branch.
whereas the git rebase basically takes the latest commit from a branch and puts it on top of the master branch.

git cherry-pick

Cherry-picking in Git means choosing a commit from one branch and apply it to another.

This is in contrast with other ways such as merge and rebase which normally applies many commits onto another branch.

  1. Make sure you are on the branch you want to apply the commit to.
git switch master

Execute the following:

git cherry-pick <commit-hash>

If you cherry-pick from a public branch, you should consider using
git cherry-pick -x <commit-hash>
This will generate a standardized commit message. This way, you (and your co-workers) can still keep track of the origin of the commit and may avoid merge conflicts in the future.

Git Tags

A tag is used to label and mark a specific commit in history.
It is usually used to mark release points (eg. v1.0, etc.).

Although a tag may appear similar to a branch, a tag, however, does not change. It points directly to a specific commit in the history and will not change unless explicitly updated.

You will not be able to checkout the tags if it’s not locally in your repository so first, you have to fetch the tags to your local repository.

First, make sure that the tag exists locally by doing

# --all will fetch all the remotes.
# --tags will fetch all tags as well
$ git fetch --all --tags --prune

Then check out the tag by running

$ git checkout tags/<tag_name> -b <branch_name>

Instead of origin use the tags/ prefix.

In this sample you have 2 tags version 1.0 & version 1.1 you can check them out with any of the following:

$ git checkout A  ...
$ git checkout version 1.0 ...
$ git checkout tags/version 1.0 ...

All of the above will do the same since the tag is only a pointer to a given commit.

How to see the list of all tags?

# list all tags
$ git tag
# list all tags with given pattern ex: v-
$ git tag --list 'v-*'

How to create tags?

There are 2 ways to create a tag:

# lightweight tag 
$ git tag
# annotated tag
$ git tag -a

The difference between the 2 is that when creating an annotated tag you can add metadata as you have in a git commit:
name, e-mail, date, comment & signature

How to delete tags?

Delete a local tag

$ git tag -d <tag_name>
Deleted tag <tag_name> (was 000000)

Note: If you try to delete a non existig Git tag, there will be see the following error:

$ git tag -d <tag_name>
error: tag '<tag_name>' not found.

Delete remote tags

# Delete a tag from the server with push tags
$ git push --delete origin <tag name>

How to clone a specific tag?

In order to grab the content of a given tag, you can use the checkout command. As explained above tags are like any other commits so we can use checkout and instead of using the SHA-1 simply replacing it with the tag_name

Option 1:

# Update the local git repo with the latest tags from all remotes
$ git fetch --all
# checkout the specific tag
$ git checkout tags/<tag> -b <branch>

Option 2:

Using the clone command

Since git supports shallow clone by adding the --branch to the clone command we can use the tag name instead of the branch name. Git knows how to "translate" the given SHA-1 to the relevant commit

# Clone a specific tag name using git clone 
$ git clone <url> --branch=<tag_name>

git clone — branch=

--branch can also take tags and detaches the HEAD at that commit in the resulting repository.

How to push tags?

git push --tags

To push all tags:

# Push all tags
$ git push --tags

Using the refs/tags instead of just specifying the <tagname>.

Why?

  • It’s recommended to use refs/tags since sometimes tags can have the same name as your branches and a simple git push will push the branch instead of the tag

To push annotated tags and current history chain tags use:

git push --follow-tags

This flag --follow-tags pushes both commits and only tags that are both:

  • Annotated tags (so you can skip local/temp build tags)
  • Reachable tags (an ancestor) from the current branch (located on the history)

From Git 2.4 you can set it using configuration

$ git config --global push.followTags true

Cheatsheet:

Github Actions

GitHub Actions helps you build, test, and deploy applications, but you can also use it to automate other tasks common to your developer workflows: triaging and managing issues, automating releases, collaborating with your user base, and more.

Simply saying automating tasks against some events such as a PR, committing comes under Github Actions. So in github actions, we listen to such events and when that event happens we trigger some functions.

--

--

Tushar Roy
0 Followers

Backend Developer | Software Engineer | Competitive Programmer | Machine Learning | Javascript | C++💞