This document will go over the basic steps for contributing through git. The goal of the improved git workflow is to be able to maintain a current release cycle in addition to maintaining the ability to support older release patching. Such a structured standard is necessary for DevOps to support both a sensible container infrastructure, as well as support automated package deployments. Additionally these structured releases become very accomodating to agile development when used correctly.
A ------------------------------------------- N --- [master]
\ \ /
\ `- M ----- [release/1.x]
\ /
`- B ------------ G ------------- K - L --- O - [develop]
\ / \ /
\----- D - F \ / [feature/ft1]
\ \ /
`- C - E ----- H - I - J [feature/ft2]
+-----------------+------------------+
| Initial Commits | A, B, C, D, O |
+-----------------+------------------+
| Feature Commits | C, D, E, F, I, J |
+-----------------+------------------+
| Merge Commits | G, H, K, M, N |
+-----------------+------------------+
| Tag Commits | L |
+-----------------+------------------+
Diagram
Demonstration of a typical workflow when handling a normal release cycle.
There are three concepts to discuss:
Prior to going into detail about the various branching strategies and the associated bureaucracy, it is important to discuss the details of these signficiant concepts.
Every additive action in git is represented with a commit. Four significant commit concepts will appear in this document:
There is value in discussing their distinction, as they are created by and represent different processes in the workflow.
Initial commits, in the context of this git workflow, is an empty commit made to distinguish the start of a branch. These are not automatically made, and are effectively enforced with convention only. Intial commits are made, more speficially, after branching from a parent branch with the intent on merging the child branch back in. After making an initial commit, a pull request draft or WIP merge request should be opened by the developer and the appropriate reviewers should be assigned.
These commits are expected to be the first commit pushed into all branch types except for the release branchs.
These are your typical commits denoting a change to the codebase. These are created by the developer and pushed directly into their branches.
The following is a list of branches in which feature commits are expected to be pushed:
These are generally created automatically by merging a pull request.
An exception to that, the context of this document,
is the merge commits found in master
. The master
branch should
updated automatically by the CI/CD to point to the most recent and stable
tag, triggered by merges into the release/<version>
branches.
See the Master Branch section for more information on this topic.
These commits are expected to be pushed into all branches except for master
.
Most of these commits will be generated by the pull request tool or by the
CI/CD tool.
Tag commits are the commits that associate tag changes. These must be completed manually, and should be the responsibility of the maintainer after accepting a pull request but prior to merging the pull request. Tags should follow semver guidelines.
The following is a list of branches in which tag commits are expected to be pushed:
These are pushed by maintainers.
There are a number of branch prefixes to be aware of. The five branch formats are:
They all have their own special meanings that will be covered.
This branch shows your latest stable commits. It should reflect your latest package version or your most recent deployment to production.
Updating the master
branch should be done automatically
by the CI/CD tool, and should be triggered on successful
merges into the release/<version>
branches.
The HEAD of master
should always point to the highest
stable tag that has successfully deployed to production.
If no deployment to production has occured, it should contain only an initial commit with the message "First commit".
The logic on the CI/CD server for determining if
master
should be updated automatically may look
something like the following:
if [[
"$(git describe --abbrev=0)" != *"-"* &&
"$(git describe --abbrev=0)" > "$(git describe --abbrev=0 xxx)"
]] ;
then
echo "Update master" ;
else
echo "Do not update master" ;
fi
Snippet
Example logic for determining if
master
needs to be updated. Expects that this is running in a CI/CD block allowed only by merges into arelease/<version>
branch.
Merges into this branch, from the develop
branch are is managed by the code maintainer. A merge into this branch triggers the CI/CD to deploy the latest tagged commit to the appropriate system using SemVer naming standards.
All merges into the release/<version>
branch should be tagged with an appropriate tag version. If major version are broken up across systems, then it would be expected that the correct branch is tagged major tag will be merged into the correct release branch.
When the CI/CD server is triggered by an update to a release branch, one would expect the CI/CD server to then release the latest tag in the current branch. The effect of this is to anticipate that only tagged commits are deployed.
The logic on the CI/CD server for determining if the current branch is a release branch may look like the following:
export RELEASE="release/.*"
if [[ "$(git branch | grep \* | cut -d ' ' -f2)" =~ $RELEASE ]] ; then
echo "Release branch - checking out last tag $(git describe --abbrev=0)" ;
git checkout $(git describe --abbrev=0)
else
echo "Not release branch - moving forward." ;
fi
Snippet
Example logic for determining if the current branch is a
release/<version>
branch, if so it checks out the latest tag on the branch. The CI/CD system should proceed with deployment on the latest tag commit if it is on a release branch.
However, it's not just enough for a release branch to check if a tag exists - it also must deteremine what suffix the tag has. Is it a stable tag, a beta tag, or an alpha tag. This can help the CI/CD system determine what environment to publish the build artifacts into.
The logic on the CI/CD server for determining the alpha/beta/stable status of a tag may look like the following:
export TAG="$(git describe --abbrev=0)"
export ALPHA="^v?([0-9]+\d*)\.([0-9]+\d*)\.([0-9]+\d*)-alpha((\.[0-9]+)|$)$"
export BETA="^v?([0-9]+\d*)\.([0-9]+\d*)\.([0-9]+\d*)-beta((\.[0-9]+)|$)$"
export STABLE="^v?([0-9]+\d*)\.([0-9]+\d*)\.([0-9]+\d*)$"
if [[ "$TAG" =~ $ALPHA ]] ; then
echo "Alpha tag $TAG" ;
elif [[ "$TAG" =~ $BETA ]] ; then
echo "Beta tag $TAG" ;
elif [[ "$TAG" =~ $STABLE ]] ; then
echo "Stable tag $TAG" ;
else
echo "Unknown tag $TAG"
fi
Snippet
Example logic for determining if the current tag is considered
alpha
,beta
, orstable
. The CI/CD system should go on to deployalpha
code to adevl
system, deploybeta
code to atest
system, and deploystable
code to aprod
system.
A common development branch, reflecting development progress on the latest version. Development should not occur directly on this branch, but instead should be done in feature/<version>
branches and merged into develop
. All merging into this branch should be performed by a code maintainer.
Where active development occurs. Generally, one developer will be working on a single feature branch at a time.
These branches are expected to be up-to-date at the time of a merge request, but it is best if they are continuously kept in-sync with changes on develop
.
These branches are opened in response to immediate changes required on the latest version’s release branch. It is expected that these changes will be back-merged into the develop branch.
These branches are opened in response to immediate changes required on a legacy version’s release branch. It is expected that these changes will not be back-merged into the develop branch.
There are two distinct contributors we need to address:
They both have unique roles to play in the SDLC workflow outlines here.
The person who develops features. They generally work out of a feature/<name>
branch.
The developer is responsible for ensuring that merge conflicts do not arise for the code maintainer between their feature/<name>
branch and the develop
branch.
The person who is responsible for maintaining the code through code reviews, merge requests, and tagging.
Angular has a CLI tool for setting up an initial project.
In addition to that, it provides the first commit.
We can make use of that first commit and immediately push that to master
.
Following that, we will do the same with develop
prior to starting with our first feature branch.
- Create a new project in GitHub
- Generate a new Angular project locally with routing and SASS
- Push first commit to
origin/master
- Checkout new
develop
branch frommaster
- Push first commit to
origin/develop
- Setup GitHub branch protection
- Start your first feature branch
this is what you might expect to see in the terminal:
ng new <project> --routing --style scss
cd <project>
git push -u origin master
git checkout -b develop
git push -u origin develop
git checkout -b feature/<title>
Snippet
Local steps for starting an Angular git project
Setting up a git project locally for non-cli tools may be a little different than Angular projects.
As an intial commit is not provided, we will generate an empty commit
with the message "First commit" and push that to master
.
Following that, we will do the same with develop
prior to starting with our first feature branch.
- Create a new project in GitHub
- Clone the project locally
- Create a first empty commit "First commit"
- Push first commit to
origin/master
- Checkout new
develop
branch frommaster
- Push first commit to
origin/develop
- Setup GitHub branch protection
- Start your first feature branch
This is what you might expect to see in the terminal:
git clone <project>
cd <project>
git commit --allow-empty -m "First commit"
git push -u origin/master
git checkout -b develop
git push -u origin/develop
git checkout -b feature/<title>
...
Snippet
Local steps for starting a simple git project
- Feature development
- Deploying patches and minor updates
- Development (alpha)
- Staging (beta)
- Production (stable)
- New major release
- Hotfixing
- Support Fixes
The latest release is the one that is actively being developed.
The develop
branch in it's current state is the unstable state for this release.
Development is not done directly on the develop
branch,
but instead done on feature/<title>
branches.
Using feature/<title>
branches for active develop ensures that
the maintainer has the ability to review all features as they are developed.
Others working on different features know they can (and should)
pull accepted features into their own feature/<title>
branches
once they have been approved and merged into develop
by the maintainer.
This will help protected parallel features from being burdened by compounding conflicts,
and reduce the probability of last minute bugs occuring after accepting a feature.
INCORRECT
... -------------- E ------- H - [develop]
\ / /
\----- B - D / [feature/<name1>]
\ /
`- A - C --- F - G [feature/<name2>]
+-----------------+------------+
| Initial Commits | A, B |
+------------------------------+
| Feature Commits | C, D, F, G |
+-----------------+------------+
| Merge Commits | E, H |
+-----------------+------------+
| Tag Commits | |
+-----------------+------------+
Diagram
In this scenario, feature branches
feature/<name1>
andfeature/<name2>
may become out of sync. Potentially significant development may have occured infeature/<name2>
after the merging offeature/<name1>
intodevelop
. As a result, possible conflicts may become compounded.
CORRECT
... -------------- E ------------- I - [develop]
\ / \ /
\----- B - D \ / [feature/<name1>]
\ \ /
`- A - C ----- F - G - H [feature/<name2>]
+-----------------+------------+
| Initial Commits | A, B |
+-----------------+------------+
| Feature Commits | C, D, H, H |
+-----------------+------------+
| Merge Commits | E, F, I |
+-----------------+------------+
| Tag Commits | |
+-----------------+------------+
Diagram
In this scenario, feature branches
feature/<name1>
andfeature/<name2>
may become out of sync. However, the parentdevelop
branch is continuously synced into the activefeature/<name2>
branch in order to minimize the difficulty of resolving conflicts.
Deployment is triggered when a merge request into the release/<version>
branch succeeds.
The latest tag will become an important factor in the deployment process for three reasons:
- The most recent tag in the branch will be used to determine target environment for deployment
- The most recent tag in the branch points to the commit that will be build and deployed
- The most recent tag in the branch, if a stable semver format,
will be compared with the most recent tag in
master
to determine ifmaster
needs to be updated automatically
There are three supported formats for determining environment selection:
tag | environment |
---|---|
v?.?.? | Production |
v?.?.?-beta | Staging |
v?.?.?-alpha | Development |
Table
Table referencing relationship between tag and environment in the context of merges into
release/<version>
branches. The?
characters in the tag column denote the following character set:/^[0-9]+$/
. These deployment strategies are largely useful for applicaton deployment, though they can be used for publishing libraries to various artifact repository environments.
A --------------------------- I --- [master]
\ \ /
\ `- H ----- [release/<version>]
\ /
`- B -------- E - F - G --- J - [develop]
\ /
`- C - D [feature/<title>]
+-----------------+------------+
| Initial Commits | A, B, C, J |
+-----------------+------------+
| Feature Commits | D |
+-----------------+------------+
| Merge Commits | E, H, I |
+-----------------+------------+
| Tag Commits | F |
+-----------------+------------+
... ------------ E --- [master]
/
... ---------- D ----- [release/<current>]
/
... ---- B - C --- F - [develop]
/
... -- A [feature/<title>]
... ------------------ [release/<legacy>]
+-----------------+---------+
| Initial Commits | F |
+-----------------+---------+
| Feature Commits | A |
+-----------------+---------+
| Merge Commits | B, D, E |
+-----------------+---------+
| Tag Commits | C |
+-----------------+---------+
Diagram
This workflow shows merging into to an existing release branch.
... ------------ E --- [master]
\ /
`- D ----- [release/<current>]
/
... ---- B - C --- F - [develop]
/
... -- A [feature/<title>]
... ------------------ [release/<legacy>]
+-----------------+---------+
| Initial Commits | F |
+-----------------+---------+
| Feature Commits | A |
+-----------------+---------+
| Merge Commits | B, D, E |
+-----------------+---------+
| Tag Commits | C |
+-----------------+---------+
Diagram
This workflow shows up a subsequent new release branch and merging into it.
Finding bugs in production is unfortunate but unavoidable.
Depending on the severity of the bug, the fix may require
bypassing the next scheduled release. In these cases, a
hotfix patch must be merged back into the active release/<version>
branch directly from a hotfix/<version>-<title>
branch.
Depending on the scope of the bug and the number of legacy release
branches in play, a support fix may also be required.
Similarly to how merging a feature into the develop
branch should prompt all developers to merge develop
into their respective feature/<title>
branches, merging
a hotfix into develop
should be followed with develop
being merged into all active feature branches.
The hotfix branch takes the form hotfix/<title>
, where
name is a short, unique, and descriptive name identifying
the problem. Two WIP pull request should be opened by the
developer immediately after they are given an initial commit;
one into the release branch and one into the development branch.
A maintainer should assigned to these pull request by the developer.
The developer should remove the WIP status from the pull requests
once they believe that the hotfix is complete.
The maintainer should then review the pull request into the feature branch.
If the review is rejected then the developer will be required to
resolve the concerns of the maintainer. If the review is accepted,
then the maintainer will push a version tag and then merge the pull request
into the release branch. Once the change is deployed,
the merge request into develop
should be merged by the maintainer.
... ---------------- E --- [master]
/
... -------------- D ----- [release/<current>]
\ /
`- A - B - C [hotfix/<title>]
`--.
\
... ------------------ F - [develop]
+-----------------+---------+
| Initial Commits | A |
+-----------------+---------+
| Feature Commits | B |
+-----------------+---------+
| Merge Commits | D, E, F |
+-----------------+---------+
| Tag Commits | C |
+-----------------+---------+
Diagram
This workflow shows merging a support into to an existing legacy release branch.
Support fixes are like hotfixes in that they are done on special branches made from a given release/<version>
branch and that they are merged directly back into the release branch. However, they are different in that their changes are to be applied to legacy releases, and they cannot be merged back into develop. If the bug affects multiple releases, they must be addressed independently to avoid issues with merging stale code and mixing version tags between releases.
... ------------------ [master]
... ------------------ [release/<current>]
... -------------- D - [release/<legacy>]
\ /
`- A - B - C [support/<legacy>-<title>]
+-----------------+---+
| Initial Commits | A |
+-----------------+---+
| Feature Commits | B |
+-----------------+---+
| Merge Commits | D |
+-----------------+---+
| Tag Commits | C |
+-----------------+---+
Diagram
This workflow shows merging a support into to an existing legacy release branch.
... ---------------- E --- [master]
/
... -------------- D ----- [release/<current>]
\ /
`- A - B - C [hotfix/<title>]
`--.
\
... ------------------ F - [develop]
... ---------------- D' -- [release/<legacy>]
\ /
`- A' - B' - C' [support/<legacy>-<title>]
+-----------------+-------------+
| Initial Commits | A, A' |
+-----------------+-------------+
| Feature Commits | B, B' |
+-----------------+-------------+
| Merge Commits | D, D', E, F |
+-----------------+-------------+
| Tag Commits | C, C' |
+-----------------+-------------+
Diagram
This workflow shows merging a support into to an existing legacy release branch while also pushing a similar yet independent patch from a hotfix branch into the current release.