I found nvie's git branching model simple enough to grasp and suitable for my projects - but the frontend supplied at GitHub was far too complex for me to understand. Hence, I wrote this script to start and finish tasks, automatically running the related commands to speed it all up a bit (without it becoming too abstract/complex).
I've read a handful of tutorials on writing shell scripts but found it difficult to find one that emphasized on portability/POSIX compliancy. I'd like the script to run just fine in sh, dash, bash and zsh. Any feedback is appreciated - for one thing, I'm a bit unsure about where to use $variable
and ${variable}
...
Intended usage
When I want to create a new branch for a task I run this:
gittask.sh new feature name_of_feature
When I'm done:
gittask.sh done
Finishing a task has it automatically deduce by the branch name prefix (feature/release/hotfix) what to do next.
- Features are merged back into the development branch.
- Releases are continuously merged back into the development branch (bugfixes) and, once the release branch is considered stable enough (decided upon by the user), merged into master (with a tag), ending the release branch.
- Hotfixes are merged back into both the master (with a tag) and the development branch.
Branch model
I use two persistent/remote branches, master and development, as well as three temporary/local branches, feature, release and hotfix.
Master branch
- Persistent
- Remote branch
- Each merge into master equals a new tag
Development branch
- Persistent
- Remote branch
Feature branches
- Temporary
- Branch off from development
- Merge back into development
- Naming: "subject" or "issue#" if on issue tracker ("feature/" is prepended)
Release branches
- Temporary
- Branch off from development
- Merge back into development (continuously) and master (when done)
- Naming: "major.minor.z", e.g. "1.2.0" (z is defined by hotfix, "release/" is prepended)
- Signed tag needed when merging with master
Hotfix branches
- Temporary
- Branch off from master
- Merge back into development and master
- Naming: "x.y.fix", e.g. "1.2.1" (x, y is defined by release, "hotfix/" is prepended)
- Signed tag needed when merging with master
Script
#!/bin/sh
# gittask.sh: taskbased git branching utility
# This script requires that git has been installed and properly configured,
# that the remote "master" and "development" branches exist (locally too)
# and that a network connection to the "origin" repository is established.
set -o errexit
usage()
{
echo
echo "Usage:"
echo " gittask.sh new feature name_of_feature"
echo " - Creates a new branch off from 'development' named"
echo " 'feature/name_of_feature'."
echo " gittask.sh new release name_of_release"
echo " - Creates a new branch off from 'development' named"
echo " 'release/name_of_release'."
echo " gittask.sh new hotfix name_of_hotfix"
echo " - Creates a new branch off from 'master' named"
echo " 'hotfix/name_of_hotfix'."
echo " gittask.sh done"
echo " - Merges current branch into master and/or development"
echo " depending on if it's a feature, release or hotfix."
}
delete_branch()
{
# Infinite loop, only way out (except for Ctrl+C) is to answer yes or no.
while true; do
echo "Delete $current branch? "
read yn
case $yn in
[Yy]* )
git branch -d ${current}
break
;;
[Nn]* )
echo "Leaving $current branch as it is."
break
;;
* )
echo "Error: Please answer (y)es or (n)o."
;;
esac
done
}
define_tag()
{
# Don't proceed until both variables have been set.
while [ -z ${version_number} ] && [ -z ${version_note} ]; do
echo "Enter version number (major.minor.fix): "
read version_number
echo "Enter version number note: "
read version_note
done
}
# Confirm that user is in a git repository, abort otherwise.
git status >/dev/null 2>&1 || { echo "Error: You're not in a git repository."; exit 1; }
# If "new", confirm that the required arguments were provided.
if [ "$1" == "new" ] && [ -n "$2" ] && [ -n "$3" ]; then
# Validate $3, only allow a-z (lower case), 0-9 and _ (underscore) in branch names.
[ "${3//[0-9a-z_]/}" = "" ] || { echo "Error: Branch names may only consist of a-z, 0-9 and underscore."; exit 1; }
case $2 in
feature )
git checkout development
git checkout -b "feature/$3"
exit 0
;;
release )
git checkout development
git checkout -b "release/$3"
exit 0
;;
hotfix )
git checkout master
git checkout -b "hotfix/$3"
exit 0
;;
* )
echo "Error: You didn't specify feature, release or hotfix."
exit 1
;;
esac
# If "done", proceed to determine current branch and by that what to do next.
elif [ "$1" == "done" ]; then
current=`git branch | awk '/\*/{print $2}'`
case ${current} in
feature* )
echo "Merging into development branch..."
git checkout development
git merge ${current}
git push origin development
delete_branch
exit 0
;;
release* )
echo "Merging into development branch..."
git checkout development
git merge ${current}
git push origin development
# Infinite loop, only way out (except for Ctrl+C) is to answer yes or no.
while true; do
echo "Merge into master (make a release)? "
read yn
case $yn in
[Yy]* )
echo "Merging into master branch..."
git checkout master
git merge ${current}
define_tag
git tag -s ${version_number} -m ${version_note}
git push --tags origin master
delete_branch
break
;;
[Nn]* )
echo "Leaving branch master as it is."
break
;;
* )
echo "Error: Please answer (y)es or (n)o."
;;
esac
done
exit 0
;;
hotfix* )
git checkout master
git merge ${current}
define_tag
git tag -s ${version_number} -m ${version_note}
git push --tags origin master
git checkout development
git merge ${current}
git push origin development
delete_branch
exit 0
;;
* )
echo "Error: You're not on a feature, release or hotfix branch."
exit 1
;;
esac
else
echo "Error: You didn't provide the needed arguments."
usage
exit 1
fi