From bda6d69994729b555f8087194aaaa03673849dc4 Mon Sep 17 00:00:00 2001 From: Jack Wu Date: Wed, 10 Sep 2014 14:27:41 +1200 Subject: [PATCH] [ADD] init --- .gitignore | 5 + .gitmodules | 3 + AUTHORS | 23 ++ Changes.mdown | 103 ++++++ LICENSE | 26 ++ README.md | 41 +++ contrib/msysgit-install.cmd | 76 ++++ git-hf | 121 +++++++ git-hf-feature | 550 ++++++++++++++++++++++++++++ git-hf-hotfix | 465 ++++++++++++++++++++++++ git-hf-init | 357 +++++++++++++++++++ git-hf-pull | 95 +++++ git-hf-push | 100 ++++++ git-hf-release | 528 +++++++++++++++++++++++++++ git-hf-support | 187 ++++++++++ git-hf-update | 99 ++++++ git-hf-upgrade | 86 +++++ git-hf-version | 59 +++ hubflow-common | 689 ++++++++++++++++++++++++++++++++++++ hubflow-shFlags | 1 + install.sh | 100 ++++++ 21 files changed, 3714 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 AUTHORS create mode 100644 Changes.mdown create mode 100644 LICENSE create mode 100644 README.md create mode 100644 contrib/msysgit-install.cmd create mode 100755 git-hf create mode 100644 git-hf-feature create mode 100644 git-hf-hotfix create mode 100644 git-hf-init create mode 100644 git-hf-pull create mode 100644 git-hf-push create mode 100644 git-hf-release create mode 100644 git-hf-support create mode 100644 git-hf-update create mode 100644 git-hf-upgrade create mode 100644 git-hf-version create mode 100644 hubflow-common create mode 120000 hubflow-shFlags create mode 100755 install.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05a42c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +debian/files +debian/*.substvars +debian/*.debhelper.log +debian/*/* +.tags* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ab7203e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "shFlags"] + path = shFlags + url = https://github.com/nvie/shFlags.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f7d1e66 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,23 @@ +Authors are (ordered by first commit date): + +- Vincent Driessen +- Benedikt Böhm +- Daniel Truemper +- Jason L. Shiffer +- Randy Merrill +- Rick Osborne +- Mark Derricutt +- Nowell Strite +- Felipe Talavera +- Guillaume-Jean Herbiet +- Joseph A. Levin +- Jannis Leidel +- Konstantin Tjuterev +- Kiall Mac Innes +- Jon Bernard +- Olivier Mengué +- Emre Berge Ergenekon +- Eric Holmes +- Vedang Manerikar + +Portions derived from other open source works are clearly marked. diff --git a/Changes.mdown b/Changes.mdown new file mode 100644 index 0000000..2281f23 --- /dev/null +++ b/Changes.mdown @@ -0,0 +1,103 @@ +0.4.2: +----- +Release date: **not yet** + +* `git flow init` now detects situations where origin already has gitflow + branches set up, and behaves accordingly (thanks Emre Berge Ergenekon). + +* `git flow feature finish` can now be called without a feature branch + name(prefix) argument and will finish the current branch, if on any. + +* `git flow feature pull` now has a `-r` flag, to support `pull --rebase` + semantics (thanks Vedang Manerikar). + +* Various minor bug fixes related to internal argument passing. + +* Improved some documentation. + +* Better support for Windows and BSD users. + +* Add package installer for the Windows platform. + +0.4.1: +----- +Release date: **2011/02/04** + +* New option `-d` added to `git flow init`, to initialize with defaults without + asking for input interactively. Ideal for creating git-flow enabled repos in + custom scripts. + +* The parsing issues related to git-flow feature's flags are now dealt with on + all known platforms. (Fixed #54, #62, #86, #97) + +* Escape queries for detecting branch/tag names. (Fixed #91) + + +0.4: +--- +Release date: **2010/10/18** + +* The flag parsing issues of git-flow subcommands are solved for most + platforms. + +* `git flow {feature,hotfix,release} finish` now takes a `-k` flag, to keep the + branch around after finishing. + +* `git flow release finish` takes a `-n` flag, to skip tagging. + +* For consistency, `git flow {release,hotfix}` now, too, have a `publish` and + `track` subcommand, just like `feature`. + +* Various minor fixes. + + +0.3: +---- +Release date: **2010/07/22** + +* New subcommands for `git flow feature`: + - **checkout**: + For easily checking out features by their short name. Even allows + unique prefixes as arguments (see below). + + - **pull**: + This subcommand allows you to painlessly work on a feature branch + together with another peer. This is especially valuable for doing + peer reviews of other people's code. For more detailed info, see the + [commit log][1]. + +* Easier addressing of branch names by using name prefixes. + For example, when using: + + git flow feature finish fo + + this automatically finishes the feature branch `foobar` if that's the only + feature branch name starting with `fo`. + +* No force flag anymore for new feature branches + `git flow feature start` lost its `-f` (force) flag. You now don't + have to be in a clean repo anymore to start a new feature branch. This + avoids the manual `git stash`, `git flow feature start`, `git stash + pop` cycle. + +* You can use `git-flow` in stand-alone repo's now. + This means it does not assume you have an `origin` repository. + (Thanks [Mark][2].) + +* No commands fetch from `origin` by default anymore. + There were some issues related to disabling this flag on some platforms. + +* Init guesses branch names you may want to use for `develop` and `master`. + +* Added super-easy installation script. (Thanks [Rick][3].) + +* Added BSD license. + +[1]: http://github.com/nvie/gitflow/commit/f68d405cc3a11e9df3671f567658a6ab6ed8e0a1 +[2]: http://github.com/talios +[3]: http://github.com/rickosborne + + +Older versions +-------------- +No change history is recorded for pre-0.3 releases. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cedd182 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a6b634 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +HubFlow +======= + +Adds the 'git hf' Git extension to provide high-level repository operations +for [DataSift's HubFlow branching model](http://datasift.github.com/gitflow/), which is based on [Vincent Driessen’s original blog post](http://nvie.com/posts/a-successful-git-branching-model/). + +![](http://nvie.com/img/2009/12/Screen-shot-2009-12-24-at-11.32.03.png) + +Installation +------------ + +1. `git clone git@github.com:datasift/gitflow.git` +2. `cd gitflow` +3. `sudo ./install.sh` + +Windows users will need something like Cygwin in order to use this extension. + +Upgrading To The Latest Version +------------------------------- + +Upgrading to the latest version of the HubFlow tools is very easy: + +1. `sudo git hf upgrade` + +Getting Started +--------------- + +See our tutorial website to learn more about the [GitFlow](http://datasift.github.com/gitflow/IntroducingGitFlow.html) branching model and [how to use the HubFlow tools](http://datasift.github.com/gitflow/GitFlowForGitHub.html). + +Changelog +--------- + +To see what's new in each release, see our [Changelog](http://datasift.github.com/gitflow/ChangeLog.html). + +License Terms +------------- +HubFlow is published under the liberal terms of the BSD License, see the +[LICENSE](LICENSE) file. Although the BSD License does not require you to share +any modifications you make to the source code, you are very much encouraged and +invited to contribute back your modifications to the community, preferably +in a Github fork, of course. diff --git a/contrib/msysgit-install.cmd b/contrib/msysgit-install.cmd new file mode 100644 index 0000000..5f005bf --- /dev/null +++ b/contrib/msysgit-install.cmd @@ -0,0 +1,76 @@ +@echo off +setlocal +if not "%~1"=="" set GIT_HOME=%~f1 +if "%GIT_HOME%"=="" call :FindGitHome "git.cmd" + +if exist "%GIT_HOME%" goto :GitHomeOK + +echo MsysGit installation directory not found.>&2 +echo Try to give the directory name on the command line:>&2 +echo %0 "%ProgramFiles%\Git" +endlocal +exit /B 1 + +:GitHomeOK +set ERR=0 + +echo Installing gitflow into "%GIT_HOME%"... + +call :ChkGetopt getopt.exe || set ERR=1 +if %ERR%==1 goto :End +echo getopt.exe... Found + +if not exist "%GIT_HOME%\bin\git-flow" goto :Install +echo GitFlow is already installed.>&2 +set /p mychoice="Do you want to replace it [y/n]" +if "%mychoice%"=="y" goto :DeleteOldFiles +goto :Abort + +:DeleteOldFiles +echo Deleting old files... +for /F %%i in ("%GIT_HOME%\git-flow*" "%GIT_HOME%\gitflow-*") do if exist "%%~fi" del /F /Q "%%~fi" + +:Install +echo Copying files... +::goto :EOF +xcopy "%~dp0\..\git-flow" "%GIT_HOME%\bin" /Y /R /F +if errorlevel 4 if not errorlevel 5 goto :AccessDenied +if errorlevel 1 set ERR=1 +xcopy "%~dp0\..\git-flow*" "%GIT_HOME%\bin" /Y /R /F || set ERR=1 +xcopy "%~dp0\..\gitflow-*" "%GIT_HOME%\bin" /Y /R /F || set ERR=1 +xcopy "%~dp0\..\shFlags\src\shflags" "%GIT_HOME%\bin\gitflow-shFlags" /Y /R /F || set ERR=1 + +if %ERR%==1 choice /T 30 /C Y /D Y /M "Some unexpected errors happened. Sorry, you'll have to fix them by yourself." + +:End +endlocal & exit /B %ERR% +goto :EOF + +:AccessDenied +set ERR=1 +echo. +echo You should run this script with "Full Administrator" rights:>&2 +echo - Right-click with Shift on the script from the Explorer>&2 +echo - Select "Run as administrator">&2 +choice /T 30 /C YN /D Y /N >nul +goto :End + +:Abort +echo Installation canceled.>&2 +set ERR=1 +goto :End + +:ChkGetopt +:: %1 is getopt.exe +if exist "%GIT_HOME%\bin\%1" goto :EOF +if exist "%~f$PATH:1" goto :EOF +echo %GIT_HOME%\bin\%1 not found.>&2 +echo You have to install this file manually. See the GitFlow README. +exit /B 1 + +:FindGitHome +setlocal +set GIT_CMD_DIR=%~dp$PATH:1 +if "%GIT_CMD_DIR%"=="" endlocal & goto :EOF +endlocal & set GIT_HOME=%GIT_CMD_DIR:~0,-5% +goto :EOF diff --git a/git-hf b/git-hf new file mode 100755 index 0000000..f114a60 --- /dev/null +++ b/git-hf @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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. +# + +# set this to workaround expr problems in shFlags on freebsd +if uname -s | egrep -iq 'bsd'; then export EXPR_COMPAT=1; fi + +# enable debug mode +if [ "$DEBUG" = "yes" ]; then + set -x +fi + +# The sed expression here replaces all backslashes by forward slashes. +# This helps our Windows users, while not bothering our Unix users. +export HUBFLOW_DIR=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +usage() { + echo "usage: git hf " + echo + echo "Available subcommands are:" + echo " init Initialize a new git repo with support for the branching model." + echo " feature Manage your feature branches." + echo " release Manage your release branches." + echo " hotfix Manage your hotfix branches." + echo " push Push the changes from your current branch (plus any new tags) back upstream." + echo " pull Pull upstream changes down into your master, develop, and current branches." + echo " update Pull upstream changes down into your master and develop branches." + echo " version Shows version information." + echo + echo "Try 'git hf help' for details." +} + +main() { + if [ $# -lt 1 ]; then + usage + exit 1 + fi + + # load common functionality + . "$HUBFLOW_DIR/hubflow-common" + + # This environmental variable fixes non-POSIX getopt style argument + # parsing, effectively breaking git-flow subcommand parsing on several + # Linux platforms. + export POSIXLY_CORRECT=1 + + # use the shFlags project to parse the command line arguments + . "$HUBFLOW_DIR/hubflow-shFlags" + FLAGS_PARENT="git hf" + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # sanity checks + SUBCOMMAND="$1"; shift + + if [ ! -e "$HUBFLOW_DIR/git-hf-$SUBCOMMAND" ]; then + usage + exit 1 + fi + + # run command + . "$HUBFLOW_DIR/git-hf-$SUBCOMMAND" + FLAGS_PARENT="git hf $SUBCOMMAND" + + # test if the first argument is a flag (i.e. starts with '-') + # in that case, we interpret this arg as a flag for the default + # command + SUBACTION="default" + if [ "$1" != "" ] && { ! echo "$1" | grep -q "^-"; } then + SUBACTION="$1"; shift + fi + if ! type "cmd_$SUBACTION" >/dev/null 2>&1; then + warn "Unknown subcommand: '$SUBACTION'" + usage + exit 1 + fi + + # run the specified action + cmd_$SUBACTION "$@" +} + +main "$@" diff --git a/git-hf-feature b/git-hf-feature new file mode 100644 index 0000000..22eec58 --- /dev/null +++ b/git-hf-feature @@ -0,0 +1,550 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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_hubflow_initialized +hubflow_load_settings +PREFIX=$(git config --get hubflow.prefix.feature) + +usage() { + echo "usage: git hf feature [list] [-v]" + echo " git hf feature start []" + echo " git hf feature submit [] []" + echo " git hf feature finish [-rFkD] []" + echo " git hf feature track " + echo " git hf feature diff []" + echo " git hf feature rebase [-i] []" + echo " git hf feature checkout []" + echo " git hf feature pull [-r] [ []]" + echo " git hf feature push [ []]" + echo " git hf feature cancel [-f] " +} + +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 hf feature start []" + warn "" + exit 0 + fi + current_branch=$(git branch --no-color | 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 " + usage + exit 1 + fi +} + +expand_nameprefix_arg() { + require_name_arg + + local expanded_name + local exitcode + track_repo=$(hubflow_track_repo "$NAME" "$PREFIX" "$ORIGIN") + expanded_name=$(hubflow_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 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_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # read arguments into global variables + NAME=$1 + BRANCH=$PREFIX$NAME +} + +parse_remote_name() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # read arguments into global variables + REMOTE=$1 + NAME=$2 + BRANCH=$PREFIX$NAME +} + +cmd_start() { + DEFINE_boolean fetch true 'fetch from $ORIGIN before creating the new branch' F + parse_args "$@" + BASE=${2:-$DEVELOP_BRANCH} + require_name_arg + + # sanity checks + require_clean_working_tree + require_remote_available + if flag fetch ; then + hubflow_fetch_latest_changes_from_origin + fi + require_branch_absent "$ORIGIN/$BRANCH" + + # 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 + + # push it back up to remote repo + hubflow_push_latest_changes_to_origin + + echo + echo "Summary of actions:" + echo "- A new branch '$BRANCH' was created, based on '$BASE'" + echo "- The branch '$BRANCH' has been pushed up to '$ORIGIN/$BRANCH'" + echo "- You are now on branch '$BRANCH'" + echo "" + echo "Now, start committing on your feature. When done, create a" + echo "pull request on GitHub. Once that has been merged, use:" + echo "" + echo " git hf feature finish $NAME" + echo +} + +cmd_submit() { + parse_args "$@" + BASE=${2:-$DEVELOP_BRANCH} + name_or_current + + # sanity checks + require_branch "$BRANCH" + require_clean_working_tree + require_github_origin_repo + require_remote_available + hubflow_fetch_latest_changes_from_origin + + # push to origin + hubflow_push_latest_changes_to_origin + + # pull request details + PR_FILE="./COMMIT_MSG" + PR_TITLE= + PR_DESC= + + rm -f "$PR_FILE" + + # ask the user for a pull request description + cat < "$PR_FILE" + +# Please enter the description for your pull request. Lines starting +# with '#' will be ignored, and an empty message aborts the request. +# +# The first line should be a short feature summary, no longer than +# 72 characters. +# +# The subsequent lines should be a longer description of the feature, +# including a summary of backwards-compatibility breaks, wrapped at +# 80 characters. +EOS + ${VISUAL:-${EDITOR:-vi}} "$PR_FILE" + + # extract pull request parameters from description + if [ -r "$PR_FILE" ]; then + PR_TITLE=$(head -n1 "$PR_FILE" | sed -e 's/^[[:space:]]+//' -e 's/"/\\"/g;' ) + PR_DESC=$(tail -n+2 "$PR_FILE" | grep -v '^#' | sed -e 's/"/\\"/g;' | tr '\n' '\000' | sed -e 's/\x00/\\n/g;' ) + fi + + # ensure there's an adequate description + if [ -z "$PR_TITLE" ]; then + die "Aborting submission due to empty summary." + elif [ -z "$PR_DESC" ]; then + warn "You have left the description empty; the review may decide to reject your" + warn "pull request because of this." + fi + + # submit pull request to GitHub and + resp=$(github_post \ + "/repos/$GITHUB_ORIGIN/pulls" \ + "{\"title\":\"$PR_TITLE\",\"body\":\"$PR_DESC\",\"head\":\"$BRANCH\",\"base\":\"$BASE\"}") + + # did it succeed? + if echo "$resp" | grep "Validation Failed" > /dev/null ; then + # no, it did not + if echo "$resp" | grep "pull request already exists" > /dev/null ; then + die "A pull request already exists for this feature" + elif echo "$resp" | grep "No commits between" > /dev/null ; then + die "You need to make some commits for this feature before you can make a pull request" + else + warn "An unexpected error was returned from GitHub. Here is the raw response:" + warn + echo "$resp" + exit 1 + fi + fi + + # parse Pull Request URL from response + PR_URL=$(echo $resp | + awk -F"," '{for(i=1;i<=NF;i++){if($i~/html_url/){print $i"\n"}}}' | + grep 'pull' | + awk -F"\":" '{print $2}' | + awk -F"\"" '{print $2}') + + if [ -z "$PR_URL" ]; then + die "Failed to create Pull Request" + fi + + echo + echo "Summary of actions:" + echo "- The branch '$BRANCH' was pushed to '$ORIGIN'" + echo "- A Pull Request from '$BRANCH' to '$BASE' was created at '$PR_URL'" + echo "" + echo "Once the Pull Request has been accepted, cleanup the feature with:" + echo "" + echo " git hf feature finish $NAME" + echo +} + +cmd_finish() { + DEFINE_boolean fetch true "fetch from $ORIGIN before performing finish" F + DEFINE_boolean rebase false "rebase instead of merge" r + DEFINE_boolean keep false "keep branch after performing finish" k + DEFINE_boolean force_delete false "force delete feature branch after finish" D + DEFINE_boolean force_merge false "force merge of feature branch if not merged yet at origin" f + parse_args "$@" + expand_nameprefix_arg_or_current + + # sanity checks + require_branch "$BRANCH" + require_clean_working_tree + require_remote_available + + # update local repo with remote changes first, if asked + if flag fetch; then + # fetch and merge the latest changes from origin + hubflow_merge_latest_changes_from_origin + fi + + if has "$ORIGIN/$BRANCH" $(git_remote_branches); then + require_branches_equal "$BRANCH" "$ORIGIN/$BRANCH" + fi + if has "$ORIGIN/$DEVELOP_BRANCH" $(git_remote_branches); then + require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH" + fi + + # make sure that the feature branch has been merged into develop + if noflag force_merge ; then + if [[ $(git rev-list -n2 "$DEVELOP_BRANCH..$BRANCH") ]] ; then + echo + echo "Feature branch has not yet been merged into $ORIGIN/$DEVELOP_BRANCH." + echo "Please raise a pull-request via GitHub first, or use the -f flag." + exit 1 + fi + fi + + # if the user wants to rebase, do that first + if flag rebase; then + if ! git hf 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 hf feature finish '$NAME' '$DEVELOP_BRANCH'" + exit 1 + fi + fi + + # merge into the develop branch + # the merge_helper will not return if there is a merge conflict + # we want to remain on the DEVELOP_BRANCH after the merge is complete + hubflow_local_merge_helper "$BRANCH" "$DEVELOP_BRANCH" no_checkout_afterwards + + # make sure the merge worked + if [[ $(git rev-list -n2 "$DEVELOP_BRANCH..$BRANCH") ]] ; then + die "feature merge failed" + fi + + # when no merge conflict is detected, just clean up the feature branch + # delete branch + if flag fetch; then + git push "$ORIGIN" ":refs/heads/$BRANCH" + fi + + # switch to the develop branch + hubflow_change_branch "$DEVELOP_BRANCH" + + # if we merged locally, push those changes up to origin + hubflow_push_latest_changes_to_origin + + # delete the local branch if it is no longer needed + if noflag keep; then + if flag force_delete; then + git branch -D "$BRANCH" + else + git branch -d "$BRANCH" + fi + fi + + echo + echo "Summary of actions:" + if flag fetch ; then + echo "- The latest changes from '$ORIGIN' were merged into '$MASTER_BRANCH' and '$DEVELOP_BRANCH'" + fi + echo "- The feature branch '$BRANCH' was merged into '$DEVELOP_BRANCH'" + if flag keep; then + echo "- Feature branch '$BRANCH' is still available" + else + echo "- Feature branch '$BRANCH' has been removed" + fi + if flag fetch; then + echo "- Feature branch '$ORIGIN/$BRANCH' has been removed" + fi + echo "- You are now on branch '$DEVELOP_BRANCH'" +} + +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, hubflow aborted." + return 1 + fi + return 0 +} + +cmd_pull() { + git hf pull "$@" +} + +cmd_push() { + git hf push "$@" +} + +cmd_cancel() { + DEFINE_boolean fetch true "fetch from $ORIGIN before performing cancel" F + DEFINE_boolean push true "push to $ORIGIN after performing cancel" p + DEFINE_boolean keep false "keep branch after performing cancel" k + DEFINE_boolean force false "safety feature; cannot cancel a feature without this flag" f + + parse_args "$@" + name_or_current + + # has the user chosen the force flag? + if noflag force ; then + warn "To prevent you accidentally cancelling a feature, you _must_ use the -f flag" + warn "with this command" + exit 1 + fi + + # sanity checks + require_branch "$BRANCH" + require_clean_working_tree + if flag push ; then + git push "$ORIGIN" "$BRANCH" || die "Could not push feature branch up to $ORIGIN" + fi + + # delete the remote branch + if flag push ; then + git push "$ORIGIN" :"$BRANCH" || \ + die "Could not delete the remote $BRANCH in $ORIGIN." + fi + + # delete the local branch + if noflag keep ; then + if [ "$BRANCH" = "$(git_current_branch)" ]; then + git checkout "$DEVELOP_BRANCH" + fi + + git branch -D "$BRANCH" + fi + + echo + echo "Summary of actions:" + if flag push ; then + echo "- Latest objects have been fetched from '$ORIGIN'" + fi + if flag push ; then + echo "- Feature branch '$BRANCH' in '$ORIGIN' has been deleted." + fi + if flag keep ; then + echo "- Feature branch '$BRANCH' is still available locally" + else + echo "- Feature branch '$BRANCH' has been deleted locally" + fi + echo +} diff --git a/git-hf-hotfix b/git-hf-hotfix new file mode 100644 index 0000000..cf5aae6 --- /dev/null +++ b/git-hf-hotfix @@ -0,0 +1,465 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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_hubflow_initialized +hubflow_load_settings +VERSION_PREFIX=$(eval "echo `git config --get hubflow.prefix.versiontag`") +PREFIX=$(git config --get hubflow.prefix.hotfix) + +usage() { + echo "usage: git hf hotfix [list] [-v]" + echo " git hf hotfix start []" + echo " git hf hotfix finish [-sumpk] " + echo " git hf hotfix track " + echo " git hf hotfix pull [-r] [ []]" + echo " git hf hotfix push [ []]" + echo " git hf hotfix cancel [-f] " +} + +cmd_default() { + cmd_list "$@" +} + +cmd_list() { + DEFINE_boolean verbose false 'verbose (more) output' v + parse_args "$@" + + local hotfix_branches + local current_branch + local short_names + hotfix_branches=$(echo "$(git_local_branches)" | grep "^$PREFIX") + if [ -z "$hotfix_branches" ]; then + warn "No hotfix branches exist." + warn "" + warn "You can start a new hotfix branch:" + warn "" + warn " git hf hotfix start []" + warn "" + exit 0 + fi + current_branch=$(git branch --no-color | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g') + short_names=$(echo "$hotfix_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" "$MASTER_BRANCH") + local master_sha=$(git rev-parse "$MASTER_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" = "$master_sha" ]; then + printf "(no commits yet)" + else + local tagname=$(git name-rev --tags --no-undefined --name-only "$base") + local nicename + if [ "$tagname" != "" ]; then + nicename=$tagname + else + nicename=$(git rev-parse --short "$base") + fi + printf "(based on $nicename)" + fi + else + printf "%s" "$branch" + fi + echo + done +} + +cmd_help() { + usage + exit 0 +} + +name_or_current() { + if [ -z "$NAME" ]; then + use_current_hotfix_branch_name + fi +} + +parse_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # read arguments into global variables + VERSION=$1 + BRANCH=$PREFIX$VERSION +} + +parse_remote_name() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # read arguments into global variables + REMOTE=$1 + NAME=$2 + BRANCH=$PREFIX$VERSION +} + +require_version_arg() { + if [ "$VERSION" == "" ]; then + warn "Missing argument " + usage + exit 1 + fi +} + +require_base_is_on_master() { + if ! git branch --no-color --contains "$BASE" 2>/dev/null \ + | sed 's/[* ] //g' \ + | grep -q "^$MASTER_BRANCH\$"; then + die "fatal: Given base '$BASE' is not a valid commit on '$MASTER_BRANCH'." + fi +} + +require_no_existing_hotfix_branches() { + local hotfix_branches=$(echo "$(git_local_branches)" | grep "^$PREFIX") + local first_branch=$(echo ${hotfix_branches} | head -n1) + first_branch=${first_branch#$PREFIX} + [ -z "$hotfix_branches" ] || \ + die "There is an existing hotfix branch ($first_branch). Finish that one first." +} + +use_current_hotfix_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 not a hotfix branch." + warn "Please specify a argument." + exit 1 + fi +} + +cmd_start() { + DEFINE_boolean fetch true "fetch from $ORIGIN before creating the new branch" F + parse_args "$@" + BASE=${2:-$MASTER_BRANCH} + require_version_arg + + # sanity checks + require_clean_working_tree + require_remote_available + if flag fetch ; then + hubflow_fetch_latest_changes_from_origin + fi + require_base_is_on_master + require_no_existing_hotfix_branches + require_branch_absent "$BRANCH" + require_tag_absent "$VERSION_PREFIX$VERSION" + if has "$ORIGIN/$MASTER_BRANCH" $(git_remote_branches); then + require_branches_equal "$MASTER_BRANCH" "$ORIGIN/$MASTER_BRANCH" + fi + + # create branch + git checkout -b "$BRANCH" "$BASE" + + # push it back up to remote repo + hubflow_push_latest_changes_to_origin + + echo + echo "Summary of actions:" + echo "- A new branch '$BRANCH' was created, based on '$BASE'" + echo "- The branch '$BRANCH' has been pushed up to '$ORIGIN/$BRANCH'" + echo "- You are now on branch '$BRANCH'" + echo + echo "Follow-up actions:" + echo "- Bump the version number now!" + echo "- Start committing your hot fixes" + echo "- When done, run:" + echo + echo " git hf hotfix finish '$VERSION'" + echo +} + +cmd_finish() { + DEFINE_boolean fetch true "fetch from $ORIGIN before performing finish" F + DEFINE_boolean sign false "sign the release tag cryptographically" s + DEFINE_string signingkey "" "use the given GPG-key for the digital signature (implies -s)" u + DEFINE_string message "" "use the given tag message" m + DEFINE_boolean push true "push to $ORIGIN after performing finish" p + DEFINE_boolean keep false "keep branch after performing finish" k + DEFINE_boolean notag false "don't tag this release" n + DEFINE_boolean nobackmerge false "don't back-merge $MASTER_BRANCH to be a parent of $DEVELOP_BRANCH (using tag if applicable)" b parse_args "$@" + parse_args "$@" + require_version_arg + + # handle flags that imply other flags + if [ "$FLAGS_signingkey" != "" ]; then + FLAGS_sign=$FLAGS_TRUE + fi + + # sanity checks + require_branch "$BRANCH" + require_clean_working_tree + require_remote_available + + # update local repo with remote changes first, if asked + if flag fetch; then + # fetch and merge the latest changes from origin + hubflow_merge_latest_changes_from_origin + fi + + # we must be up to date before continuing + if has "$ORIGIN/$MASTER_BRANCH" $(git_remote_branches); then + require_branches_equal "$MASTER_BRANCH" "$ORIGIN/$MASTER_BRANCH" + fi + if has "$ORIGIN/$DEVELOP_BRANCH" $(git_remote_branches); then + require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH" + fi + + # try to merge into master + # in case a previous attempt to finish this release branch has failed, + # but the merge into master was successful, we skip it now + if ! git_is_branch_merged_into "$BRANCH" "$MASTER_BRANCH"; then + git checkout "$MASTER_BRANCH" || \ + die "Could not check out $MASTER_BRANCH." + git merge --no-ff -srecursive -Xtheirs "$BRANCH" || \ + die "There were merge conflicts." + # TODO: What do we do now? + fi + + if noflag notag; then + # try to tag the release + # in case a previous attempt to finish this release branch has failed, + # but the tag was set successful, we skip it now + local tagname=$VERSION_PREFIX$VERSION + if ! git_tag_exists "$tagname"; then + local opts="-a" + flag sign && opts="$opts -s" + [ "$FLAGS_signingkey" != "" ] && opts="$opts -u '$FLAGS_signingkey'" + [ "$FLAGS_message" != "" ] && opts="$opts -m '$FLAGS_message'" + eval git tag $opts "$VERSION_PREFIX$VERSION" || \ + die "Tagging failed. Please run finish again to retry." + fi + fi + + # try to merge into develop + if noflag nobackmerge; then + # in case a previous attempt to finish this hotfix branch has failed, + # but the merge into develop was successful, we skip it now + if ! git_is_branch_merged_into "$MASTER_BRANCH" "$DEVELOP_BRANCH"; then + git checkout "$DEVELOP_BRANCH" || \ + die "Could not check out $DEVELOP_BRANCH." + # merge the master branch back into develop; this makes the master + # branch - and the new tag (if provided) - a parent of the development + # branch, which in turn lets you use 'git describe' on either branch + if noflag notag; then + git merge --no-ff "$tagname" || \ + die "There were merge conflicts." + else + git merge --no-ff "$MASTER_BRANCH" || \ + die "There were merge conflicts." + fi + fi + else + # in case a previous attempt to finish this hotfix branch has failed, + # but the merge into develop was successful, we skip it now + if ! git_is_branch_merged_into "$BRANCH" "$DEVELOP_BRANCH"; then + git checkout "$DEVELOP_BRANCH" || \ + die "Could not check out $DEVELOP_BRANCH." + # just merge the release branch into the development branch + git merge --no-ff "$BRANCH" || \ + die "There were merge conflicts." + fi + fi + + # delete branch + if noflag keep; then + # delete the remote branch first + if flag push ; then + git push "$ORIGIN" :"$BRANCH" || \ + die "Could not delete remote branch" + fi + + # delete the local branch afterwards + git branch -d "$BRANCH" + fi + + if flag push; then + git push "$ORIGIN" "$DEVELOP_BRANCH" || \ + die "Could not push to $DEVELOP_BRANCH from $ORIGIN." + git push "$ORIGIN" "$MASTER_BRANCH" || \ + die "Could not push to $MASTER_BRANCH from $ORIGIN." + if noflag notag; then + git push --tags "$ORIGIN" || \ + die "Could not push tags to $ORIGIN." + fi + fi + + echo + echo "Summary of actions:" + echo "- Latest objects have been fetched from '$ORIGIN'" + echo "- Hotfix branch has been merged into '$MASTER_BRANCH'" + if noflag notag; then + echo "- The hotfix was tagged '$VERSION_PREFIX$VERSION'" + fi + echo "- Hotfix branch has been back-merged into '$DEVELOP_BRANCH'" + if flag keep; then + echo "- Hotfix branch '$BRANCH' is still available" + else + echo "- Hotfix branch '$BRANCH' has been deleted" + fi + if flag push; then + echo "- '$DEVELOP_BRANCH', '$MASTER_BRANCH' and tags have been pushed to '$ORIGIN'" + fi + echo +} + +cmd_cancel() { + DEFINE_boolean fetch true "fetch from $ORIGIN before performing cancel" F + DEFINE_string message "" "use the given tag message" m + DEFINE_boolean push true "push to $ORIGIN after performing cancel" p + DEFINE_boolean keep false "keep branch after performing cancel" k + DEFINE_boolean force false "safety feature; cannot cancel a hotfix without this flag" f + DEFINE_boolean discard true "drop the changes in this hotfix; do not merge back into develop" d + + parse_args "$@" + require_version_arg + + # has the user chosen the force flag? + if noflag force ; then + warn "To prevent you accidentally cancelling a hotfix, you _must_ use the -f flag" + warn "with this command" + exit 1 + fi + + # sanity checks + require_branch "$BRANCH" + require_clean_working_tree + if flag push ; then + git push "$ORIGIN" "$BRANCH" || die "Could not push hotfix branch up to $ORIGIN" + fi + + # we only merge into develop if the user hasn't selected the -d flag + if noflag discard ; then + if flag fetch ; then + git fetch -q "$ORIGIN" "$DEVELOP_BRANCH" || \ + die "Could not fetch $DEVELOP_BRANCH from $ORIGIN." + fi + if has "$ORIGIN/$DEVELOP_BRANCH" $(git_remote_branches); then + require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH" + fi + + # try to merge into develop + # + # in case a previous attempt to finish this release branch has failed, + # but the merge into develop was successful, we skip it now + if ! git_is_branch_merged_into "$BRANCH" "$DEVELOP_BRANCH"; then + git checkout "$DEVELOP_BRANCH" || \ + die "Could not check out $DEVELOP_BRANCH." + # just merge the release branch into the development branch + git merge --no-ff "$BRANCH" || \ + die "There were merge conflicts." + fi + + # push back to remote repo + if flag push ; then + git push "$ORIGIN" "$DEVELOP_BRANCH" || \ + die "Could not push to $DEVELOP_BRANCH from $ORIGIN." + fi + fi + + # delete the remote branch + if flag push ; then + git push "$ORIGIN" :"$BRANCH" || \ + die "Could not delete the remote $BRANCH in $ORIGIN." + fi + + # delete local branch + if noflag keep ; then + if [ "$BRANCH" = "$(git_current_branch)" ]; then + git checkout "$DEVELOP_BRANCH" + fi + + git branch -D "$BRANCH" + fi + + echo + echo "Summary of actions:" + if flag push ; then + echo "- Latest objects have been fetched from '$ORIGIN'" + fi + if noflag discard ; then + echo "- Hotfix branch has been merged into '$DEVELOP_BRANCH'" + fi + if noflag discard && flag push ; then + echo "- '$DEVELOP_BRANCH' has been pushed to '$ORIGIN'" + fi + if flag push ; then + echo "- Hotfix branch '$BRANCH' in '$ORIGIN' has been deleted." + fi + if flag keep ; then + echo "- Hotfix branch '$BRANCH' is still available locally" + else + echo "- Hotfix branch '$BRANCH' has been deleted locally" + fi + + echo +} + +cmd_pull() { + git hf pull "$@" +} + +cmd_push() { + git hf push "$@" +} diff --git a/git-hf-init b/git-hf-init new file mode 100644 index 0000000..aca4310 --- /dev/null +++ b/git-hf-init @@ -0,0 +1,357 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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. +# + +usage() { + echo "usage: git hf init [-af]" +} + +parse_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" +} + +# Default entry when no SUBACTION is given +cmd_default() { + DEFINE_boolean force false 'force setting of hubflow branches, even if already configured' f + DEFINE_boolean ask false 'ask for branch naming conventions' a + parse_args "$@" + + if ! git rev-parse --git-dir >/dev/null 2>&1; then + git init + else + # assure that we are not working in a repo with local changes + git_repo_is_headless || require_clean_working_tree + fi + + # running git hf init on an already initialized repo is fine + if hubflow_is_initialized && ! flag force; then + warn "Already initialized for hubflow." + warn "To force reinitialization, use: git hf init -f" + exit 0 + fi + + local branch_count + local answer + + if noflag ask; then + warn "Using default branch names." + fi + + # add a master branch if no such branch exists yet + local master_branch + if hubflow_has_master_configured && ! flag force; then + master_branch=$(git config --get hubflow.branch.master) + else + # Two cases are distinguished: + # 1. A fresh git repo (without any branches) + # We will create a new master/develop branch for the user + # 2. Some branches do already exist + # We will disallow creation of new master/develop branches and + # rather allow to use existing branches for hubflow. + local default_suggestion + local should_check_existence + branch_count=$(git_local_branches | wc -l) + if [ "$branch_count" -eq 0 ]; then + echo "No branches exist yet. Base branches must be created now." + should_check_existence=NO + default_suggestion=$(git config --get hubflow.branch.master || echo master) + else + echo + echo "Which branch should be used for tracking production releases?" + git_local_branches | sed 's/^.*$/ - &/g' + + should_check_existence=YES + default_suggestion= + for guess in $(git config --get hubflow.branch.master) \ + 'production' 'main' 'master'; do + if git_local_branch_exists "$guess"; then + default_suggestion="$guess" + break + fi + done + + if [ -z "$default_suggestion" ] ; then + for guess in $(git config --get hubflow.branch.master) \ + 'production' 'main' 'master'; do + if git_remote_branch_exists "origin/$guess"; then + default_suggestion="$guess" + break + fi + done + fi + + # if we have no default at this point, use 'master' + if [ -z "$default_suggestion" ] ; then + default_suggestion="master" + should_check_existence="NO" + fi + fi + + printf "Branch name for production releases: [$default_suggestion] " + if flag ask; then + read answer + else + printf "\n" + fi + master_branch=${answer:-$default_suggestion} + + # check existence in case of an already existing repo + if [ "$should_check_existence" = "YES" ]; then + # if no local branch exists and a remote branch of the same + # name exists, checkout that branch and use it for master + if ! git_local_branch_exists "$master_branch" && \ + git_remote_branch_exists "origin/$master_branch"; then + git branch "$master_branch" "origin/$master_branch" >/dev/null 2>&1 + elif ! git_local_branch_exists "$master_branch"; then + die "Local branch '$master_branch' does not exist." + fi + fi + + # store the name of the master branch + git config hubflow.branch.master "$master_branch" + fi + + # add a develop branch if no such branch exists yet + local develop_branch + if hubflow_has_develop_configured && ! flag force; then + develop_branch=$(git config --get hubflow.branch.develop) + else + # Again, the same two cases as with the master selection are + # considered (fresh repo or repo that contains branches) + local default_suggestion + local should_check_existence + branch_count=$(git_local_branches | grep -v "^${master_branch}\$" | wc -l) + if [ "$branch_count" -eq 0 ]; then + should_check_existence=NO + default_suggestion=$(git config --get hubflow.branch.develop || echo develop) + else + echo + echo "Which branch should be used for integration of the \"next release\"?" + git_local_branches | grep -v "^${master_branch}\$" | sed 's/^.*$/ - &/g' + + should_check_existence=YES + default_suggestion= + for guess in $(git config --get hubflow.branch.develop) \ + 'develop' 'int' 'integration' 'master'; do + if git_local_branch_exists "$guess"; then + default_suggestion="$guess" + break + fi + done + + if [ -z "$default_suggestion" ]; then + for guess in $(git config --get hubflow.branch.develop) \ + 'develop' 'int' 'integration' 'master'; do + if git_remote_branch_exists "origin/$guess"; then + default_suggestion="$guess" + break + fi + done + fi + fi + + printf "Branch name for \"next release\" development: [$default_suggestion] " + if flag ask; then + read answer + else + printf "\n" + fi + develop_branch=${answer:-$default_suggestion} + + if [ "$master_branch" = "$develop_branch" ]; then + die "Production and integration branches should differ." + fi + + # check existence in case of an already existing repo + if [ "$should_check_existence" = "YES" ]; then + git_local_branch_exists "$develop_branch" || \ + die "Local branch '$develop_branch' does not exist." + fi + + # store the name of the develop branch + git config hubflow.branch.develop "$develop_branch" + fi + + # Creation of HEAD + # ---------------- + # We create a HEAD now, if it does not exist yet (in a fresh repo). We need + # it to be able to create new branches. + local created_hubflow_branch=0 + if ! git rev-parse --quiet --verify HEAD >/dev/null 2>&1; then + git symbolic-ref HEAD "refs/heads/$master_branch" + git commit --allow-empty --quiet -m "Initial commit" + created_hubflow_branch=1 + fi + + # Creation of master + # ------------------ + # We cannot safely assume that the master branch actually exists. It will + # exist nearly all of the time, but this deals with those rare occaisons + # when the master branch needs setting up + if ! git_local_branch_exists "$master_branch"; then + if git_remote_branch_exists "origin/$master_branch"; then + git branch "$develop_branch" "origin/$master_branch" >/dev/null 2>&1 + else + git branch --no-track "$master_branch" + fi + created_hubflow_branch=1 + fi + + # Creation of develop + # ------------------- + # The develop branch possibly does not exist yet. This is the case when, + # in a git init'ed repo with one or more commits, master was picked as the + # default production branch and develop was "created". We should create + # the develop branch now in that case (we base it on master, of course) + if ! git_local_branch_exists "$develop_branch"; then + if git_remote_branch_exists "origin/$develop_branch"; then + git branch "$develop_branch" "origin/$develop_branch" >/dev/null 2>&1 + else + git branch --no-track "$develop_branch" "$master_branch" + fi + created_hubflow_branch=1 + fi + + # assert the hubflow repo has been correctly initialized + hubflow_is_initialized + + # switch to develop branch if its newly created + if [ $created_hubflow_branch -eq 1 ]; then + git checkout -q "$develop_branch" + fi + + # finally, ask the user for naming conventions (branch and tag prefixes) + if flag force || \ + ! git config --get hubflow.prefix.feature >/dev/null 2>&1 || + ! git config --get hubflow.prefix.release >/dev/null 2>&1 || + ! git config --get hubflow.prefix.hotfix >/dev/null 2>&1 || + ! git config --get hubflow.prefix.support >/dev/null 2>&1 || + ! git config --get hubflow.prefix.versiontag >/dev/null 2>&1; then + echo + echo "How to name your supporting branch prefixes?" + fi + + local prefix + + # Feature branches + if ! git config --get hubflow.prefix.feature >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --get hubflow.prefix.feature || echo feature/) + printf "Feature branches? [$default_suggestion] " + if flag ask; then + read answer + else + printf "\n" + fi + [ "$answer" = "-" ] && prefix= || prefix=${answer:-$default_suggestion} + git config hubflow.prefix.feature "$prefix" + fi + + # Release branches + if ! git config --get hubflow.prefix.release >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --get hubflow.prefix.release || echo release/) + printf "Release branches? [$default_suggestion] " + if flag ask; then + read answer + else + printf "\n" + fi + [ "$answer" = "-" ] && prefix= || prefix=${answer:-$default_suggestion} + git config hubflow.prefix.release "$prefix" + fi + + + # Hotfix branches + if ! git config --get hubflow.prefix.hotfix >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --get hubflow.prefix.hotfix || echo hotfix/) + printf "Hotfix branches? [$default_suggestion] " + if flag ask; then + read answer + else + printf "\n" + fi + [ "$answer" = "-" ] && prefix= || prefix=${answer:-$default_suggestion} + git config hubflow.prefix.hotfix "$prefix" + fi + + + # Support branches + if ! git config --get hubflow.prefix.support >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --get hubflow.prefix.support || echo support/) + printf "Support branches? [$default_suggestion] " + if flag ask; then + read answer + else + printf "\n" + fi + [ "$answer" = "-" ] && prefix= || prefix=${answer:-$default_suggestion} + git config hubflow.prefix.support "$prefix" + fi + + + # Version tag prefix + if ! git config --get hubflow.prefix.versiontag >/dev/null 2>&1 || flag force; then + default_suggestion=$(git config --get hubflow.prefix.versiontag || echo "") + printf "Version tag prefix? [$default_suggestion] " + if flag ask; then + read answer + else + printf "\n" + fi + [ "$answer" = "-" ] && prefix= || prefix=${answer:-$default_suggestion} + git config hubflow.prefix.versiontag "$prefix" + fi + + # at this point, we should be on the develop branch, which may or + # may not exist at origin + # + # Load the settings that were just written so that $ORIGIN, + # etc. are properly defined + hubflow_load_settings + # push up to origin, in case this branch does not exist there (yet) + hubflow_push_latest_changes_to_origin +} + +cmd_help() { + usage + exit 0 +} diff --git a/git-hf-pull b/git-hf-pull new file mode 100644 index 0000000..2bb8cb0 --- /dev/null +++ b/git-hf-pull @@ -0,0 +1,95 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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_hubflow_initialized +hubflow_load_settings + +usage() { + echo "usage: git hf pull [-r]" +} + +parse_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" +} + +cmd_default() { + cmd_pull "$@" +} + +cmd_help() { + usage + exit 0 +} + +cmd_pull() { + # we support rebasing + DEFINE_boolean rebase false "pull with rebase" r + parse_args "$@" + + # make sure we're okay to start + require_clean_working_tree + + # we need to fetch the latest changes from upstream, before we can merge + hubflow_fetch_latest_changes_from_origin + + # merge the changes from upstream + if flag rebase ; then + git pull --rebase "$ORIGIN" + if [[ $? -ne 0 ]]; then + warn "Pull was aborted." + warn + warn "There might be conflicts during the rebase, or '$ORIGIN' may be inaccessible." + exit 1 + fi + else + hubflow_remote_merge_helper "$ORIGIN" "$BRANCH" + fi + + # all done + echo + echo "Summary of actions:" + echo "- Any changes from $ORIGIN/$BRANCH have been pulled into branch '$BRANCH'" +} \ No newline at end of file diff --git a/git-hf-push b/git-hf-push new file mode 100644 index 0000000..dd7aa8f --- /dev/null +++ b/git-hf-push @@ -0,0 +1,100 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2013 MediaSift Ltd. 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_hubflow_initialized +hubflow_load_settings + +usage() { + echo "usage: git hf push [-f]" +} + +cmd_default() { + cmd_push "$@" +} + +cmd_help() { + usage + exit 0 +} + +parse_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" +} + +cmd_push() { + DEFINE_boolean force false "Forces a destructive push using the --force/-f flag" F + parse_args "$@" + + # make sure we're okay to start + require_clean_working_tree + + # fetch and merge the latest changes from origin + hubflow_fetch_latest_changes_from_origin + hubflow_remote_merge_helper "$ORIGIN" "$BRANCH" + + # push to origin + if flag force ; then + hubflow_push_latest_changes_to_origin force + else + hubflow_push_latest_changes_to_origin + fi + local result=$? + + # what happened? + echo + echo "Summary of actions:" + if [[ $result -eq 1 ]] ; then + echo "- A new remote branch '$ORIGIN/$BRANCH' was created" + echo "- The local branch '$BRANCH' was configured to track the remote branch" + elif [[ $result -eq 2 ]] ; then + echo "- The remote branch '$ORIGIN/$BRANCH' was overwritten with your changes" + elif [[ $result -eq 3 ]] ; then + echo "- The remote branch '$ORIGIN/$BRANCH' was updated with your changes" + else + echo "- No action taken, '$ORIGIN/$BRANCH' already up-to-date" + fi + + # all done +} diff --git a/git-hf-release b/git-hf-release new file mode 100644 index 0000000..bc1b7d6 --- /dev/null +++ b/git-hf-release @@ -0,0 +1,528 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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_hubflow_initialized +hubflow_load_settings +VERSION_PREFIX=$(eval "echo `git config --get hubflow.prefix.versiontag`") +PREFIX=$(git config --get hubflow.prefix.release) + +usage() { + echo "usage: git hf release [list] [-v]" + echo " git hf release start []" + echo " git hf release finish [-sumpk] " + echo " git hf release cancel " + echo " git hf release push []" + echo " git hf release pull []" +} + +cmd_default() { + cmd_list "$@" +} + +cmd_list() { + DEFINE_boolean verbose false 'verbose (more) output' v + parse_args "$@" + + local release_branches + local current_branch + local short_names + release_branches=$(echo "$(git_local_branches)" | grep "^$PREFIX") + if [ -z "$release_branches" ]; then + warn "No release branches exist." + warn "" + warn "You can start a new release branch:" + warn "" + warn " git hf release start []" + warn "" + exit 0 + fi + + current_branch=$(git branch --no-color | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g') + short_names=$(echo "$release_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)" + else + local nicename=$(git rev-parse --short "$base") + printf "(based on $nicename)" + fi + else + printf "%s" "$branch" + fi + echo + done +} + +cmd_help() { + usage + exit 0 +} + +parse_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # read arguments into global variables + VERSION=$1 + BRANCH=$PREFIX$VERSION +} + +require_version_arg() { + if [ "$VERSION" = "" ]; then + warn "Missing argument " + usage + exit 1 + fi +} + +require_base_is_on_develop() { + if ! git branch --no-color --contains "$BASE" 2>/dev/null \ + | sed 's/[* ] //g' \ + | grep -q "^$DEVELOP_BRANCH\$"; then + die "fatal: Given base '$BASE' is not a valid commit on '$DEVELOP_BRANCH'." + fi +} + +require_no_existing_release_branches() { + local release_branches=$(echo "$(git_local_branches)" | grep "^$PREFIX") + local first_branch=$(echo ${release_branches} | head -n1) + first_branch=${first_branch#$PREFIX} + [ -z "$release_branches" ] || \ + die "There is an existing release branch ($first_branch). Finish that one first." +} + +cmd_start() { + DEFINE_boolean fetch true "fetch from $ORIGIN before creating the new branch" F + parse_args "$@" + BASE=${2:-$DEVELOP_BRANCH} + require_version_arg + + # sanity checks + require_clean_working_tree + require_remote_available + if flag fetch ; then + hubflow_fetch_latest_changes_from_origin + fi + require_base_is_on_develop + require_no_existing_release_branches + + # sanity checks + require_clean_working_tree + require_branch_absent "$BRANCH" + require_tag_absent "$VERSION_PREFIX$VERSION" + if noflag nofetch; then + git fetch -q "$ORIGIN" "$DEVELOP_BRANCH" + fi + if has "$ORIGIN/$DEVELOP_BRANCH" $(git_remote_branches); then + require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH" + fi + + # create branch + git checkout -b "$BRANCH" "$BASE" + + # push it back up to remote repo + hubflow_push_latest_changes_to_origin + + echo + echo "Summary of actions:" + echo "- A new branch '$BRANCH' was created, based on '$BASE'" + echo "- The branch '$BRANCH' has been pushed up to '$ORIGIN/$BRANCH'" + echo "- You are now on branch '$BRANCH'" + echo + echo "Follow-up actions:" + echo "- Bump the version number now!" + echo "- Start committing last-minute fixes in preparing your release" + echo "- When done, run:" + echo + echo " git hf release finish '$VERSION'" + echo +} + +cmd_finish() { + DEFINE_boolean fetch true "fetch from $ORIGIN before performing finish" F + DEFINE_boolean sign false "sign the release tag cryptographically" s + DEFINE_string signingkey "" "use the given GPG-key for the digital signature (implies -s)" u + DEFINE_string message "" "use the given tag message" m + DEFINE_boolean push true "push to $ORIGIN after performing finish" p + DEFINE_boolean keep false "keep branch after performing finish" k + DEFINE_boolean notag false "don't tag this release" n + DEFINE_boolean nobackmerge false "don't back-merge $MASTER_BRANCH to be a parent of $DEVELOP_BRANCH (using tag if applicable)" b + + parse_args "$@" + require_version_arg + + # handle flags that imply other flags + if [ "$FLAGS_signingkey" != "" ]; then + FLAGS_sign=$FLAGS_TRUE + fi + + # sanity checks + require_branch "$BRANCH" + require_clean_working_tree + require_remote_available + if flag fetch ; then + hubflow_fetch_latest_changes_from_origin + fi + + # update local repo with remote changes first, if asked + if flag fetch; then + # fetch and merge the latest changes from origin + hubflow_merge_latest_changes_from_origin + fi + + # push up to origin + if flag push ; then + hubflow_push_latest_changes_to_origin + fi + + # we need to be up to date before we go any further + if has "$ORIGIN/$MASTER_BRANCH" $(git_remote_branches); then + require_branches_equal "$MASTER_BRANCH" "$ORIGIN/$MASTER_BRANCH" + fi + if has "$ORIGIN/$DEVELOP_BRANCH" $(git_remote_branches); then + require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH" + fi + + # try to merge into master + # in case a previous attempt to finish this release branch has failed, + # but the merge into master was successful, we skip it now + if ! git_is_branch_merged_into "$BRANCH" "$MASTER_BRANCH"; then + git checkout "$MASTER_BRANCH" || \ + die "Could not check out $MASTER_BRANCH." + git merge --no-ff -srecursive -Xtheirs "$BRANCH" || \ + die "There were merge conflicts." + # TODO: What do we do now? + fi + + if noflag notag; then + # try to tag the release + # in case a previous attempt to finish this release branch has failed, + # but the tag was set successful, we skip it now + local tagname=$VERSION_PREFIX$VERSION + if ! git_tag_exists "$tagname"; then + local opts="-a" + flag sign && opts="$opts -s" + [ "$FLAGS_signingkey" != "" ] && opts="$opts -u '$FLAGS_signingkey'" + [ "$FLAGS_message" != "" ] && opts="$opts -m '$FLAGS_message'" + eval git tag $opts "$tagname" || \ + die "Tagging failed. Please run finish again to retry." + fi + fi + + # try to merge into develop + if noflag nobackmerge; then + # in case a previous attempt to finish this release branch has failed, + # but the merge into develop was successful, we skip it now + if ! git_is_branch_merged_into "$MASTER_BRANCH" "$DEVELOP_BRANCH"; then + git checkout "$DEVELOP_BRANCH" || \ + die "Could not check out $DEVELOP_BRANCH." + # merge the master branch back into develop; this makes the master + # branch - and the new tag (if provided) - a parent of the development + # branch, which in turn lets you use 'git describe' on either branch + if noflag notag; then + git merge --no-ff "$tagname" || \ + die "There were merge conflicts." + else + git merge --no-ff "$MASTER_BRANCH" || \ + die "There were merge conflicts." + fi + fi + else + # in case a previous attempt to finish this release branch has failed, + # but the merge into develop was successful, we skip it now + if ! git_is_branch_merged_into "$BRANCH" "$DEVELOP_BRANCH"; then + git checkout "$DEVELOP_BRANCH" || \ + die "Could not check out $DEVELOP_BRANCH." + # just merge the release branch into the development branch + git merge --no-ff "$BRANCH" || \ + die "There were merge conflicts." + fi + fi + + # delete branch + if noflag keep; then + if [ "$BRANCH" = "$(git_current_branch)" ]; then + git checkout "$MASTER_BRANCH" + fi + git branch -d "$BRANCH" + fi + + if flag push; then + git push "$ORIGIN" "$DEVELOP_BRANCH" || \ + die "Could not push to $DEVELOP_BRANCH from $ORIGIN." + git push "$ORIGIN" "$MASTER_BRANCH" || \ + die "Could not push to $MASTER_BRANCH from $ORIGIN." + if noflag notag; then + git push --tags "$ORIGIN" || \ + die "Could not push tags to $ORIGIN." + fi + if noflag keep ; then + git push "$ORIGIN" :"$BRANCH" || \ + die "Could not delete the remote $BRANCH in $ORIGIN." + fi + fi + + echo + echo "Summary of actions:" + if flag push ; then + echo "- Latest objects have been fetched from '$ORIGIN'" + fi + echo "- Release branch has been merged into '$MASTER_BRANCH'" + if noflag notag; then + echo "- The release was tagged '$tagname'" + if noflag nobackmerge; then + echo "- Tag '$tagname' has been back-merged into '$DEVELOP_BRANCH'" + fi + fi + if flag nobackmerge; then + echo "- Release branch has been merged into '$DEVELOP_BRANCH'" + else + echo "- Branch '$MASTER_BRANCH' has been back-merged into '$DEVELOP_BRANCH'" + fi + if flag keep; then + echo "- Release branch '$BRANCH' is still available" + else + echo "- Release branch '$BRANCH' has been deleted" + fi + if flag push; then + echo "- '$DEVELOP_BRANCH', '$MASTER_BRANCH' and tags have been pushed to '$ORIGIN'" + if noflag keep ; then + echo "- Release branch '$BRANCH' in '$ORIGIN' has been deleted." + fi + fi + echo +} + +cmd_cancel() { + DEFINE_boolean fetch true "fetch from $ORIGIN before performing cancel" F + DEFINE_string message "" "use the given tag message" m + DEFINE_boolean push true "push to $ORIGIN after performing cancel" p + DEFINE_boolean keep false "keep branch after performing cancel" k + DEFINE_boolean force false "safety feature; cannot cancel a release without this flag" f + DEFINE_boolean discard true "drop the changes in this release; do not merge back into develop" d + + parse_args "$@" + require_version_arg + + # has the user chosen the force flag? + if noflag force ; then + warn "To prevent you accidentally cancelling a release, you _must_ use the -f flag" + warn "with this command" + exit 1 + fi + + # sanity checks + require_branch "$BRANCH" + require_clean_working_tree + if flag push ; then + git push "$ORIGIN" "$BRANCH" || die "Could not push release branch up to $ORIGIN" + fi + + # we only merge into develop if the user hasn't selected the -d flag + if noflag discard ; then + if flag fetch ; then + git fetch -q "$ORIGIN" "$DEVELOP_BRANCH" || \ + die "Could not fetch $DEVELOP_BRANCH from $ORIGIN." + fi + if has "$ORIGIN/$DEVELOP_BRANCH" $(git_remote_branches); then + require_branches_equal "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH" + fi + + # try to merge into develop + # + # in case a previous attempt to finish this release branch has failed, + # but the merge into develop was successful, we skip it now + if ! git_is_branch_merged_into "$BRANCH" "$DEVELOP_BRANCH"; then + git checkout "$DEVELOP_BRANCH" || \ + die "Could not check out $DEVELOP_BRANCH." + # just merge the release branch into the development branch + git merge --no-ff "$BRANCH" || \ + die "There were merge conflicts." + fi + fi + + # delete branch + if noflag keep ; then + if [ "$BRANCH" = "$(git_current_branch)" ]; then + git checkout "$DEVELOP_BRANCH" + fi + + # do we need to delete remote branch too? + if flag push ; then + git push "$ORIGIN" :"$BRANCH" || \ + die "Could not delete the remote $BRANCH in $ORIGIN." + fi + + git branch -d "$BRANCH" + fi + + if noflag discard ; then + # push back to remote repo + if flag push ; then + git push "$ORIGIN" "$DEVELOP_BRANCH" || \ + die "Could not push to $DEVELOP_BRANCH from $ORIGIN." + fi + fi + + echo + echo "Summary of actions:" + if flag push ; then + echo "- Latest objects have been fetched from '$ORIGIN'" + fi + if noflag nodiscard ; then + echo "- Release branch has been merged into '$DEVELOP_BRANCH'" + fi + if flag keep ; then + echo "- Release branch '$BRANCH' is still available" + else + echo "- Release branch '$BRANCH' has been deleted" + if flag push ; then + echo "- Release branch '$BRANCH' in '$ORIGIN' has been deleted." + fi + fi + + if noflag nodiscard && flag push ; then + echo "- '$DEVELOP_BRANCH' has been pushed to '$ORIGIN'" + fi + echo +} + +cmd_publish() { + parse_args "$@" + require_version_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_pull() { + git hf pull "$@" +} + +cmd_push() { + git hf push "$@" +} + + +parse_remote_name() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # read arguments into global variables + REMOTE=$1 + NAME=$2 + BRANCH=$PREFIX$NAME +} + +name_or_current() { + if [ -z "$NAME" ]; then + use_current_release_branch_name + fi +} + +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, hubflow aborted." + return 1 + fi + return 0 +} + + +use_current_release_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 release branch." + warn "Please specify a argument." + exit 1 + fi +} diff --git a/git-hf-support b/git-hf-support new file mode 100644 index 0000000..968e747 --- /dev/null +++ b/git-hf-support @@ -0,0 +1,187 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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_hubflow_initialized +hubflow_load_settings +VERSION_PREFIX=$(eval "echo `git config --get hubflow.prefix.versiontag`") +PREFIX=$(git config --get hubflow.prefix.support) + +warn "note: The support subcommand is still very EXPERIMENTAL!" +warn "note: DO NOT use it in a production situation." + +usage() { + echo "usage: git hf support [list] [-v]" + echo " git hf support start [-F] " +} + +cmd_default() { + cmd_list "$@" +} + +cmd_list() { + DEFINE_boolean verbose false 'verbose (more) output' v + parse_args "$@" + + local support_branches + local current_branch + local short_names + support_branches=$(echo "$(git_local_branches)" | grep "^$PREFIX") + if [ -z "$support_branches" ]; then + warn "No support branches exist." + warn "" + warn "You can start a new support branch:" + warn "" + warn " git hf support start " + warn "" + exit 0 + fi + current_branch=$(git branch --no-color | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g') + short_names=$(echo "$support_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" "$MASTER_BRANCH") + local master_sha=$(git rev-parse "$MASTER_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" = "$master_sha" ]; then + printf "(no commits yet)" + else + local tagname=$(git name-rev --tags --no-undefined --name-only "$base") + local nicename + if [ "$tagname" != "" ]; then + nicename=$tagname + else + nicename=$(git rev-parse --short "$base") + fi + printf "(based on $nicename)" + fi + else + printf "%s" "$branch" + fi + echo + done +} + +cmd_help() { + usage + exit 0 +} + +parse_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" + + # read arguments into global variables + VERSION=$1 + BASE=$2 + BRANCH=$PREFIX$VERSION +} + +require_version_arg() { + if [ "$VERSION" = "" ]; then + warn "Missing argument " + usage + exit 1 + fi +} + +require_base_arg() { + if [ "$BASE" = "" ]; then + warn "Missing argument " + usage + exit 1 + fi +} + +require_base_is_on_master() { + if ! git branch --no-color --contains "$BASE" 2>/dev/null \ + | sed 's/[* ] //g' \ + | grep -q "^$MASTER_BRANCH\$"; then + die "fatal: Given base '$BASE' is not a valid commit on '$MASTER_BRANCH'." + fi +} + +cmd_start() { + DEFINE_boolean fetch false "fetch from $ORIGIN before performing finish" F + parse_args "$@" + require_version_arg + require_base_arg + require_base_is_on_master + + # sanity checks + require_clean_working_tree + + # fetch remote changes + if flag fetch; then + git fetch -q "$ORIGIN" "$BASE" + fi + require_branch_absent "$BRANCH" + + # create branch + git checkout -b "$BRANCH" "$BASE" + + echo + echo "Summary of actions:" + echo "- A new branch '$BRANCH' was created, based on '$BASE'" + echo "- You are now on branch '$BRANCH'" + echo +} diff --git a/git-hf-update b/git-hf-update new file mode 100644 index 0000000..d89e6fe --- /dev/null +++ b/git-hf-update @@ -0,0 +1,99 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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_hubflow_initialized +hubflow_load_settings + +usage() { + echo "usage: git hf update [-p]" +} + +parse_args() { + # parse options + FLAGS "$@" || exit $? + eval set -- "${FLAGS_ARGV}" +} + +cmd_default() { + cmd_update "$@" +} + +cmd_help() { + usage + exit 0 +} + +cmd_update() { + # flags + DEFINE_boolean prune true "delete local copies of branches that have been deleted from $ORIGIN" p + parse_args "$@" + + # make sure we're okay to start + require_clean_working_tree + + # fetch and merge the latest changes from origin + hubflow_merge_latest_changes_from_origin + + # make sure we end up where we should be + if [[ $(git_current_branch) != $BRANCH ]] ; then + git checkout "$BRANCH" + fi + + # drop any branches that have disappeared from origin + if flag prune ; then + git fetch --prune || die "Unable to prune branches that no longer exist at '$ORIGIN'" + fi + + # all done + echo + echo "Summary of actions:" + echo "- Any changes to branches at $ORIGIN have been downloaded to your local repository" + if flag prune ; then + echo "- Any branches that have been deleted at $ORIGIN have also been deleted from your local repository" + fi + echo "- Any changes from $ORIGIN/$MASTER_BRANCH have been merged into branch '$MASTER_BRANCH'" + echo "- Any changes from $ORIGIN/$DEVELOP_BRANCH have been merged into branch '$DEVELOP_BRANCH'" + echo "- Any resolved merge conflicts have been pushed back to $ORIGIN" + echo "- You are now on branch '$BRANCH'" +} \ No newline at end of file diff --git a/git-hf-upgrade b/git-hf-upgrade new file mode 100644 index 0000000..bbdeba3 --- /dev/null +++ b/git-hf-upgrade @@ -0,0 +1,86 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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. +# + +usage() { + echo "usage: git hf upgrade" +} + +cmd_default() { + cmd_upgrade "$@" +} + +cmd_help() { + usage + exit 0 +} + +cmd_upgrade() { + DEFINE_boolean check false "check for upgrade, but do not upgrade" c + # is there a new version available? + if ! hubflow_has_new_version_available > /dev/null ; then + die "You are already running the latest version" + elif flag check ; then + die "A newer version of the HubFlow tools is available" + fi + + # make sure we have permissions + if [ `id -u` != 0 ] ; then + die "Not running as root (try using sudo?)" + fi + + # make sure there isn't already a gitflow folder here + if [ -e gitflow ] ; then + die "Cannot clone the HubFlow repo; there's already a folder called 'gitflow' in your current working directory" + fi + + git clone --recursive $HUBFLOW_REPO || die "Unable to clone the HubFlow repo" + cd gitflow || die "Could not find the gitflow repo we've just cloned" + ./install.sh || die "Failed to install gitflow" + cd .. + rm -rf ./gitflow || die "Unable to remove the gitflow repo we've just cloned" + + echo + echo "Summary of actions:" + echo "- A new version of the HubFlow tools has been installed" + echo +} diff --git a/git-hf-version b/git-hf-version new file mode 100644 index 0000000..87c41cd --- /dev/null +++ b/git-hf-version @@ -0,0 +1,59 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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. +# + +usage() { + echo "usage: git hf version" +} + +cmd_default() { + if hubflow_has_new_version_available > /dev/null ; then + echo "$HUBFLOW_VERSION - upgrade available" + else + echo "$HUBFLOW_VERSION - latest version" + fi +} + +cmd_help() { + usage + exit 0 +} diff --git a/hubflow-common b/hubflow-common new file mode 100644 index 0000000..9205dd5 --- /dev/null +++ b/hubflow-common @@ -0,0 +1,689 @@ +# +# HubFlow - a fork of the git-flow tools to apply Vincent Driessen's +# branching model to working with GitHub +# +# Original blog post presenting this model is found at: +# http://nvie.com/git-model +# +# The HubFlow documentation is found at: +# http://datasift.github.com/gitflow/ +# +# Feel free to contribute to this project at: +# http://github.com/datasift/gitflow +# +# Copyright 2010 Vincent Driessen. All rights reserved. +# Copyright 2012 MediaSift Ltd. 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 variables +# + +HUBFLOW_VERSION=1.5.2 +HUBFLOW_REPO=https://github.com/datasift/gitflow + +# +# 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; [ -z $FLAG ] || [ $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_remote_branch_exists() { + has $1 $(git_remote_branches) +} + +git_branch_exists() { + has $1 $(git_all_branches) +} + +git_tag_exists() { + has $1 $(git_all_tags) +} + +git_ping_remote() { + # git-ls-remote does not work w/ Git 1.7.10.2 (Apple Git-33) when + # executed with no parameters + git ls-remote "$ORIGIN" > /dev/null 2>&1 +} + +# +# 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 +} + +# +# hubflow specific common functionality +# + +# check if this repo has been inited for hubflow +hubflow_has_master_configured() { + local master=$(git config --get hubflow.branch.master) + [ "$master" != "" ] && git_local_branch_exists "$master" +} + +hubflow_has_develop_configured() { + local develop=$(git config --get hubflow.branch.develop) + [ "$develop" != "" ] && git_local_branch_exists "$develop" +} + +hubflow_has_prefixes_configured() { + git config --get hubflow.prefix.feature >/dev/null 2>&1 && \ + git config --get hubflow.prefix.release >/dev/null 2>&1 && \ + git config --get hubflow.prefix.hotfix >/dev/null 2>&1 && \ + git config --get hubflow.prefix.support >/dev/null 2>&1 && \ + git config --get hubflow.prefix.versiontag >/dev/null 2>&1 +} + +hubflow_is_initialized() { + hubflow_has_master_configured && \ + hubflow_has_develop_configured && \ + [ "$(git config --get hubflow.branch.master)" != \ + "$(git config --get hubflow.branch.develop)" ] && \ + hubflow_has_prefixes_configured +} + +# loading settings that can be overridden using git config +hubflow_load_settings() { + export DOT_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) + export MASTER_BRANCH=$(git config --get hubflow.branch.master) + export DEVELOP_BRANCH=$(git config --get hubflow.branch.develop) + export ORIGIN=$(git config --get hubflow.origin || echo origin) + export BRANCH=$(git_current_branch) +} + +hubflow_change_branch() { + # what is the current branch? + local current_branch=$(git_current_branch) + + # do we need to switch branch? + if [[ $current_branch != $1 ]] ; then + git checkout "$1" || die "Unable to checkout branch '$1'" + fi +} + +hubflow_branch_push() { + # make sure we have a stack to push onto + if [[ -z BRANCH_STACK ]] ; then + BRANCH_STACK=() + BRANCH_STACK_SP=0 + fi + + # push the requested branch onto the stack + BRANCH_STACK[$BRANCH_STACK_SP]="$1" + let "BRANCH_STACK_SP += 1" + + # do we need to switch branch too? + hubflow_change_branch "$1" +} + +hubflow_branch_push_current_branch() { + hubflow_branch_push "$(git_current_branch)" +} + +hubflow_branch_pop() { + # make sure we have a stack to pop from + [[ -n $BRANCH_STACK ]] || die "Internal error: attempt to pop from non-existent BRANCH_STACK" + + # make sure the stack is not empty + [[ $BRANCH_STACK_SP != 0 ]] || die "Internal error: attempt to pop from empty BRANCH_STACK" + + # pop the branch from the stack + local popped_branch=${BRANCH_STACK[$BRANCH_STACK_SP]} + BRANCH_STACK[$BRANCH_STACK_SP]= + let "BRANCH_STACK_SP -= 1" + + # do we need to switch branch too? + hubflow_change_branch "$popped_branch" +} + +hubflow_branch_pop_no_checkout() { + # make sure we have a stack to pop from + [[ -n $BRANCH_STACK ]] || die "Internal error: attempt to pop from non-existent BRANCH_STACK" + + # make sure the stack is not empty + [[ $BRANCH_STACK_SP != 0 ]] || die "Internal error: attempt to pop from empty BRANCH_STACK" + + # pop the branch from the stack + BRANCH_STACK[$BRANCH_STACK_SP]= + let "BRANCH_STACK_SP -= 1" +} + +# $1: origin +# $2: branch to merge into +# $3: 'no_checkout_afterwards' if we do not want to checkout the current branch after the merge +hubflow_remote_merge_helper() { + # is there anything to merge? + git_compare_branches "$2" "$1/$2" + if [[ $? -eq 0 ]] ; then + return + fi + + # switch to the right branch locally + hubflow_branch_push "$2" + + # merge from origin + # do not pull, as we will already have a local copy of the branch + git merge "$1/$2" + + # did the merge work? + if [[ $? -ne 0 ]] ; then + # oops.. we have a merge conflict! + # + # tell the user what to do next + echo + echo "There were merge conflicts. To resolve the merge conflict manually, use:" + echo + echo " git mergetool" + echo " git commit" + echo + echo "You should then push your changes back up to '$1', using:" + echo + echo " git push '$1' '$2'" + echo + echo "After you have pushed back to '$1', please retry your HubFlow command" + exit 1 + fi + + # switch back to the original branch + if [[ -z $3 || $3 != 'no_checkout_afterwards' ]] ; then + hubflow_branch_pop + else + hubflow_branch_pop_no_checkout + fi +} + +# $1: local branch to merge from +# $2: local branch to merge to +# $3: 'no_checkout_afterwards' if we do not want to checkout the current branch after the merge +hubflow_local_merge_helper() { + # is there anything to merge? + git_compare_branches "$1" "$2" + if [[ $? -eq 0 ]] ; then + return + fi + + # switch to the right branch locally + hubflow_branch_push "$2" + + # merge into branch + if [ "$(git rev-list -n2 "$2..$1" | wc -l)" -eq 1 ]; then + git merge --ff "$1" + else + git merge --no-ff "$1" + fi + + # did the merge work? + if [ $? -ne 0 ]; then + # oops.. we have a merge conflict! + # + # tell the user what to do next + echo + echo "There were merge conflicts. To resolve the merge conflict manually, use:" + echo + echo " git mergetool" + echo " git commit" + echo + echo "After you have resolved all merge conflicts, please retry your HubFlow command" + exit 1 + fi + + # switch back to the original branch + if [[ -z $3 || $3 != 'no_checkout_afterwards' ]] ; then + hubflow_branch_pop + else + hubflow_branch_pop_no_checkout + fi +} + +hubflow_fetch_latest_changes_from_origin() { + git remote update "$ORIGIN" || die "Unable to get list of latest changes from '$ORIGIN'" + git fetch "$ORIGIN" || die "Unable to fetch latest changes from '$ORIGIN'" + git fetch --tags || die "Unable to fetch latest tags from '$ORIGIN'" +} + +hubflow_merge_latest_changes_from_origin() { + # we need to fetch the latest changes from upstream, before we can merge + hubflow_fetch_latest_changes_from_origin + + # remember which branch is currently checked out + hubflow_branch_push_current_branch + + # merge the master branch first + # merge conflicts on master will be rare + if has "$ORIGIN/$MASTER_BRANCH" $(git_remote_branches); then + hubflow_remote_merge_helper "$ORIGIN" "$MASTER_BRANCH" no_checkout_afterwards + git_compare_branches "$MASTER_BRANCH" "$ORIGIN/$MASTER_BRANCH" + if [[ $? -ne 0 ]] ; then + git push "$ORIGIN" "$MASTER_BRANCH" || die "Push to '$ORIGIN' failed" + fi + fi + + # merge the develop branch next + # merge conflicts on develop are sadly more common + if has "$ORIGIN/$DEVELOP_BRANCH" $(git_remote_branches); then + hubflow_remote_merge_helper "$ORIGIN" "$DEVELOP_BRANCH" no_checkout_afterwards + git_compare_branches "$DEVELOP_BRANCH" "$ORIGIN/$DEVELOP_BRANCH" + if [[ $? -ne 0 ]] ; then + git push "$ORIGIN" "$DEVELOP_BRANCH" || die "Push to '$ORIGIN' failed" + fi + fi + + # switch back to the original branch + hubflow_branch_pop +} + +# $1: 'force' if push should overwrite existing origin contents +hubflow_push_latest_changes_to_origin() { + local current_branch=$(git_current_branch) + + if ! git_branch_exists "$ORIGIN/$current_branch" ; then + # create remote branch + git push "$ORIGIN" "$current_branch:refs/heads/$current_branch" || die "Push to '$ORIGIN' failed" + # bring metadata back + git fetch -q "$ORIGIN" + + # configure remote tracking + git config "branch.$current_branch.remote" "$ORIGIN" + git config "branch.$current_branch.merge" "refs/heads/$current_branch" + + # we're done + return 1 + fi + + # we have an existing branch to update or overwrite + if [[ $1 == "force" ]] ; then + git push --force "$ORIGIN" "$current_branch:refs/heads/$current_branch" || die "Push to '$ORIGIN' failed" + return 2 + fi + + git_compare_branches "$current_branch" "$ORIGIN/$current_branch" + if [[ $? -ne 0 ]] ; then + git push "$ORIGIN" "$current_branch:refs/heads/$current_branch" || die "Push to '$ORIGIN' failed" + return 3 + fi + + # no action taken + return 0 +} + +hubflow_get_latest_version_number() { + LATEST="`git ls-remote --tags https://github.com/datasift/gitflow | grep -v '\^' | tail -n 1 | cut -d / -f 3`" + + echo $LATEST +} + +hubflow_has_new_version_available() { + LATEST=`hubflow_get_latest_version_number` + if [[ $LATEST != $HUBFLOW_VERSION ]] ; then + echo "A new version of the HubFlow tools is available." + echo "To upgrade, run 'git hf upgrade'." + echo + return 0 + fi + + # no new version available + return 1 +} + +# +# hubflow_track_repo +# +# Inputs: +# $1 = name prefix to resolve +# $2 = branch prefix to use +# $3 = origin +# +# When you want to switch to a remote feautre +# you must tell git to track it in your local config +# This script will take care of that +# +hubflow_track_repo(){ + local name=$1 + local prefix=$2 + local origin=$3 + +echo "$name" + # return if feature exists + if git_local_branch_exists "$prefix$name"; then + return 1 + fi + + # track the branch if it does not exist + if git_branch_exists "$origin/$prefix$name"; then + git branch --track $prefix$name + return 2 + fi + +return 0 +} + + +# +# hubflow_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 +# +hubflow_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 +} + +# +# GitHub speicific common functionality +# +GITHUB_API_URL="https://api.github.com" +GITHUB_AUTH_TOKEN_KEY="github.oauth.token" + +# +# Gets the GitHub OAuth token for this user/repo +# If no token has been created, the user will be prompted to create one. +# +github_auth_token() { + GITHUB_TOKEN=$(git config --get "$GITHUB_AUTH_TOKEN_KEY") + + if [ -z "$GITHUB_TOKEN" ]; then + read -p "GitHub Username: " USER + read -s -p "GitHub Password: " PASS + echo + + if [ -z "$USER" ]; then + die "You must enter your GitHub username to authenticate with" + fi + + if [ -z "$PASS" ]; then + die "You must enter your GitHub password to authenticate with" + fi + + GITHUB_TOKEN=$(curl -s \ + -u "$USER:$PASS" \ + -d '{"scopes":["repo"],"note":"gitflow automation"}' \ + "$GITHUB_API_URL/authorizations" | + awk -F"," '{for(i=1;i<=NF;i++){if($i~/token/){print $i}}}' | + awk -F":" '{print $2}'| awk -F"\"" '{print $2}') + + git config "$GITHUB_AUTH_TOKEN_KEY" "$GITHUB_TOKEN" + fi +} + +# Ensures we have been authorized on GitHub +require_github_auth_token() { + github_auth_token + + if [ -z "$GITHUB_TOKEN" ]; then + die "Failed to authorize GitHub user '$USER' for '$GITHUB_ORIGIN'" + fi +} + +# Gets the name of the origin repo on GitHub +# e.g. datasift/gitflow +github_origin_repo() { + # get the URL for the $ORIGIN repo + GITHUB_ORIGIN=$(git remote show -n "$ORIGIN" | grep 'Fetch URL' | cut -c 14-) + # echo "$GITHUB_ORIGIN" 1>&2 + + # if the URL doesn't point at GitHub, blank it + if echo "$GITHUB_ORIGIN" | grep 'git@github.com:' > /dev/null ; then + GITHUB_ORIGIN=$(echo $GITHUB_ORIGIN | cut -d : -f 2- | sed -e 's|\.git||g;' ) + elif echo "$GITHUB_ORIGIN" | grep 'https://github.com' > /dev/null ; then + GITHUB_ORIGIN=$(echo $GITHUB_ORIGIN | cut -c 20- | sed -e 's|\.git||g;' ) + else + GITHUB_ORIGIN= + fi + #echo "$GITHUB_ORIGIN" 1>&2 +} + +require_github_origin_repo() { + github_origin_repo + + if [ -z $GITHUB_ORIGIN ]; then + die "The remote repo for this branch, '$ORIGIN' is not a GitHub repository." + fi +} + +# Posts an authenticated request to GitHub and returns the response +github_post() { + require_github_auth_token + + resp=$(curl -s -H "Authorization: bearer $GITHUB_TOKEN" -d "$2" "$GITHUB_API_URL$1") + echo $resp +} + +# +# 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_remote_available() { + if ! git_ping_remote $1 ; then + die "fatal: remote repository $1 is unavailable; are you offline?" + fi +} + +require_hubflow_initialized() { + if ! hubflow_is_initialized; then + die "fatal: Not a hubflow-enabled repo yet. Please run \"git hf 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 +} diff --git a/hubflow-shFlags b/hubflow-shFlags new file mode 120000 index 0000000..7b736c1 --- /dev/null +++ b/hubflow-shFlags @@ -0,0 +1 @@ +shFlags/src/shflags \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..6c874d0 --- /dev/null +++ b/install.sh @@ -0,0 +1,100 @@ +#! /bin/bash +# +# install.sh +# git-flow make-less installer for *nix systems, by Rick Osborne +# +# Based on the git-flow core Makefile: +# http://github.com/nvie/gitflow/blob/master/Makefile + +# Licensed under the same restrictions as git-flow: +# http://github.com/nvie/gitflow/blob/develop/LICENSE + +# Usage: [environment] install.sh [install|uninstall|help] +# +# where can be: +# Installing INSTALL_INTO=$INSTALL_INTO" (default is /usr/local/bin) + +# ensure we have permissions needed to write to the place +# that we want to install the code into +# +# $1: folder to check +check_write_access() +{ + local target="$1" + + # if $1 does not exist, we go and check that we can create it + while [[ ! -d "$target" ]]; do + target=$(dirname "$target") + done + + # do we have write permissions? + if [[ ! -w "$target" ]]; then + echo "'$target' is not writable by $(whoami)" + + # should we be running as root? + if [[ `id -u` != 0 ]]; then + echo "Run as install as root (use sudo)" + fi + return 1 + fi + return 0 +} + +# @TODO +# +# * detect a suitable prefix for Windows users +if [ -z "$INSTALL_INTO" ] ; then + INSTALL_INTO="/usr/local/bin" +fi + +REPO_DIR="$(dirname $0)" +EXEC_FILES="git-hf" +SCRIPT_FILES="git-hf-init git-hf-feature git-hf-hotfix git-hf-push git-hf-pull git-hf-release git-hf-support git-hf-update git-hf-upgrade git-hf-version hubflow-common hubflow-shFlags" +SUBMODULE_FILE="hubflow-shFlags" + +case "$1" in + uninstall) + echo "Uninstalling hubflow from $INSTALL_INTO" + if [ -d "$INSTALL_INTO" ] ; then + for script_file in $SCRIPT_FILES $EXEC_FILES ; do + echo "rm -vf $INSTALL_INTO/$script_file" + rm -vf "$INSTALL_INTO/$script_file" + done + else + echo "The '$INSTALL_INTO' directory was not found." + echo "Do you need to set INSTALL_INTO ?" + fi + exit + ;; + help) + echo "Usage: [environment] install.sh [install|uninstall|help]" + echo "Environment:" + echo " INSTALL_INTO=$INSTALL_INTO" + exit + ;; + *) + # check write access first + check_write_access "$INSTALL_INTO" || exit 1 + + # if we get here, we can proceed with the install + echo "Installing hubflow to $INSTALL_INTO" + if [ -f "$REPO_DIR/$SUBMODULE_FILE" ] ; then + echo "Submodules look up to date" + else + echo "Updating submodules" + lastcwd="$PWD" + cd "$REPO_DIR" + git submodule init -q + git submodule update -q + cd "$lastcwd" + fi + install -v -d -m 0755 "$INSTALL_INTO" + for exec_file in $EXEC_FILES ; do + install -v -m 0755 "$REPO_DIR/$exec_file" "$INSTALL_INTO" + done + for script_file in $SCRIPT_FILES ; do + install -v -m 0644 "$REPO_DIR/$script_file" "$INSTALL_INTO" + done + exit + ;; +esac