Last active
September 8, 2017 23:27
-
-
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).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.