Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: f68d405cc3
Fetching contributors…

Cannot retrieve contributors at this time

496 lines (431 sloc) 13.522 kb
#
# 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.
#
require_git_repo
require_gitflow_initialized
gitflow_load_settings
PREFIX=$(git config --get gitflow.prefix.feature)
usage() {
echo "usage: git flow feature [list] [-v]"
echo " git flow feature start [-F] <name> [<base>]"
echo " git flow feature finish [-rF] <name|nameprefix>"
echo " git flow feature publish <name>"
echo " git flow feature track <name>"
echo " git flow feature diff [<name|nameprefix>]"
echo " git flow feature rebase [-i] [<name|nameprefix>]"
echo " git flow feature checkout [<name|nameprefix>]"
echo " git flow feature pull <remote> [<name>]"
}
cmd_default() {
cmd_list "$@"
}
cmd_list() {
DEFINE_boolean verbose false 'verbose (more) output' v
parse_args "$@"
local feature_branches
local current_branch
local short_names
feature_branches=$(echo "$(git_local_branches)" | grep "^$PREFIX")
if [ -z "$feature_branches" ]; then
warn "No feature branches exist."
warn ""
warn "You can start a new feature branch:"
warn ""
warn " git flow feature start <name> [<base>]"
warn ""
exit 0
fi
current_branch=$(git branch | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g')
short_names=$(echo "$feature_branches" | sed "s ^$PREFIX g")
# determine column width first
local width=0
local branch
for branch in $short_names; do
local len=${#branch}
width=$(max $width $len)
done
width=$(($width+3))
local branch
for branch in $short_names; do
local fullname=$PREFIX$branch
local base=$(git merge-base "$fullname" "$DEVELOP_BRANCH")
local develop_sha=$(git rev-parse "$DEVELOP_BRANCH")
local branch_sha=$(git rev-parse "$fullname")
if [ "$fullname" = "$current_branch" ]; then
printf "* "
else
printf " "
fi
if flag verbose; then
printf "%-${width}s" "$branch"
if [ "$branch_sha" = "$develop_sha" ]; then
printf "(no commits yet)"
elif [ "$base" = "$branch_sha" ]; then
printf "(is behind develop, may ff)"
elif [ "$base" = "$develop_sha" ]; then
printf "(based on latest develop)"
else
printf "(may be rebased)"
fi
else
printf "%s" "$branch"
fi
echo
done
}
cmd_help() {
usage
exit 0
}
require_name_arg() {
if [ "$NAME" = "" ]; then
warn "Missing argument <name>"
usage
exit 1
fi
}
expand_nameprefix_arg() {
require_name_arg
local expanded_name
local exitcode
expanded_name=$(gitflow_resolve_nameprefix "$NAME" "$PREFIX")
exitcode=$?
case $exitcode in
0) NAME=$expanded_name
BRANCH=$PREFIX$NAME
;;
*) exit 1 ;;
esac
}
use_current_feature_branch_name() {
local current_branch=$(git_current_branch)
if startswith "$current_branch" "$PREFIX"; then
BRANCH=$current_branch
NAME=${BRANCH#$PREFIX}
else
warn "The current HEAD is no feature branch."
warn "Please specify a <name> argument."
exit 1
fi
}
expand_nameprefix_arg_or_current() {
if [ "$NAME" != "" ]; then
expand_nameprefix_arg
require_branch "$PREFIX$NAME"
else
use_current_feature_branch_name
fi
}
name_or_current() {
if [ -z "$NAME" ]; then
use_current_feature_branch_name
fi
}
parse_cmdline() {
# parse options
FLAGS "$@" || exit $?
eval set -- "${FLAGS_ARGV}"
}
parse_args() {
parse_cmdline "$@"
# read arguments into global variables
NAME=$1
BRANCH=$PREFIX$NAME
}
parse_remote_name() {
parse_cmdline "$@"
# read arguments into global variables
REMOTE=$1
NAME=$2
BRANCH=$PREFIX$NAME
}
cmd_start() {
DEFINE_boolean fetch false 'fetch from origin before performing local operation' F
parse_args "$@"
BASE=${2:-$DEVELOP_BRANCH}
require_name_arg
# sanity checks
require_branch_absent "$BRANCH"
# update the local repo with remote changes, if asked
if flag fetch; then
git fetch -q "$ORIGIN" "$DEVELOP_BRANCH"
fi
# if the origin branch counterpart exists, assert that the local branch
# isn't behind it (to avoid unnecessary rebasing)
if git_branch_exists "$ORIGIN/$DEVELOP_BRANCH"; then
require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH"
fi
# create branch
if ! git checkout -b "$BRANCH" "$BASE"; then
die "Could not create feature branch '$BRANCH'"
fi
echo
echo "Summary of actions:"
echo "- A new branch '$BRANCH' was created, based on '$BASE'"
echo "- You are now on branch '$BRANCH'"
echo ""
echo "Now, start committing on your feature. When done, use:"
echo ""
echo " git flow feature finish $NAME"
echo
}
cmd_finish() {
DEFINE_boolean fetch false "fetch from $ORIGIN before performing finish" F
DEFINE_boolean rebase false 'rebase instead of merge' r
parse_args "$@"
expand_nameprefix_arg
# sanity checks
require_branch "$BRANCH"
# detect if we're restoring from a merge conflict
if [ -f "$DOT_GIT_DIR/.gitflow/MERGE_BASE" ]; then
#
# TODO: detect that we're working on the correct branch here!
# The user need not necessarily have given the same $NAME twice here
# (although he/she should).
#
# TODO: git_is_clean_working_tree() should provide an alternative
# exit code for "unmerged changes in working tree", which we should
# actually be testing for here
if git_is_clean_working_tree; then
FINISH_BASE=$(cat "$DOT_GIT_DIR/.gitflow/MERGE_BASE")
# Since the working tree is now clean, either the user did a
# succesfull merge manually, or the merge was cancelled.
# We detect this using git_is_branch_merged_into()
if git_is_branch_merged_into "$BRANCH" "$FINISH_BASE"; then
rm -f "$DOT_GIT_DIR/.gitflow/MERGE_BASE"
helper_finish_cleanup
exit 0
else
# If the user cancelled the merge and decided to wait until later,
# that's fine. But we have to acknowledge this by removing the
# MERGE_BASE file and continuing normal execution of the finish
rm -f "$DOT_GIT_DIR/.gitflow/MERGE_BASE"
fi
else
echo
echo "Merge conflicts not resolved yet, use:"
echo " git mergetool"
echo " git commit"
echo
echo "You can then complete the finish by running it again:"
echo " git flow feature finish $NAME"
echo
exit 1
fi
fi
# sanity checks
require_clean_working_tree
# update local repo with remote changes first, if asked
if flag fetch; then
git fetch -q "$ORIGIN" "$BRANCH"
fi
if has "$ORIGIN/$BRANCH" "$(git_remote_branches)"; then
require_branches_equal "$BRANCH" "$ORIGIN/$BRANCH"
fi
require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH"
# if the user wants to rebase, do that first
if flag rebase; then
if ! git flow feature rebase "$NAME" "$DEVELOP_BRANCH"; then
warn "Finish was aborted due to conflicts during rebase."
warn "Please finish the rebase manually now."
warn "When finished, re-run:"
warn " git flow feature finish '$NAME' '$DEVELOP_BRANCH'"
exit 1
fi
fi
# merge into BASE
git checkout "$DEVELOP_BRANCH"
if [ "$(git rev-list -n2 "$DEVELOP_BRANCH..$BRANCH" | wc -l)" -eq 1 ]; then
git merge --ff "$BRANCH"
else
git merge --no-ff "$BRANCH"
fi
if [ $? -ne 0 ]; then
# oops.. we have a merge conflict!
# write the given $DEVELOP_BRANCH to a temporary file (we need it later)
mkdir -p "$DOT_GIT_DIR/.gitflow"
echo "$DEVELOP_BRANCH" > "$DOT_GIT_DIR/.gitflow/MERGE_BASE"
echo
echo "There were merge conflicts. To resolve the merge conflict manually, use:"
echo " git mergetool"
echo " git commit"
echo
echo "You can then complete the finish by running it again:"
echo " git flow feature finish $NAME"
echo
exit 1
fi
# when no merge conflict is detected, just clean up the feature branch
helper_finish_cleanup
}
helper_finish_cleanup() {
# sanity checks
require_branch "$BRANCH"
require_clean_working_tree
# delete branch
if flag fetch; then
git push "$ORIGIN" ":refs/heads/$BRANCH"
fi
git branch -D "$BRANCH"
echo
echo "Summary of actions:"
echo "- The feature branch '$BRANCH' was merged into '$DEVELOP_BRANCH'"
#echo "- Merge conflicts were resolved" # TODO: Add this line when it's supported
echo "- Feature branch '$BRANCH' has been removed"
echo "- You are now on branch '$DEVELOP_BRANCH'"
echo
}
cmd_publish() {
parse_args "$@"
expand_nameprefix_arg
# sanity checks
require_clean_working_tree
require_branch "$BRANCH"
git fetch -q "$ORIGIN"
require_branch_absent "$ORIGIN/$BRANCH"
# create remote branch
git push "$ORIGIN" "$BRANCH:refs/heads/$BRANCH"
git fetch -q "$ORIGIN"
# configure remote tracking
git config "branch.$BRANCH.remote" "$ORIGIN"
git config "branch.$BRANCH.merge" "refs/heads/$BRANCH"
git checkout "$BRANCH"
echo
echo "Summary of actions:"
echo "- A new remote branch '$BRANCH' was created"
echo "- The local branch '$BRANCH' was configured to track the remote branch"
echo "- You are now on branch '$BRANCH'"
echo
}
cmd_track() {
parse_args "$@"
require_name_arg
# sanity checks
require_clean_working_tree
require_branch_absent "$BRANCH"
git fetch -q "$ORIGIN"
require_branch "$ORIGIN/$BRANCH"
# create tracking branch
git checkout -b "$BRANCH" "$ORIGIN/$BRANCH"
echo
echo "Summary of actions:"
echo "- A new remote tracking branch '$BRANCH' was created"
echo "- You are now on branch '$BRANCH'"
echo
}
cmd_diff() {
parse_args "$@"
if [ "$NAME" != "" ]; then
expand_nameprefix_arg
BASE=$(git merge-base "$DEVELOP_BRANCH" "$BRANCH")
git diff "$BASE..$BRANCH"
else
if ! git_current_branch | grep -q "^$PREFIX"; then
die "Not on a feature branch. Name one explicitly."
fi
BASE=$(git merge-base "$DEVELOP_BRANCH" HEAD)
git diff "$BASE"
fi
}
cmd_checkout() {
parse_args "$@"
if [ "$NAME" != "" ]; then
expand_nameprefix_arg
git checkout "$BRANCH"
else
die "Name a feature branch explicitly."
fi
}
cmd_co() {
# Alias for checkout
cmd_checkout "$@"
}
cmd_rebase() {
DEFINE_boolean interactive false 'do an interactive rebase' i
parse_args "$@"
expand_nameprefix_arg_or_current
warn "Will try to rebase '$NAME'..."
require_clean_working_tree
require_branch "$BRANCH"
git checkout -q "$BRANCH"
local OPTS=
if flag interactive; then
OPTS="$OPTS -i"
fi
git rebase $OPTS "$DEVELOP_BRANCH"
}
avoid_accidental_cross_branch_action() {
local current_branch=$(git_current_branch)
if [ "$BRANCH" != "$current_branch" ]; then
warn "Trying to pull from '$BRANCH' while currently on branch '$current_branch'."
warn "To avoid unintended merges, git-flow aborted."
return 1
fi
return 0
}
cmd_pull() {
#DEFINE_string prefix false 'alternative remote feature branch name prefix' p
parse_remote_name "$@"
if [ -z "$REMOTE" ]; then
die "Name a remote explicitly."
fi
name_or_current
# To avoid accidentally merging different feature branches into each other,
# die if the current feature branch differs from the requested $NAME
# argument.
local current_branch=$(git_current_branch)
if startswith "$current_branch" "$PREFIX"; then
# we are on a local feature branch already, so $BRANCH must be equal to
# the current branch
avoid_accidental_cross_branch_action || die
fi
require_clean_working_tree
if git_branch_exists "$BRANCH"; then
# Again, avoid accidental merges
avoid_accidental_cross_branch_action || die
# we already have a local branch called like this, so simply pull the
# remote changes in
git pull -q "$REMOTE" "$BRANCH" || die "Failed to pull from remote '$REMOTE'."
echo "Pulled $REMOTE's changes into $BRANCH."
else
# setup the local branch clone for the first time
git fetch -q "$REMOTE" "$BRANCH" || die "Fetch failed." # stores in FETCH_HEAD
git branch --no-track "$BRANCH" FETCH_HEAD || die "Branch failed."
git checkout -q "$BRANCH" || die "Checking out new local branch failed."
echo "Created local branch $BRANCH based on $REMOTE's $BRANCH."
fi
}
Jump to Line
Something went wrong with that request. Please try again.