Skip to main content
6 of 8
added 4 characters in body; edited tags
palacsint
  • 29.9k
  • 9
  • 81
  • 156

Shell script to help with branch management for tasks in git

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
totte
  • 81
  • 1
  • 4