Let's try to improve Git Flow workflow
Some of you will blame on me but, in my opinion, the sync of remote and local branches on the Git Flow workflow can be improved in certain kind of business.
This is why I would to try a different approch in these following article.
I repeat these practices are not adapted to everybody.
Firstly, let's talk about the default behaviour of Git Flow branching.
Default behaviour
Feature
git flow feature start addChangeLog
touch CHANGELOG.md
git add CHANGELOG.md
git commit -m "Add CHANGELOG"
git flow publish
# Will push on local and remote # HAPPY PATH
# Summary of actions:
# - The remote branch 'feature/addChangelog' was created or updated
# - The local branch 'feature/addChangelog' was configured to track the remote branch
# - You are now on branch 'feature/addChangelog'
git flow finish
# Summary of actions:
# - The feature branch 'feature/addChangelog' was merged into 'develop'
# - Feature branch 'feature/addChangelog' has been locally deleted; it has been remotely deleted from 'origin'
# - You are now on branch 'develop'
Release
### Release
git flow release start 0.0.0
git flow publish
# Will push on local and remote # HAPPY PATH
# Summary of actions:
# - The remote branch 'release/0.0.0' was created or updated
# - The local branch 'release/0.0.0' was configured to track the remote branch
# - You are now on branch 'release/0.0.0'
git flow finish
# Summary of actions:
# - Release branch 'release/0.0.0' has been merged into 'main'
# - The release was tagged '0.0.0'
# - Release tag '0.0.0' has been back-merged into 'develop'
# - Release branch 'release/0.0.0' has been locally deleted; it has been remotely deleted from 'origin'
# - You are now on branch 'develop'
Now, I will try to improve these steps by :
Adding a step which create or update automatically an MR when you publish a feature or release.
Syncing local and remote branches on every publish or finish steps.
I intentionally dismiss the others Git flow relative branches in this article but you're free to adapt theses strategies to the others branches.
Requirements
You need to download some packages.
Please install them with your packages manager depending on your environment :
git-flow
curl
jq
[ Optional ] Generate a PAT to permits creation of the MR when you publish.
Visit this page and create a Personal Access Token with api et read_api scopes.
Copy the PAT, we need this on the following part.
Opt for a short expiration date when you create this Access Token
- Create a new ~/.gitconfig file
[init]
templateDir = ~/.git_templates
[user]
name = <First Name> <Last Name>
email = <yourname>@<mail.provider>
[gitlab]
fqdn = gitlab.com # A adapter selon votre environnement
token = glpat-xxx # Let's this empty if you don't want to use a token.
- Create the hooks
Create a folder's hierarchy which contains the hook templates.
mkdir -p ~/.git_templates/{_inc,hooks}
We can, now, create the hooks.
cat > ~/.git_templates/_inc/vars.sh << "EOF"
# Global common Hook vars
BRANCH=$(git rev-parse --abbrev-ref HEAD)
TOKEN=$(git config --global --get gitlab.token)
GITLAB_FQDN=$(git config --global --get gitlab.fqdn)
PROJECT_NAME=$(git remote get-url origin | sed -e "s/^.*${GITLAB_FQDN}[:/]//" -e "s/.\{4\}$//" -e "s/\//%2F/")
# Colors
INFOCOLOR="\e[1;96m"
WARNCOLOR="\e[1;93m"
DFLTCOLOR="\e[0m"
EOF
cat > ~/.git_templates/_inc/functions.sh << "EOF"
# Opened Merge Requests linked to the actual source/target branches ?
mr_count() {
RQMR=$(curl -sSX GET --header "PRIVATE-TOKEN: ${TOKEN}" "https://${GITLAB_FQDN}/api/v4/projects/${PROJECT_NAME}/merge_requests?source_branch=${BRANCH}&target_branch=${1}&state=opened" | jq .)
if $(printf "${RQMR}" | jq .message >/dev/null 2>&1); then
# Display WARN message
printf "%b" "${WARNCOLOR}WARNING: API Gitlab call from mr_count func raised the following error\n${RQMR}${DFLTCOLOR}\n"
MR=-1
elif [ $(printf "${RQMR}" | jq .[].id | wc -l) -eq 0 ]; then
MR=0
else
printf "%b" "${INFOCOLOR}A Merge Request already exists, it will be upgraded${DFLTCOLOR}\n"
MR=1
fi
}
# Merge Request proposal
create_mr() {
# Allows us to read user input below, assigns stdin to keyboard
exec < /dev/tty
printf "Do you want to create a Merge Request (y/n)? "
read answer
if [ "${answer}" != "${answer#[Yy]}" ] ;then
printf "${INFOCOLOR}Create a Merge Request${DFLTCOLOR}\n"
curl -sX POST \
--header "PRIVATE-TOKEN: ${TOKEN}" \
https://${GITLAB_FQDN}/api/v4/projects/${PROJECT_NAME}/merge_requests \
-d title=${BRANCH} \
-d source_branch=${BRANCH} \
-d target_branch=${1} \
-d description="@all review"
fi
}
EOF
cat > ~/.git_templates/hooks/post-merge << "EOF"
#! /usr/bin/env sh
# Source vars & functions files
. ~/.git_templates/_inc/vars.sh
. ~/.git_templates/_inc/functions.sh
# Local vars
REF=$(printf "${GIT_REFLOG_ACTION}" | cut -d " " -f2)
case "${REF}" in
feature/*)
printf "${INFOCOLOR}Update remote ${BRANCH} branch${DFLTCOLOR}\n"
git push -u origin ${BRANCH}
;;
release/[0-9]*\.[0-9]*\.*[0-9])
printf "${INFOCOLOR}Update remote ${BRANCH} branch${DFLTCOLOR}\n"
git push -u origin ${BRANCH}
;;
[0-9]*\.[0-9]*\.*[0-9])
### git flow release finish
printf "${INFOCOLOR}Update remote ${BRANCH} branch${DFLTCOLOR}\n"
git push -u origin ${BRANCH}
printf "${INFOCOLOR}Create a ${REF} tag${DFLTCOLOR}\n"
git push origin ${REF}
;;
*)
exit 0
;;
esac
EOF
cat > ~/.git_templates/hooks/pre-push << "EOF"
#! /usr/bin/env sh
# Source vars & functions files
. ~/.git_templates/_inc/vars.sh
. ~/.git_templates/_inc/functions.sh
# Here we go
case "${BRANCH}" in
feature/*) # git flow feature publish
# MR - target on defined Git Flow develop branch
mr_count ${DEVELOP_BRANCH}
[ "${MR}" -eq 0 ] && create_mr ${DEVELOP_BRANCH} || continue
;;
release/*) # git flow release publish
# MR - target on defined Git Flow main branch
mr_count ${MASTER_BRANCH}
[ "${MR}" -eq 0 ] && create_mr ${MASTER_BRANCH} || continue
;;
*)
exit 0
;;
esac
EOF
Finally, apply the following rights on the folder:
chmod 755 ~/.git_templates/hooks/*
Use cases
On each Gitlab project, a first initialization is mandatory.
The develop and main branches must exist.
git switch main
git swich develop
The starting of feature or release must be made on the develop branch.
git flow init
# You can keep default choices
Create a new feature
git flow feature start my_feature
# Create a local branch named feature/my_feature
# Edit your project and apply your changes...
git add -A
git commit -m "Adding feature"
git flow feature publish
# Push your changes on remote feature/my_feature branch
# You will be asked if you want to create or edit an MR.
git flow feature finish
# Merge feature/my_feature branch to develop branch (local & remote) and merge the MR if created.
# Delete the feature/my_feature branch (local & remote)
Create a new release
git flow release start 0.0.1 # SemVer notation mandatory
# Create a local branch named release/0.0.1
# Edit your project and apply your changes...
git add -A
git commit -m "First release"
git flow release publish
# Push your changes on remote release/0.0.1 branch
# You will be asked if you want to create or edit an MR.
git flow release finish
# Merge release/0.0.1 branch to main branch (local & remote) and merge the MR if created.
# Adding a tag 0.0.1
# Merge the tag 0.0.1 to the develop branch (local & remote)
# Delete the release/0.0.1 branch (local & remote)
That's all folks !
Subscribe to my newsletter
Read articles from Bruno directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Bruno
Bruno
Depuis août 2024, j'accompagne divers projets sur l'optimisation des processus DevOps. Ces compétences, acquises par plusieurs années d'expérience dans le domaine de l'IT, me permettent de contribuer de manière significative à la réussite et l'évolution des infrastructures de mes clients. Mon but est d'apporter une expertise technique pour soutenir la mission et les valeurs de mes clients, en garantissant la scalabilité et l'efficacité de leurs services IT.