| # |
| # git-flow -- A collection of Git extensions to provide high-level |
| # repository operations for Vincent Driessen's branching model. |
| # |
| # Original blog post presenting this model is found at: |
| # http://nvie.com/archives/323 |
| # |
| # Feel free to contribute to this project at: |
| # http://github.com/nvie/gitflow |
| # |
| # Copyright (c) 2010 by Vincent Driessen |
| # Copyright (c) 2010 by Benedikt Böhm |
| # |
| |
| # shell output |
| warn() { echo "$@" >&2; } |
| die() { warn "$@"; exit 1; } |
| |
| # set logic |
| has() { |
| typeset item=$1; shift |
| echo " $@ " | grep -q " $item " |
| } |
| |
| # 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() { typeset FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; } |
| noflag() { typeset FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -ne $FLAGS_TRUE ]; } |
| |
| # |
| # Git specific common functionality |
| # |
| |
| # get all available branches |
| LOCAL_BRANCHES=$(git branch | sed 's/^[* ] //') |
| REMOTE_BRANCHES=$(git branch -r | sed 's/^[* ] //') |
| ALL_BRANCHES="$LOCAL_BRANCHES $REMOTE_BRANCHES" |
| ALL_TAGS=$(git tag) |
| |
| # |
| # resolve_nameprefix |
| # |
| # Inputs: |
| # $1 = name prefix to resolve |
| # $2 = branch prefix to use |
| # |
| # Searches branch names from 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 |
| # |
| resolve_nameprefix() { |
| typeset name=$1 |
| typeset prefix=$2 |
| typeset matches |
| typeset -i num_matches |
| |
| # first, check if there is a perfect match |
| if has "$LOCAL_BRANCHES" "$prefix$name"; then |
| echo "$name" |
| return 0 |
| fi |
| |
| matches=$(echo "$LOCAL_BRANCHES" | grep "^$prefix$name") |
| num_matches=$(echo "$matches" | wc -l) |
| if [ -z "$matches" ]; then |
| # no prefix match, so take it literally |
| warn "No branch matches 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 |
| } |
| |
| gitflow_current_branch() { |
| git branch | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g' |
| } |
| |
| gitflow_test_clean_working_tree() { |
| if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then |
| return 1 |
| elif ! git diff-index --cached --quiet --ignore-submodules HEAD --; then |
| return 2 |
| else |
| return 0 |
| fi |
| } |
| |
| gitflow_require_clean_working_tree() { |
| gitflow_test_clean_working_tree |
| typeset -i result=$? |
| if [ $result -eq 1 ]; then |
| die "fatal: Working tree contains unstaged changes. Aborting." |
| fi |
| if [ $result -eq 2 ]; then |
| die "fatal: Index contains uncommited changes. Aborting." |
| fi |
| } |
| |
| gitflow_require_local_branch() { |
| if ! has $1 $LOCAL_BRANCHES; then |
| die "fatal: Local branch '$1' does not exist and is required." |
| fi |
| } |
| |
| gitflow_require_remote_branch() { |
| if ! has $1 $REMOTE_BRANCHES; then |
| die "Remote branch '$1' does not exist and is required." |
| fi |
| } |
| |
| gitflow_require_branch() { |
| if ! has $1 $ALL_BRANCHES; then |
| die "Branch '$1' does not exist and is required." |
| fi |
| } |
| |
| gitflow_require_branch_absent() { |
| if has $1 $ALL_BRANCHES; then |
| die "Branch '$1' already exists. Pick another name." |
| fi |
| } |
| |
| gitflow_require_tag_absent() { |
| if has $1 $ALL_TAGS; then |
| die "Tag '$1' already exists. Pick another name." |
| fi |
| } |
| |
| gitflow_tag_exists() { |
| has $1 $ALL_TAGS |
| } |
| |
| # |
| # gitflow_test_branches_equal() |
| # |
| # Tests whether branches and their "origin" counterparts have diverged and need |
| # merging first. It returns error codes to provide more detail, like so: |
| # |
| # 0 Branch heads point to the same commit |
| # 1 First given branch needs fast-forwarding |
| # 2 Second given branch needs fast-forwarding |
| # 3 Branch needs a real merge |
| # |
| gitflow_test_branches_equal() { |
| typeset commit1=$(git rev-parse "$1") |
| typeset commit2=$(git rev-parse "$2") |
| if [ "$commit1" != "$commit2" ]; then |
| typeset base=$(git merge-base "$commit1" "$commit2") |
| if [ "$commit1" = "$base" ]; then |
| return 1 |
| elif [ "$commit2" = "$base" ]; then |
| return 2 |
| else |
| return 3 |
| fi |
| else |
| return 0 |
| fi |
| } |
| |
| gitflow_require_branches_equal() { |
| gitflow_require_local_branch "$1" |
| gitflow_require_remote_branch "$2" |
| gitflow_test_branches_equal "$1" "$2" |
| typeset -i status=$? |
| if [ $status -gt 0 ]; then |
| warn "Branches '$1' and '$2' have diverged." |
| if [ $status -eq 1 ]; then |
| die "And branch '$1' may be fast-forwarded." |
| elif [ $status -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 |
| } |
| |
| # |
| # gitflow_is_branch_merged_into() |
| # |
| # Checks whether branch $1 is succesfully merged into $2 |
| # |
| gitflow_is_branch_merged_into() { |
| typeset subject=$1 |
| typeset base=$2 |
| typeset all_merges=$(git branch --contains $subject | sed 's/^[* ] //') |
| has $base $all_merges |
| } |