| # |
| # 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/git-model |
| # |
| # Feel free to contribute to this project at: |
| # http://github.com/nvie/gitflow |
| # |
| # 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 VINCENT DRIESSEN ``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 VINCENT DRIESSEN 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. |
| # |
| # The views and conclusions contained in the software and documentation are |
| # those of the authors and should not be interpreted as representing official |
| # policies, either expressed or implied, of Vincent Driessen. |
| # |
| |
| # |
| # Common functionality |
| # |
| |
| # shell output |
| warn() { echo "$@" >&2; } |
| die() { warn "$@"; exit 1; } |
| |
| escape() { |
| echo "$1" | sed 's/\([\.\+\$\*]\)/\\\1/g' |
| } |
| |
| # set logic |
| has() { |
| local item=$1; shift |
| echo " $@ " | grep -q " $(escape $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() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; } |
| noflag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -ne $FLAGS_TRUE ]; } |
| |
| # |
| # Git specific common functionality |
| # |
| |
| git_local_branches() { git branch --no-color | sed 's/^[* ] //'; } |
| git_remote_branches() { git branch -r --no-color | sed 's/^[* ] //'; } |
| git_all_branches() { ( git branch --no-color; git branch -r --no-color) | sed 's/^[* ] //'; } |
| git_all_tags() { git tag; } |
| |
| git_current_branch() { |
| git branch --no-color | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g' |
| } |
| |
| git_is_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 |
| } |
| |
| git_repo_is_headless() { |
| ! git rev-parse --quiet --verify HEAD >/dev/null 2>&1 |
| } |
| |
| git_local_branch_exists() { |
| has $1 $(git_local_branches) |
| } |
| |
| git_branch_exists() { |
| has $1 $(git_all_branches) |
| } |
| |
| git_tag_exists() { |
| has $1 $(git_all_tags) |
| } |
| |
| # |
| # git_compare_branches() |
| # |
| # 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 |
| # 4 There is no merge base, i.e. the branches have no common ancestors |
| # |
| git_compare_branches() { |
| local commit1=$(git rev-parse "$1") |
| local commit2=$(git rev-parse "$2") |
| if [ "$commit1" != "$commit2" ]; then |
| local 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 succesfully merged into $2 |
| # |
| git_is_branch_merged_into() { |
| local subject=$1 |
| local base=$2 |
| local all_merges="$(git branch --no-color --contains $subject | sed 's/^[* ] //')" |
| has $base $all_merges |
| } |
| |
| # |
| # gitflow specific common functionality |
| # |
| |
| # check if this repo has been inited for gitflow |
| gitflow_has_master_configured() { |
| local master=$(git config --get gitflow.branch.master) |
| [ "$master" != "" ] && git_local_branch_exists "$master" |
| } |
| |
| gitflow_has_develop_configured() { |
| local develop=$(git config --get gitflow.branch.develop) |
| [ "$develop" != "" ] && git_local_branch_exists "$develop" |
| } |
| |
| gitflow_has_prefixes_configured() { |
| git config --get gitflow.prefix.feature >/dev/null 2>&1 && \ |
| git config --get gitflow.prefix.release >/dev/null 2>&1 && \ |
| git config --get gitflow.prefix.hotfix >/dev/null 2>&1 && \ |
| git config --get gitflow.prefix.support >/dev/null 2>&1 && \ |
| git config --get gitflow.prefix.versiontag >/dev/null 2>&1 |
| } |
| |
| gitflow_is_initialized() { |
| gitflow_has_master_configured && \ |
| gitflow_has_develop_configured && \ |
| [ "$(git config --get gitflow.branch.master)" != \ |
| "$(git config --get gitflow.branch.develop)" ] && \ |
| gitflow_has_prefixes_configured |
| } |
| |
| # loading settings that can be overridden using git config |
| gitflow_load_settings() { |
| export DOT_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) |
| 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_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=$1 |
| local prefix=$2 |
| local matches |
| local num_matches |
| |
| # 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 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 |
| } |
| |
| # |
| # Assertions for use in git-flow subcommands |
| # |
| |
| require_git_repo() { |
| if ! git rev-parse --git-dir >/dev/null 2>&1; then |
| die "fatal: Not a git repository" |
| fi |
| } |
| |
| require_gitflow_initialized() { |
| if ! gitflow_is_initialized; then |
| die "fatal: Not a gitflow-enabled repo yet. Please run \"git flow init\" first." |
| fi |
| } |
| |
| require_clean_working_tree() { |
| git_is_clean_working_tree |
| local 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 |
| } |
| |
| require_local_branch() { |
| if ! git_local_branch_exists $1; then |
| die "fatal: Local branch '$1' does not exist and is required." |
| fi |
| } |
| |
| require_remote_branch() { |
| if ! has $1 $(git_remote_branches); then |
| die "Remote branch '$1' does not exist and is required." |
| fi |
| } |
| |
| require_branch() { |
| if ! has $1 $(git_all_branches); then |
| die "Branch '$1' does not exist and is required." |
| fi |
| } |
| |
| require_branch_absent() { |
| if has $1 $(git_all_branches); then |
| die "Branch '$1' already exists. Pick another name." |
| fi |
| } |
| |
| require_tag_absent() { |
| for tag in $(git_all_tags); do |
| if [ "$1" = "$tag" ]; then |
| die "Tag '$1' already exists. Pick another name." |
| fi |
| done |
| } |
| |
| require_branches_equal() { |
| require_local_branch "$1" |
| require_remote_branch "$2" |
| git_compare_branches "$1" "$2" |
| local 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 |
| } |