| Benedikt Böhm | e2fa491 | 2010-01-26 14:41:20 +0100 | [diff] [blame] | 1 | #!/bin/sh | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 2 | # | 
| Vincent Driessen | 6c2d30b | 2010-01-26 22:18:36 +0100 | [diff] [blame] | 3 | # git-flow -- A collection of Git extensions to provide high-level | 
|  | 4 | # repository operations for Vincent Driessen's branching model. | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 5 | # | 
|  | 6 | # Original blog post presenting this model is found at: | 
|  | 7 | #    http://nvie.com/archives/323 | 
|  | 8 | # | 
|  | 9 | # Feel free to contribute to this project at: | 
|  | 10 | #    http://github.com/nvie/gitflow | 
|  | 11 | # | 
|  | 12 | # Copyright (c) 2010 by Vincent Driessen | 
|  | 13 | # Copyright (c) 2010 by Benedikt Böhm | 
|  | 14 | # | 
|  | 15 |  | 
| Benedikt Böhm | e5eaff9 | 2010-01-26 12:44:55 +0100 | [diff] [blame] | 16 | # enable debug mode | 
|  | 17 | if [ "$DEBUG" = "yes" ]; then | 
|  | 18 | set -x | 
|  | 19 | fi | 
|  | 20 |  | 
| Benedikt Böhm | 4a864fb | 2010-01-26 12:59:27 +0100 | [diff] [blame] | 21 | export GITFLOW_DIR=$(dirname "$0") | 
|  | 22 | export MASTER_BRANCH=$(git config --get gitflow.branch.master || echo master) | 
|  | 23 | export DEVELOP_BRANCH=$(git config --get gitflow.branch.develop || echo develop) | 
| Benedikt Böhm | 350e715 | 2010-01-26 13:05:05 +0100 | [diff] [blame] | 24 | export ORIGIN=$(git config --get gitflow.origin || echo origin) | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 25 | export README=$(git config --get gitflow.readme || echo README) | 
| Benedikt Böhm | 4a864fb | 2010-01-26 12:59:27 +0100 | [diff] [blame] | 26 |  | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 27 | warn() { echo "$@" >&2; } | 
|  | 28 | die() { warn "$@"; exit 1; } | 
| Benedikt Böhm | e2fa491 | 2010-01-26 14:41:20 +0100 | [diff] [blame] | 29 |  | 
|  | 30 | has() { | 
|  | 31 | local item=$1; shift | 
|  | 32 | echo " $@ " | grep -q " $item " | 
|  | 33 | } | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 34 |  | 
|  | 35 | usage() { | 
| Benedikt Böhm | 7d703a8 | 2010-01-26 13:17:12 +0100 | [diff] [blame] | 36 | . "$GITFLOW_DIR/git-flow-version" | 
| Vincent Driessen | 6c2d30b | 2010-01-26 22:18:36 +0100 | [diff] [blame] | 37 | echo "git-flow, version $GITFLOW_VERSION" | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 38 | echo | 
| Benedikt Böhm | 7d703a8 | 2010-01-26 13:17:12 +0100 | [diff] [blame] | 39 | echo "usage: git flow <cmd> <type> <args>" | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 40 | echo "       git flow init [<url>]" | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 41 | echo | 
| Benedikt Böhm | 7d703a8 | 2010-01-26 13:17:12 +0100 | [diff] [blame] | 42 | echo "<type> can be any of: feature, release, hotfix, support" | 
|  | 43 | echo | 
|  | 44 | echo "Try 'git flow help <type>' for details." | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 45 | } | 
|  | 46 |  | 
|  | 47 | main() { | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 48 | if [ $# -lt 1 ]; then | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 49 | usage | 
|  | 50 | exit 1 | 
|  | 51 | fi | 
|  | 52 |  | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 53 | # sanity checks | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 54 | ACTION="$1"; shift | 
|  | 55 |  | 
|  | 56 | if [ "$ACTION" = "init" ]; then | 
|  | 57 | gitflow_init "$@" | 
|  | 58 | exit 0 | 
|  | 59 | fi | 
|  | 60 |  | 
|  | 61 | BTYPE="$1"; shift | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 62 |  | 
|  | 63 | if [ ! -e "$GITFLOW_DIR/git-flow-$BTYPE" ]; then | 
|  | 64 | usage | 
|  | 65 | exit 1 | 
|  | 66 | fi | 
|  | 67 |  | 
| Vincent Driessen | 2ba9a4d | 2010-01-27 12:51:07 +0100 | [diff] [blame] | 68 | if ! git rev-parse --git-dir >/dev/null; then | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 69 | die "Not a git repository" | 
|  | 70 | fi | 
|  | 71 |  | 
|  | 72 | # get all available branches | 
|  | 73 | LOCAL_BRANCHES=$(git branch | sed 's/^[* ] //') | 
|  | 74 | REMOTE_BRANCHES=$(git branch -r | sed 's/^[* ] //') | 
|  | 75 | ALL_BRANCHES="$LOCAL_BRANCHES $REMOTE_BRANCHES" | 
|  | 76 |  | 
|  | 77 | # run command | 
|  | 78 | . "$GITFLOW_DIR/git-flow-$BTYPE" | 
|  | 79 |  | 
| Vincent Driessen | 1f2741a | 2010-01-27 12:55:12 +0100 | [diff] [blame] | 80 | if ! typeset -f cmd_$ACTION >/dev/null; then | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 81 | usage | 
|  | 82 | exit 1 | 
|  | 83 | fi | 
|  | 84 |  | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 85 | # run command | 
|  | 86 | cmd_$ACTION "$@" | 
|  | 87 | } | 
|  | 88 |  | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 89 | gitflow_init() { | 
|  | 90 | echo | 
|  | 91 | echo "Summary of actions:" | 
|  | 92 |  | 
| Benedikt Böhm | 74a4fe2 | 2010-01-28 01:11:29 +0800 | [diff] [blame] | 93 | if ! git rev-parse --git-dir 2>&1 >/dev/null; then | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 94 | git init --quiet | 
|  | 95 | echo "- A new git repository at $PWD was created" | 
|  | 96 | fi | 
|  | 97 |  | 
| Benedikt Böhm | 74a4fe2 | 2010-01-28 01:11:29 +0800 | [diff] [blame] | 98 | if ! git rev-parse --quiet --verify HEAD 2>&1 >/dev/null; then | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 99 | touch $README | 
|  | 100 | git add $README | 
|  | 101 | git commit --quiet -m "initial commit" | 
|  | 102 | if [ "$MASTER_BRANCH" != "master" ]; then | 
|  | 103 | git branch -m master $MASTER_BRANCH | 
|  | 104 | fi | 
|  | 105 | echo "- An initial commit was created at branch '$MASTER_BRANCH'" | 
|  | 106 | fi | 
|  | 107 |  | 
| Benedikt Böhm | 74a4fe2 | 2010-01-28 01:11:29 +0800 | [diff] [blame] | 108 | if ! git rev-parse --verify $MASTER_BRANCH 2>&1 >/dev/null; then | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 109 | die "Cannot find your master branch. Try: git branch -m <mymaster> $MASTER_BRANCH" | 
|  | 110 | fi | 
|  | 111 |  | 
|  | 112 | gitflow_check_clean_working_tree | 
|  | 113 |  | 
|  | 114 | if git remote | grep -q $ORIGIN; then | 
|  | 115 | git fetch -q $ORIGIN | 
|  | 116 | gitflow_require_branches_equal $MASTER_BRANCH $ORIGIN/$MASTER_BRANCH | 
|  | 117 | fi | 
|  | 118 |  | 
| Benedikt Böhm | 74a4fe2 | 2010-01-28 01:11:29 +0800 | [diff] [blame] | 119 | if git rev-parse --verify $DEVELOP_BRANCH 2>&1 >/dev/null; then | 
| Benedikt Böhm | 427c5db | 2010-01-26 18:23:44 +0100 | [diff] [blame] | 120 | gitflow_require_branches_equal $DEVELOP_BRANCH $ORIGIN/$DEVELOP_BRANCH | 
|  | 121 | else | 
|  | 122 | git checkout -q -b $DEVELOP_BRANCH $MASTER_BRANCH | 
|  | 123 | echo "- A new branch '$DEVELOP_BRANCH' was created" | 
|  | 124 | echo "- You are now on branch '$DEVELOP_BRANCH'" | 
|  | 125 | fi | 
|  | 126 |  | 
|  | 127 | if ! git remote | grep -q $ORIGIN; then | 
|  | 128 | if [ "$1" = "" ]; then | 
|  | 129 | echo "- No remote location was added. Try: git remote add $ORIGIN <url>" | 
|  | 130 | else | 
|  | 131 | git remote add $ORIGIN $1 | 
|  | 132 | echo "- A new remote location '$1' was added" | 
|  | 133 | fi | 
|  | 134 | fi | 
|  | 135 |  | 
|  | 136 | echo | 
|  | 137 |  | 
|  | 138 | if git remote | grep -q $ORIGIN; then | 
|  | 139 | git push $ORIGIN $MASTER_BRANCH $DEVELOP_BRANCH | 
|  | 140 | fi | 
|  | 141 | } | 
|  | 142 |  | 
| Benedikt Böhm | 00ccea6 | 2010-01-26 12:39:36 +0100 | [diff] [blame] | 143 | gitflow_check_clean_working_tree() { | 
|  | 144 | if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then | 
|  | 145 | die "Working tree contains unstaged changes. Aborting ..." | 
|  | 146 | fi | 
|  | 147 | if ! git diff-index --cached --quiet --ignore-submodules HEAD --; then | 
|  | 148 | die "Index contains uncommited changes. Aborting ..." | 
|  | 149 | fi | 
|  | 150 | } | 
|  | 151 |  | 
|  | 152 | gitflow_require_local_branch() { | 
|  | 153 | if ! has $1 $LOCAL_BRANCHES; then | 
|  | 154 | die "Local branch '$1' does not exist and is required." | 
|  | 155 | fi | 
|  | 156 | } | 
|  | 157 |  | 
|  | 158 | gitflow_require_remote_branch() { | 
|  | 159 | if ! has $1 $REMOTE_BRANCHES; then | 
|  | 160 | die "Remote branch '$1' does not exist and is required." | 
|  | 161 | fi | 
|  | 162 | } | 
|  | 163 |  | 
|  | 164 | gitflow_require_branch() { | 
|  | 165 | if ! has $1 $ALL_BRANCHES; then | 
|  | 166 | die "Branch '$1' does not exist and is required." | 
|  | 167 | fi | 
|  | 168 | } | 
|  | 169 |  | 
|  | 170 | gitflow_require_branch_absent() { | 
|  | 171 | if has $1 $ALL_BRANCHES; then | 
|  | 172 | die "Branch '$1' already exists. Pick another name." | 
|  | 173 | fi | 
|  | 174 | } | 
|  | 175 |  | 
|  | 176 | # | 
|  | 177 | # gitflow_test_branches_equal() | 
|  | 178 | # | 
|  | 179 | # Tests whether branches and their "origin" counterparts have diverged and need | 
|  | 180 | # merging first. It returns error codes to provide more detail, like so: | 
|  | 181 | # | 
|  | 182 | # 0    Branch heads point to the same commit | 
|  | 183 | # 1    First given branch needs fast-forwarding | 
|  | 184 | # 2    Second given branch needs fast-forwarding | 
|  | 185 | # 3    Branch needs a real merge | 
|  | 186 | # | 
|  | 187 | gitflow_test_branches_equal() { | 
|  | 188 | commit1=$(git rev-parse "$1") | 
|  | 189 | commit2=$(git rev-parse "$2") | 
|  | 190 | if [ "$commit1" != "$commit2" ]; then | 
|  | 191 | base=$(git merge-base "$commit1" "$commit2") | 
|  | 192 | if [ "$commit1" = "$base" ]; then | 
|  | 193 | return 1 | 
|  | 194 | elif [ "$commit2" = "$base" ]; then | 
|  | 195 | return 2 | 
|  | 196 | else | 
|  | 197 | return 3 | 
|  | 198 | fi | 
|  | 199 | else | 
|  | 200 | return 0 | 
|  | 201 | fi | 
|  | 202 | } | 
|  | 203 |  | 
|  | 204 | gitflow_require_branches_equal() { | 
|  | 205 | gitflow_require_local_branch "$1" | 
|  | 206 | gitflow_require_remote_branch "$2" | 
|  | 207 | gitflow_test_branches_equal "$1" "$2" | 
|  | 208 | status=$? | 
|  | 209 | if [ $status -gt 0 ]; then | 
|  | 210 | warn "Branches '$1' and '$2' have diverged." | 
|  | 211 | if [ $status -eq 1 ]; then | 
|  | 212 | die "And branch '$1' may be fast-forwarded." | 
|  | 213 | elif [ $status -eq 2 ]; then | 
|  | 214 | # Warn here, since there is no harm in being ahead | 
|  | 215 | warn "And local branch '$1' is ahead of '$2'." | 
|  | 216 | else | 
|  | 217 | die "Branches need merging first." | 
|  | 218 | fi | 
|  | 219 | fi | 
|  | 220 | } | 
|  | 221 |  | 
|  | 222 | main "$@" |