Skip to content

Instantly share code, notes, and snippets.

@sompylasar
Last active September 8, 2017 23:27
Show Gist options
  • Save sompylasar/78fc59f810ebe66323124ee1814eff7d to your computer and use it in GitHub Desktop.
Save sompylasar/78fc59f810ebe66323124ee1814eff7d to your computer and use it in GitHub Desktop.
A script that starts a git branch that is dedicated to a task tracking ticket (makes an empty commit that references the ticket).
#!/usr/bin/env bash
## Starts a git branch that is dedicated to a task tracking ticket.
## Makes an empty commit that references the ticket.
##
## Created: 2016-08-22
## Author: sompylasar <babak.john@gmail.com> https://github.com/sompylasar
## License: MIT
# Halt on errors and unbound variables.
set -eu
# Error codes.
ERROR_CODE_SUCCESS=0
ERROR_CODE_USAGE_ERROR=1
ERROR_CODE_STASH_SAVE_FAILED=2
ERROR_CODE_STASH_APPLY_FAILED=3
ERROR_CODE_STASH_NO_REFERENCE=4
ERROR_CODE_CHECKOUT_FAILED=5
ERROR_CODE_COMMIT_FAILED=6
ERROR_CODE_NO_CURRENT_BRANCH=7
ERROR_CODE_NO_STATUS=8
ERROR_CODE_NO_DATE=9
ERROR_CODE_BRANCH_EXISTS=10
# Variables that are used across the helper functions below.
BRANCH_NAME=""
BRANCH_TICKET_URL=""
CURRENT_BRANCH_NAME=""
STASH_NAME=""
##############################################################################
# Helper functions.
##############################################################################
# Prints the script usage docs.
function _usage() {
echo "Usage: git-branch <ticket-url> <branch-name>"
echo " <ticket-url> -- ex. 'https://github.com/example/example/issues/1'"
echo " <branch-name> -- ex. '1-first-issue'"
echo ""
exit ${ERROR_CODE_USAGE_ERROR}
}
# Prints a success message.
function _echo_success() {
echo ""
echo "git-branch SUCCESS Created branch '${BRANCH_NAME}' from '${CURRENT_BRANCH_NAME}' for ${BRANCH_TICKET_URL}"
echo ""
}
# Prints an error message.
# @param {string} $1 The message to print.
function _echo_error() {
echo "git-branch ERROR $1" >>/dev/stderr
}
# Prints a log message.
# @param {string} $1 The message to print.
function _echo_log() {
echo "git-branch LOG $1"
}
# Saves the working directory changes in a git stash.
# Does nothing if `STASH_NAME` is not set.
function _stash_save() {
if [[ -n "${STASH_NAME}" ]]; then
_echo_log "About to save stash of working directory changes as '${STASH_NAME}'..."
# Make it clean: stash all the changes, including untracked files.
git stash save --include-untracked "${STASH_NAME}"
ERROR_CODE=$?
if [[ ${ERROR_CODE} != 0 ]]; then
# Reset the stash name on error to avoid trying to apply from _finally via _error.
STASH_NAME=""
_error "Stash save failed (${ERROR_CODE})." ${ERROR_CODE_STASH_SAVE_FAILED}
fi
_echo_log "Saved stash as '${STASH_NAME}'."
fi
}
# Applies the working directory changes from the previously stored git stash.
# Does nothing if `STASH_NAME` is not set.
function _stash_apply() {
# Rollback the stash.
if [[ -n "${STASH_NAME}" ]]; then
local output_error_code=$1
if [[ ${output_error_code} == 0 ]]; then
output_error_code=${ERROR_CODE_STASH_APPLY_FAILED}
fi
local stash_reference=$( git reflog stash --no-color | grep "${STASH_NAME}" | head -n1 | cut -d' ' -f2 | cut -d':' -f1 2>/dev/null )
if [[ -z "${stash_reference}" ]]; then
_error "Cannot find stash reference by name '${STASH_NAME}'." ${ERROR_CODE_STASH_NO_REFERENCE}
fi
_echo_log "About to apply stash by reference '${stash_reference}'..."
git stash pop "${stash_reference}"
ERROR_CODE=$?
# Reset the stash name to avoid applying once again from _finally via _error.
STASH_NAME=""
if [[ ${ERROR_CODE} != 0 ]]; then
_error "Stash apply failed (${ERROR_CODE})." ${output_error_code}
fi
_echo_log "Applied stash by reference '${stash_reference}'."
fi
}
# Calls _echo_error and then _finally.
# @param {string} $1 The error message to print.
# @param {int} $2 The error code to exit with.
function _error() {
_echo_error "$1"
_finally $2
}
# Prints the undo instructions.
function _echo_undo_instructions() {
echo "For immediate undo, use the following chain of commands:"
echo "git reset --soft \"${CURRENT_BRANCH_NAME}\" && \\"
echo "git checkout \"${CURRENT_BRANCH_NAME}\" && \\"
echo "git branch -d \"${BRANCH_NAME}\""
echo ""
}
# Applies the working directory changes from the previously stored git stash,
# and exits after printing the success (on success) message and the undo instructions.
# @param {int} $1 The error code to exit with.
function _finally() {
local output_error_code=$1
_stash_apply ${output_error_code}
if [[ ${output_error_code} == 0 ]]; then
_echo_success
fi
_echo_undo_instructions
exit ${output_error_code}
}
##############################################################################
# Main program.
##############################################################################
if [[ $# < 2 ]]; then
_usage
fi
BRANCH_TICKET_URL="$1"
BRANCH_NAME="$2"
if [[ -z "${BRANCH_TICKET_URL}" || -z "${BRANCH_NAME}" ]]; then
_usage
fi
# Get the current branch name.
CURRENT_BRANCH_NAME=$( git rev-parse --abbrev-ref HEAD 2>/dev/null )
# Fall back to the commit hash if no branch name exists for the currently checked out commit.
if [[ "${CURRENT_BRANCH_NAME}" == "HEAD" ]]; then
CURRENT_BRANCH_NAME=$( git rev-parse HEAD 2>/dev/null )
fi
if [[ -z "${CURRENT_BRANCH_NAME}" ]]; then
# No _echo_undo_instructions here.
_echo_error "Missing current branch name or commit, please check the state of the repo." ${ERROR_CODE_NO_CURRENT_BRANCH}
exit ${ERROR_CODE_NO_CURRENT_BRANCH}
fi
if [[ "${BRANCH_NAME}" == "${CURRENT_BRANCH_NAME}" ]]; then
# No _echo_undo_instructions here.
_echo_error "Already on branch '${CURRENT_BRANCH_NAME}'."
exit ${ERROR_CODE_BRANCH_EXISTS}
fi
# Use stash only if there are uncommitted changes.
if [[ -n "$(git status --porcelain)" ]]; then
ERROR_CODE=$?
if [[ ${ERROR_CODE} != 0 ]]; then
# No _echo_undo_instructions here.
_echo_error "Error from git status (${ERROR_CODE}), please check the state of the repo."
exit ${ERROR_CODE_NO_STATUS}
fi
STASH_NAME="git-branch-`date +%s`"
ERROR_CODE=$?
if [[ ${ERROR_CODE} != 0 ]]; then
# No _echo_undo_instructions here.
_echo_error "Error from date (${ERROR_CODE})."
exit ${ERROR_CODE_NO_DATE}
fi
_echo_log "Working directory has uncommitted changes."
fi
# Saves only if `STASH_NAME` is set. Logging is inside.
_stash_save
_echo_log "About to create a new branch '${BRANCH_NAME}' and checkout to it..."
# Create a new branch and checkout it.
git checkout -b "${BRANCH_NAME}"
ERROR_CODE=$?
if [[ ${ERROR_CODE} != 0 ]]; then
_error "Checkout failed '${BRANCH_NAME}' (${ERROR_CODE})." ${ERROR_CODE_CHECKOUT_FAILED}
fi
_echo_log "Switched to the new branch '${BRANCH_NAME}'."
_echo_log "About to make a branch start commit to the new branch '${BRANCH_NAME}'..."
# Make an empty commit with the branch ticket URL.
# The message template is hardcoded for now:
# ```
# Start branch '<branch-name>' from '<current-branch-name>'
#
# Refs <ticket-url>
# ```
git commit --allow-empty -m "Start branch '${BRANCH_NAME}' from '${CURRENT_BRANCH_NAME}'" -m "Refs ${BRANCH_TICKET_URL}"
ERROR_CODE=$?
if [[ ${ERROR_CODE} != 0 ]]; then
_error "Commit failed (${ERROR_CODE})." ${ERROR_CODE_COMMIT_FAILED}
fi
_echo_log "Committed the branch start to the new branch '${BRANCH_NAME}'."
# Finalize, display success message.
_finally ${ERROR_CODE_SUCCESS}
@danielAtRS
Copy link

danielAtRS commented Sep 8, 2017

Awesome! Use should post one of those sweet animated gif's that you do showing this in action starting from a new branch or however you use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment