811 lines
19 KiB
Bash
811 lines
19 KiB
Bash
# $Id$
|
|
# vim:et:ft=sh:sts=2:sw=2
|
|
#
|
|
# git-flow -- A collection of Git extensions to provide high-level
|
|
# repository operations for Vincent Driessen's branching model.
|
|
#
|
|
# A blog post presenting this model is found at:
|
|
# http://blog.avirtualhome.com/development-workflow-using-git/
|
|
#
|
|
# Feel free to contribute to this project at:
|
|
# http://github.com/petervanderdoes/gitflow
|
|
#
|
|
# Authors:
|
|
# Copyright 2012-2019 Peter van der Does. All rights reserved.
|
|
#
|
|
# Original Author:
|
|
# Copyright 2010 Vincent Driessen. All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
# list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
|
|
#
|
|
# Common functionality
|
|
#
|
|
|
|
# Shell output
|
|
warn() { echo "$@" >&2; }
|
|
die() { warn "Fatal: $@"; exit 1; }
|
|
die_help() { warn $@; flags_help; exit 1; }
|
|
|
|
escape() {
|
|
echo "$1" | sed 's/\([\.\$\*]\)/\\\1/g'
|
|
}
|
|
|
|
#
|
|
# String contains function
|
|
# $1 haystack
|
|
# $2 Needle
|
|
#
|
|
contains() {
|
|
local return
|
|
|
|
case $1 in
|
|
*$2*)
|
|
return=$FLAGS_TRUE
|
|
;;
|
|
*)
|
|
return=$FLAGS_FALSE
|
|
;;
|
|
esac
|
|
return $return
|
|
}
|
|
|
|
# Basic math
|
|
min() { [ "$1" -le "$2" ] && echo "$1" || echo "$2"; }
|
|
max() { [ "$1" -ge "$2" ] && echo "$1" || echo "$2"; }
|
|
|
|
# Basic string matching
|
|
startswith() { [ "$1" != "${1#$2}" ]; }
|
|
endswith() { [ "$1" != "${1%$2}" ]; }
|
|
|
|
# Convenience functions for checking shFlags flags
|
|
flag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; }
|
|
noflag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -ne $FLAGS_TRUE ]; }
|
|
|
|
# check_boolean
|
|
# Check if given value can be interpreted as a boolean
|
|
#
|
|
# This function determines if the passed parameter is a valid boolean value.
|
|
#
|
|
# Param $1: string Value to check if it's a valid boolean
|
|
#
|
|
# Return: string FLAGS_TRUE|FLAGS_FALSE|FLAGS_ERROR
|
|
# FLAGS_TRUE if the parameter is a boolean TRUE
|
|
# FLAGS_FALSE if the parameter is a boolean FALSE
|
|
# FLAGS_ERROR if the parameter is not a boolean
|
|
#
|
|
check_boolean() {
|
|
local _return _value
|
|
_value="${1}"
|
|
case "${_value}" in
|
|
${FLAGS_TRUE} | [yY] | [yY][eE][sS] | [tT] | [tT][rR][uU][eE])
|
|
_return=${FLAGS_TRUE}
|
|
;;
|
|
${FLAGS_FALSE} | [nN] | [nN][oO] | [fF] | [fF][aA][lL][sS][eE])
|
|
_return=${FLAGS_FALSE}
|
|
;;
|
|
|
|
*)
|
|
_return=${FLAGS_ERROR}
|
|
;;
|
|
esac
|
|
unset _value
|
|
return ${_return}
|
|
}
|
|
|
|
#
|
|
# Git specific common functionality
|
|
#
|
|
|
|
git_local_branches() { git for-each-ref --sort refname --format='%(refname:short)' refs/heads; }
|
|
git_remote_branches() { git for-each-ref --sort refname --format='%(refname:short)' refs/remotes; }
|
|
git_all_branches() { git for-each-ref --sort refname --format='%(refname:short)' refs/remotes refs/heads; }
|
|
git_all_tags() { git for-each-ref --format='%(refname:short)' refs/tags; }
|
|
|
|
git_local_branches_prefixed() {
|
|
[ -z $1 ] && die "Prefix parameter missing." # This should never happen.
|
|
git for-each-ref --format='%(refname:short)' refs/heads/$1\* ;
|
|
}
|
|
|
|
git_current_branch() {
|
|
local branch_name
|
|
|
|
branch_name="$(git symbolic-ref --quiet HEAD)"
|
|
[ -z $branch_name ] && branch_name="(unnamed branch)" || branch_name="$(git for-each-ref --format='%(refname:short)' $branch_name)"
|
|
echo "$branch_name"
|
|
}
|
|
|
|
git_is_clean_working_tree() {
|
|
git rev-parse --verify HEAD >/dev/null || exit 1
|
|
git update-index -q --ignore-submodules --refresh
|
|
|
|
# Check for unstaged changes
|
|
git diff-files --quiet --ignore-submodules || return 1
|
|
|
|
# Check for Uncommited changes
|
|
git diff-index --cached --quiet --ignore-submodules HEAD -- || return 2
|
|
|
|
return 0
|
|
}
|
|
|
|
git_repo_is_headless() {
|
|
! git rev-parse --quiet --verify HEAD >/dev/null 2>&1
|
|
}
|
|
|
|
git_local_branch_exists() {
|
|
[ -n "$1" ] || die "Missing branch name"
|
|
[ -n "$(git for-each-ref --format='%(refname:short)' refs/heads/$1)" ]
|
|
}
|
|
|
|
git_remote_branch_exists() {
|
|
[ -n "$1" ] || die "Missing branch name"
|
|
[ -n "$(git for-each-ref --format='%(refname:short)' refs/remotes/$1)" ]
|
|
}
|
|
|
|
git_remote_branch_delete() {
|
|
[ -n "$1" ] || die "Missing branch name"
|
|
if git_remote_branch_exists "$ORIGIN/$1"; then
|
|
git_do push "$ORIGIN" :"$1" || die "Could not delete the remote $1 in $ORIGIN."
|
|
return 0
|
|
else
|
|
warn "Trying to delete the remote branch $1, but it does not exists in $ORIGIN"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
git_branch_exists() {
|
|
[ -n "$1" ] || die "Missing branch name"
|
|
git_local_branch_exists "$1" || git_remote_branch_exists "$ORIGIN/$1"
|
|
}
|
|
|
|
git_tag_exists() {
|
|
[ -n "$1" ] || die "Missing tag name"
|
|
[ -n "$(git for-each-ref --format='%(refname:short)' refs/tags/$1)" ]
|
|
}
|
|
|
|
git_config_bool_exists() {
|
|
local value
|
|
|
|
[ -n "$1" ] || die "Missing config option"
|
|
value=$(git config --get --bool $1)
|
|
[ "$value" = "true" ]
|
|
}
|
|
#
|
|
# git_compare_refs()
|
|
#
|
|
# Tests whether two references have diverged and need merging
|
|
# first. It returns error codes to provide more detail, like so:
|
|
#
|
|
# 0 References point to the same commit
|
|
# 1 First given reference needs fast-forwarding
|
|
# 2 Second given reference needs fast-forwarding
|
|
# 3 References need a real merge
|
|
# 4 There is no merge base, i.e. the references have no common ancestors
|
|
#
|
|
git_compare_refs() {
|
|
local commit1 commit2 base
|
|
|
|
commit1=$(git rev-parse "$1"^{})
|
|
commit2=$(git rev-parse "$2"^{})
|
|
if [ "$commit1" != "$commit2" ]; then
|
|
base=$(git merge-base "$commit1" "$commit2")
|
|
if [ $? -ne 0 ]; then
|
|
return 4
|
|
elif [ "$commit1" = "$base" ]; then
|
|
return 1
|
|
elif [ "$commit2" = "$base" ]; then
|
|
return 2
|
|
else
|
|
return 3
|
|
fi
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
#
|
|
# git_is_branch_merged_into()
|
|
#
|
|
# Checks whether branch $1 is successfully merged into $2
|
|
#
|
|
git_is_branch_merged_into() {
|
|
local merge_hash base_hash
|
|
|
|
merge_hash=$(git merge-base "$1"^{} "$2"^{})
|
|
base_hash=$(git rev-parse "$1"^{})
|
|
|
|
# If the hashes are equal, the branches are merged.
|
|
[ "$merge_hash" = "$base_hash" ]
|
|
}
|
|
|
|
#
|
|
# git_is_ancestor()
|
|
#
|
|
# This is the same function as git_is_branch_merged_into but
|
|
# for readability given a different name.
|
|
#
|
|
git_is_ancestor() {
|
|
git_is_branch_merged_into "$1" "$2"
|
|
}
|
|
|
|
#
|
|
# git_fetch_branch()
|
|
#
|
|
# $1 Origin - Where to fetch from
|
|
# $2 Branch - Which branch to fetch
|
|
#
|
|
# This fetches the given branch from the given origin.
|
|
# Instead of storing it in FETCH_HEAD it will be stored in
|
|
# refs/remotes/<origin>/<branch>
|
|
#
|
|
git_fetch_branch() {
|
|
local origin branch
|
|
|
|
[ -n "$1" ] || die "Missing origin"
|
|
[ -n "$2" ] || die "Missing branch name"
|
|
origin="$1"
|
|
branch="$2"
|
|
if git_remote_branch_exists "$origin/$branch"; then
|
|
git_do fetch -q "$origin" "$branch" || die "Could not fetch $branch from $origin."
|
|
else
|
|
warn "Trying to fetch branch '$origin/$branch' but it does not exist."
|
|
fi
|
|
}
|
|
|
|
#
|
|
# gitflow specific common functionality
|
|
#
|
|
|
|
# Function used to check if the repository is git-flow enabled.
|
|
gitflow_has_master_configured() {
|
|
local master
|
|
|
|
master=$(git config --get gitflow.branch.master)
|
|
[ "$master" != "" ] && git_local_branch_exists "$master"
|
|
}
|
|
|
|
gitflow_has_develop_configured() {
|
|
local develop
|
|
|
|
develop=$(git config --get gitflow.branch.develop)
|
|
[ "$develop" != "" ] && git_local_branch_exists "$develop"
|
|
}
|
|
|
|
gitflow_is_initialized() {
|
|
gitflow_has_master_configured && \
|
|
gitflow_has_develop_configured && \
|
|
[ "$(git config --get gitflow.branch.master)" != "$(git config --get gitflow.branch.develop)" ] && \
|
|
git config --get-regexp gitflow.prefix >/dev/null 2>&1
|
|
}
|
|
|
|
# Loading settings that can be overridden using git config
|
|
gitflow_load_settings() {
|
|
export GIT_CURRENT_REPO_DIR="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
DOT_GIT_DIR=$(git rev-parse --git-dir)
|
|
export DOT_GIT_DIR="$(cd ${DOT_GIT_DIR} >/dev/null 2>&1 && pwd)"
|
|
export HOOKS_DIR="$(git config --get gitflow.path.hooks || echo ${DOT_GIT_DIR}/hooks)" # the second option is used to support previous versions of git-flow
|
|
export MASTER_BRANCH=$(git config --get gitflow.branch.master)
|
|
export DEVELOP_BRANCH=$(git config --get gitflow.branch.develop)
|
|
export ORIGIN=$(git config --get gitflow.origin || echo origin)
|
|
|
|
GITFLOW_CONFIG="$DOT_GIT_DIR/gitflow_config"
|
|
if [ -f "$GITFLOW_CONFIG" ]; then # move all settings from old .git/gitflow_config to the local conf.
|
|
warn "Migrating old \"$GITFLOW_CONFIG\" to the \"--local\" repo config."
|
|
_config_lines=`git config --list --file="$GITFLOW_CONFIG"`;
|
|
for _config_line in ${_config_lines}; do
|
|
_key=${_config_line%=*}
|
|
_value=${_config_line#=*}
|
|
git_do config --local gitflow.${_key} ${_value}
|
|
done;
|
|
mv "$GITFLOW_CONFIG" "$GITFLOW_CONFIG".backup 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
#
|
|
# gitflow_resolve_nameprefix
|
|
#
|
|
# Inputs:
|
|
# $1 = name prefix to resolve
|
|
# $2 = branch prefix to use
|
|
#
|
|
# Searches branch names from git_local_branches() to look for a unique
|
|
# branch name whose name starts with the given name prefix.
|
|
#
|
|
# There are multiple exit codes possible:
|
|
# 0: The unambiguous full name of the branch is written to stdout
|
|
# (success)
|
|
# 1: No match is found.
|
|
# 2: Multiple matches found. These matches are written to stderr
|
|
#
|
|
gitflow_resolve_nameprefix() {
|
|
local name prefix
|
|
local match matches num_matches
|
|
|
|
name=$1
|
|
prefix=$2
|
|
|
|
# first, check if there is a perfect match
|
|
if git_local_branch_exists "$prefix$name"; then
|
|
echo "$name"
|
|
return 0
|
|
fi
|
|
|
|
matches=$(echo "$(git_local_branches)" | grep "^$(escape "$prefix$name")")
|
|
num_matches=$(echo "$matches" | wc -l)
|
|
if [ -z "$matches" ]; then
|
|
# no prefix match, so take it literally
|
|
warn "No branches match '$prefix$name*'"
|
|
return 1
|
|
else
|
|
if [ $num_matches -eq 1 ]; then
|
|
echo "${matches#$prefix}"
|
|
return 0
|
|
else
|
|
# multiple matches, cannot decide
|
|
warn "Multiple branches match prefix '$name':"
|
|
for match in $matches; do
|
|
warn "- $match"
|
|
done
|
|
return 2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Check if the given branch is a git-flow branch
|
|
#
|
|
gitflow_is_prefixed_branch() {
|
|
local branch return
|
|
|
|
branch=$1
|
|
case $branch in
|
|
$(git config --get gitflow.prefix.feature)* | \
|
|
$(git config --get gitflow.prefix.bugfix)* | \
|
|
$(git config --get gitflow.prefix.release)* | \
|
|
$(git config --get gitflow.prefix.hotfix)* | \
|
|
$(git config --get gitflow.prefix.support)* )
|
|
return=0
|
|
;;
|
|
*)
|
|
return=1
|
|
;;
|
|
esac
|
|
return $return
|
|
}
|
|
#
|
|
# Update the config with the base of a new git-flow branch.
|
|
#
|
|
# @param $1 Base of the new branch
|
|
# @param $2 Name of the branch
|
|
#
|
|
gitflow_config_set_base_branch() {
|
|
local base branch
|
|
|
|
base=$1
|
|
branch=$2
|
|
$(git_do config --local "gitflow.branch.$branch.base" $base)
|
|
}
|
|
|
|
#
|
|
# Get the base of a branch as set by gitflow_set_branch
|
|
#
|
|
# @param $1 Name of the branch
|
|
# @return string|empty String when a base is found otherwise empty
|
|
#
|
|
gitflow_config_get_base_branch() {
|
|
local branch
|
|
|
|
branch=$1
|
|
echo $(git config --local --get "gitflow.branch.$branch.base")
|
|
}
|
|
|
|
#
|
|
# Remove the section that contains the base of a branch as set by gitflow_set_branch
|
|
#
|
|
# @param $1 Name of the branch
|
|
#
|
|
gitflow_config_remove_base_section() {
|
|
local branch
|
|
|
|
branch=$1
|
|
$(git_do config --local --remove-section "gitflow.branch.$branch" 2>/dev/null)
|
|
}
|
|
|
|
#
|
|
# Remove the base of the git-flow branch from the.
|
|
# @param $1 Name of the branch
|
|
#
|
|
gitflow_config_remove_base_branch() {
|
|
local base
|
|
|
|
base=$1
|
|
$(git_do config --local --unset "gitflow.branch.$branch.base" 2>/dev/null)
|
|
}
|
|
|
|
#
|
|
# Remove the base of the git-flow branch from the.
|
|
# @param $1 Name of the branch
|
|
#
|
|
gitflow_config_rename_sections() {
|
|
local new
|
|
local old
|
|
|
|
old=$1
|
|
new=$2
|
|
$(git_do config --local --rename-section "gitflow.branch.$old" "gitflow.branch.$new" 2>/dev/null)
|
|
}
|
|
|
|
# gitflow_override_flag_boolean()
|
|
#
|
|
# Override a boolean flag
|
|
#
|
|
# Param $1: string The name of the config variable e.g. "feature.start.fetch"
|
|
# Param $2: string The flag name
|
|
#
|
|
gitflow_override_flag_boolean() {
|
|
local _variable
|
|
|
|
_variable=$(git config --bool --get gitflow.$1 2>&1)
|
|
case $? in
|
|
0)
|
|
[ "${_variable}" = "true" ] && eval "FLAGS_${2}=${FLAGS_TRUE}" || eval "FLAGS_${2}=${FLAGS_FALSE}"
|
|
;;
|
|
128)
|
|
die "${_variable}"
|
|
;;
|
|
esac
|
|
unset _variable
|
|
return ${FLAGS_TRUE}
|
|
}
|
|
|
|
# gitflow_override_flag_string()
|
|
#
|
|
# Override a string flag
|
|
#
|
|
# Param $1: string The name of the config variable e.g. "feature.start.fetch"
|
|
# Param $2: string The flag name
|
|
#
|
|
gitflow_override_flag_string() {
|
|
local _variable
|
|
|
|
_variable=$(git config --get gitflow.$1 2>&1)
|
|
case $? in
|
|
0)
|
|
eval "FLAGS_${2}=\"${_variable}\""
|
|
;;
|
|
esac
|
|
unset _variable
|
|
return ${FLAGS_TRUE}
|
|
}
|
|
|
|
# gitflow_create_squash_message()
|
|
#
|
|
# Create the squash message, overriding the one generated by git itself
|
|
#
|
|
# Param $1: string The line to be added
|
|
# Param $2: string The base of the branch that will me merged
|
|
# Param $3: string The branch that will be merged.
|
|
#
|
|
gitflow_create_squash_message() {
|
|
echo Squashed commit of the following:
|
|
echo
|
|
echo $1
|
|
echo
|
|
git log --no-merges --pretty=medium ^"$2" $3
|
|
}
|
|
|
|
#
|
|
# Parameter functions
|
|
#
|
|
gitflow_require_name_arg() {
|
|
if [ "$NAME" = "" ]; then
|
|
die_help "Missing argument <name>"
|
|
fi
|
|
}
|
|
|
|
gitflow_expand_nameprefix_arg() {
|
|
local expanded_name exitcode
|
|
|
|
gitflow_require_name_arg
|
|
|
|
expanded_name=$(gitflow_resolve_nameprefix "$NAME" "$PREFIX")
|
|
exitcode=$?
|
|
case $exitcode in
|
|
0)
|
|
NAME=$expanded_name
|
|
BRANCH=$PREFIX$NAME
|
|
;;
|
|
*)
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
gitflow_require_version_arg() {
|
|
if [ "$VERSION" = "" ]; then
|
|
die_help "Missing argument <version>"
|
|
fi
|
|
}
|
|
|
|
gitflow_expand_versionprefix_arg() {
|
|
local expanded_version exitcode
|
|
|
|
gitflow_require_version_arg
|
|
|
|
version=$(gitflow_resolve_nameprefix "$VERSION" "$PREFIX")
|
|
exitcode=$?
|
|
case $exitcode in
|
|
0)
|
|
VERSION=$version
|
|
BRANCH=$PREFIX$VERSION
|
|
;;
|
|
*)
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
|
|
gitflow_require_base_arg() {
|
|
if [ "$BASE" = "" ]; then
|
|
die_help "Missing argument <base>"
|
|
fi
|
|
}
|
|
|
|
gitflow_use_current_branch_name() {
|
|
local current_branch
|
|
|
|
current_branch=$(git_current_branch)
|
|
|
|
if startswith "$current_branch" "$PREFIX"; then
|
|
BRANCH=$current_branch
|
|
NAME=${BRANCH#$PREFIX}
|
|
else
|
|
warn "The current HEAD is no ${SUBCOMMAND} branch."
|
|
warn "Please specify a <name> argument."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
gitflow_use_current_branch_version() {
|
|
local current_branch
|
|
|
|
current_branch=$(git_current_branch)
|
|
|
|
if startswith "$current_branch" "$PREFIX"; then
|
|
BRANCH=$current_branch
|
|
VERSION=${BRANCH#$PREFIX}
|
|
else
|
|
warn "The current HEAD is no ${SUBCOMMAND} branch."
|
|
warn "Please specify a <version> argument."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
gitflow_rename_branch() {
|
|
# Parse arguments
|
|
FLAGS "$@" || exit $?
|
|
eval set -- "${FLAGS_ARGV}"
|
|
|
|
# read arguments into global variables
|
|
if [ -z $1 ]; then
|
|
NEW_NAME=''
|
|
else
|
|
NEW_NAME=$1
|
|
fi
|
|
|
|
if [ -z $2 ]; then
|
|
NAME=''
|
|
else
|
|
NAME=$2
|
|
fi
|
|
BRANCH=${PREFIX}${NAME}
|
|
NEW_BRANCH=${PREFIX}${NEW_NAME}
|
|
|
|
if [ -z "$NEW_NAME" ]; then
|
|
die "No new name given."
|
|
fi
|
|
|
|
# Use current branch if no name is given
|
|
if [ "$NAME" = "" ]; then
|
|
gitflow_use_current_branch_name
|
|
fi
|
|
|
|
|
|
# Sanity checks
|
|
require_branch "$BRANCH"
|
|
require_branch_absent "$NEW_BRANCH"
|
|
|
|
run_pre_hook "$NAME" "$ORIGIN" "$BRANCH"
|
|
git_do branch -m "$BRANCH" "$NEW_BRANCH" || die "Error renaming branch '$BRANCH' to '$NEW_BRANCH'"
|
|
gitflow_config_rename_sections "$BRANCH" "$NEW_BRANCH"
|
|
run_post_hook "$NAME" "$ORIGIN" "$BRANCH"
|
|
|
|
echo
|
|
echo "Summary of actions:"
|
|
echo "- Branch '$BRANCH' has been renamed to '$NEW_BRANCH'."
|
|
echo "- You are now on branch '$(git_current_branch)'"
|
|
echo
|
|
}
|
|
#
|
|
# Assertions for use in git-flow subcommands
|
|
#
|
|
|
|
require_git_repo() {
|
|
git rev-parse 2>/dev/null || die "Not a git repository"
|
|
}
|
|
|
|
require_gitflow_initialized() {
|
|
gitflow_is_initialized || die "Not a gitflow-enabled repo yet. Please run 'git flow init' first."
|
|
$(git config --get gitflow.prefix.versiontag >/dev/null 2>&1) || die "Version tag not set. Please run 'git flow init'."
|
|
}
|
|
|
|
require_clean_working_tree() {
|
|
local result
|
|
|
|
git_is_clean_working_tree
|
|
result=$?
|
|
if [ $result -eq 1 ]; then
|
|
die "Working tree contains unstaged changes. Aborting."
|
|
fi
|
|
if [ $result -eq 2 ]; then
|
|
die "Index contains uncommited changes. Aborting."
|
|
fi
|
|
}
|
|
|
|
require_base_is_local_branch() {
|
|
git_local_branch_exists "$1" || die "Base '$1' needs to be a branch. It does not exist and is required."
|
|
}
|
|
|
|
require_local_branch() {
|
|
git_local_branch_exists "$1" || die "Local branch '$1' does not exist and is required."
|
|
}
|
|
|
|
require_remote_branch() {
|
|
git_remote_branch_exists "$1" || die "Remote branch '$1' does not exist and is required."
|
|
}
|
|
|
|
require_branch() {
|
|
git_branch_exists "$1" || die "Branch '$1' does not exist and is required."
|
|
}
|
|
|
|
require_branch_absent() {
|
|
git_branch_exists "$1" && die "Branch '$1' already exists. Pick another name."
|
|
}
|
|
|
|
require_local_branch_absent() {
|
|
git_local_branch_exists "$1" && die "Branch '$1' already exists. Pick another name."
|
|
}
|
|
|
|
require_tag_absent() {
|
|
git_tag_exists "$1" && die "Tag '$1' already exists. Pick another name."
|
|
}
|
|
|
|
require_branches_equal() {
|
|
local compare_refs_result
|
|
|
|
require_local_branch "$1"
|
|
require_remote_branch "$2"
|
|
git_compare_refs "$1" "$2"
|
|
compare_refs_result=$?
|
|
|
|
if [ $compare_refs_result -gt 0 ]; then
|
|
warn "Branches '$1' and '$2' have diverged."
|
|
if [ $compare_refs_result -eq 1 ]; then
|
|
die "And branch '$1' may be fast-forwarded."
|
|
elif [ $compare_refs_result -eq 2 ]; then
|
|
# Warn here, since there is no harm in being ahead
|
|
warn "And local branch '$1' is ahead of '$2'."
|
|
else
|
|
die "Branches need merging first."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Show commands if flag is set.
|
|
#
|
|
git_do() {
|
|
if flag showcommands; then
|
|
echo "git $@" >&2
|
|
fi
|
|
|
|
git "$@"
|
|
}
|
|
|
|
#
|
|
# run_filter_hook
|
|
#
|
|
# Looks for a Git hook script called as defined by the first variable
|
|
#
|
|
# filter-flow-command
|
|
#
|
|
# If such a hook script exists and is executable, it is called with the given
|
|
# positional arguments.
|
|
#
|
|
run_filter_hook() {
|
|
local command scriptfile return
|
|
|
|
command=$1
|
|
shift
|
|
scriptfile="${HOOKS_DIR}/filter-flow-${command}"
|
|
if [ -x "$scriptfile" ]; then
|
|
return=`$scriptfile "$@"`
|
|
if [ $? -eq 127 ]; then
|
|
echo "$return"
|
|
exit 127
|
|
fi
|
|
echo $return
|
|
else
|
|
echo "$@"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# run_pre_hook
|
|
#
|
|
# Looks for a Git hook script called
|
|
#
|
|
# pre-flow-<subcmd>-<subaction>
|
|
#
|
|
# If such a hook script exists and is executable, it is called with the given
|
|
# positional arguments. If its return code non-zero, the git-flow action is
|
|
# aborted.
|
|
#
|
|
run_pre_hook() {
|
|
local scriptfile exitcode
|
|
|
|
scriptfile="${HOOKS_DIR}/pre-flow-${SUBCOMMAND}-${SUBACTION}"
|
|
exitcode=0
|
|
if [ -x "$scriptfile" ]; then
|
|
"$scriptfile" "$@"
|
|
exitcode=$?
|
|
|
|
if [ $exitcode -gt 0 ]; then
|
|
die "Hook command $scriptfile ended with exit code $exitcode."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# run_post_hook
|
|
#
|
|
# Looks for a Git hook script called
|
|
#
|
|
# post-flow-<subcmd>-<subaction>
|
|
#
|
|
# If such a hook script exists and is executable, it is called with the given
|
|
# positional arguments. Its return code is ignored.
|
|
#
|
|
run_post_hook() {
|
|
local scriptfile
|
|
|
|
scriptfile="${HOOKS_DIR}/post-flow-${SUBCOMMAND}-${SUBACTION}"
|
|
if [ -x "$scriptfile" ]; then
|
|
"$scriptfile" "$@"
|
|
fi
|
|
}
|
|
|
|
flags_help() {
|
|
eval "$( echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "-h" || echo exit $? )"
|
|
}
|