Advanced Git concepts for Developers
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.
- Separate subject from body with a blank line
- Limit the subject line to 50 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line
- Wrap the body at 72 characters
- 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_branchWhen 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:
- A
develop
branch is created frommaster
- A
release
branch is created fromdevelop
Feature
branches are created fromdevelop
- When a
feature
is complete it is merged into thedevelop
branch - When the
release
branch is done it is merged intodevelop
andmaster
- If an issue in
master
is detected ahotfix
branch is created frommaster
- Once the
hotfix
is complete it is merged to bothdevelop
andmaster
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 websitegit open --commit
git open -c
# Open the current commit in the repo websitegit open --issue
git open -i
# If this branch is named like issue/#123, this will open the corresponding
# issue in the repo websitegit 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.
- 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.