| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 1 | # | 
|  | 2 | # git-flow -- A collection of Git extensions to provide high-level | 
|  | 3 | # repository operations for Vincent Driessen's branching model. | 
|  | 4 | # | 
|  | 5 | # Original blog post presenting this model is found at: | 
|  | 6 | #    http://nvie.com/archives/323 | 
|  | 7 | # | 
|  | 8 | # Feel free to contribute to this project at: | 
|  | 9 | #    http://github.com/nvie/gitflow | 
|  | 10 | # | 
|  | 11 | # Copyright (c) 2010 by Vincent Driessen | 
|  | 12 | # Copyright (c) 2010 by Benedikt Böhm | 
|  | 13 | # | 
|  | 14 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 15 | # | 
|  | 16 | # Common functionality | 
|  | 17 | # | 
|  | 18 |  | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 19 | # shell output | 
|  | 20 | warn() { echo "$@" >&2; } | 
|  | 21 | die() { warn "$@"; exit 1; } | 
|  | 22 |  | 
|  | 23 | # set logic | 
|  | 24 | has() { | 
| Vincent Driessen | f46e290 | 2010-02-15 23:01:52 +0100 | [diff] [blame] | 25 | local item=$1; shift | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 26 | echo " $@ " | grep -q " $item " | 
|  | 27 | } | 
|  | 28 |  | 
|  | 29 | # basic math | 
|  | 30 | min() { [ "$1" -le "$2" ] && echo "$1" || echo "$2"; } | 
|  | 31 | max() { [ "$1" -ge "$2" ] && echo "$1" || echo "$2"; } | 
|  | 32 |  | 
|  | 33 | # basic string matching | 
|  | 34 | startswith() { [ "$1" != "${1#$2}" ]; } | 
| Vincent Driessen | e0d8af3 | 2010-02-22 12:21:36 +0100 | [diff] [blame] | 35 | endswith() { [ "$1" != "${1%$2}" ]; } | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 36 |  | 
|  | 37 | # convenience functions for checking shFlags flags | 
| Vincent Driessen | f46e290 | 2010-02-15 23:01:52 +0100 | [diff] [blame] | 38 | flag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; } | 
|  | 39 | noflag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -ne $FLAGS_TRUE ]; } | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 40 |  | 
|  | 41 | # | 
|  | 42 | # Git specific common functionality | 
|  | 43 | # | 
|  | 44 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 45 | git_local_branches() { git branch | sed 's/^[* ] //'; } | 
|  | 46 | git_remote_branches() { git branch -r | sed 's/^[* ] //'; } | 
|  | 47 | git_all_branches() { ( git branch; git branch -r) | sed 's/^[* ] //'; } | 
|  | 48 | git_all_tags() { git tag; } | 
|  | 49 |  | 
| Vincent Driessen | 55c1553 | 2010-02-22 07:28:27 +0100 | [diff] [blame] | 50 | git_current_branch() { | 
|  | 51 | git branch | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g' | 
|  | 52 | } | 
|  | 53 |  | 
|  | 54 | git_is_clean_working_tree() { | 
|  | 55 | if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then | 
|  | 56 | return 1 | 
|  | 57 | elif ! git diff-index --cached --quiet --ignore-submodules HEAD --; then | 
|  | 58 | return 2 | 
|  | 59 | else | 
|  | 60 | return 0 | 
|  | 61 | fi | 
|  | 62 | } | 
|  | 63 |  | 
|  | 64 | git_repo_is_headless() { | 
|  | 65 | ! git rev-parse --quiet --verify HEAD >/dev/null 2>&1 | 
|  | 66 | } | 
|  | 67 |  | 
|  | 68 | git_local_branch_exists() { | 
|  | 69 | has $1 $(git_local_branches) | 
|  | 70 | } | 
|  | 71 |  | 
|  | 72 | git_branch_exists() { | 
|  | 73 | has $1 $(git_all_branches) | 
|  | 74 | } | 
|  | 75 |  | 
|  | 76 | git_tag_exists() { | 
|  | 77 | has $1 $(git_all_tags) | 
|  | 78 | } | 
|  | 79 |  | 
|  | 80 | # | 
|  | 81 | # git_compare_branches() | 
|  | 82 | # | 
|  | 83 | # Tests whether branches and their "origin" counterparts have diverged and need | 
|  | 84 | # merging first. It returns error codes to provide more detail, like so: | 
|  | 85 | # | 
|  | 86 | # 0    Branch heads point to the same commit | 
|  | 87 | # 1    First given branch needs fast-forwarding | 
|  | 88 | # 2    Second given branch needs fast-forwarding | 
|  | 89 | # 3    Branch needs a real merge | 
|  | 90 | # 4    There is no merge base, i.e. the branches have no common ancestors | 
|  | 91 | # | 
|  | 92 | git_compare_branches() { | 
|  | 93 | local commit1=$(git rev-parse "$1") | 
|  | 94 | local commit2=$(git rev-parse "$2") | 
|  | 95 | if [ "$commit1" != "$commit2" ]; then | 
|  | 96 | local base=$(git merge-base "$commit1" "$commit2") | 
|  | 97 | if [ $? -ne 0 ]; then | 
|  | 98 | return 4 | 
|  | 99 | elif [ "$commit1" = "$base" ]; then | 
|  | 100 | return 1 | 
|  | 101 | elif [ "$commit2" = "$base" ]; then | 
|  | 102 | return 2 | 
|  | 103 | else | 
|  | 104 | return 3 | 
|  | 105 | fi | 
|  | 106 | else | 
|  | 107 | return 0 | 
|  | 108 | fi | 
|  | 109 | } | 
|  | 110 |  | 
|  | 111 | # | 
|  | 112 | # git_is_branch_merged_into() | 
|  | 113 | # | 
|  | 114 | # Checks whether branch $1 is succesfully merged into $2 | 
|  | 115 | # | 
|  | 116 | git_is_branch_merged_into() { | 
|  | 117 | local subject=$1 | 
|  | 118 | local base=$2 | 
|  | 119 | local all_merges=$(git branch --contains $subject | sed 's/^[* ] //') | 
|  | 120 | has $base $all_merges | 
|  | 121 | } | 
|  | 122 |  | 
|  | 123 | # | 
|  | 124 | # gitflow specific common functionality | 
|  | 125 | # | 
|  | 126 |  | 
| Vincent Driessen | b25ab83 | 2010-02-20 11:21:23 +0100 | [diff] [blame] | 127 | # check if this repo has been inited for gitflow | 
|  | 128 | gitflow_has_master_configured() { | 
|  | 129 | local master=$(git config --get gitflow.branch.master) | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 130 | [ "$master" != "" ] && git_local_branch_exists "$master" | 
| Vincent Driessen | b25ab83 | 2010-02-20 11:21:23 +0100 | [diff] [blame] | 131 | } | 
|  | 132 |  | 
|  | 133 | gitflow_has_develop_configured() { | 
|  | 134 | local develop=$(git config --get gitflow.branch.develop) | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 135 | [ "$develop" != "" ] && git_local_branch_exists "$develop" | 
| Vincent Driessen | b25ab83 | 2010-02-20 11:21:23 +0100 | [diff] [blame] | 136 | } | 
|  | 137 |  | 
| Vincent Driessen | 1d8bb0d | 2010-02-20 16:46:38 +0100 | [diff] [blame] | 138 | gitflow_has_prefixes_configured() { | 
|  | 139 | git config --get gitflow.prefix.feature >/dev/null 2>&1     && \ | 
|  | 140 | git config --get gitflow.prefix.release >/dev/null 2>&1     && \ | 
|  | 141 | git config --get gitflow.prefix.hotfix >/dev/null 2>&1      && \ | 
|  | 142 | git config --get gitflow.prefix.support >/dev/null 2>&1     && \ | 
|  | 143 | git config --get gitflow.prefix.versiontag >/dev/null 2>&1 | 
|  | 144 | } | 
|  | 145 |  | 
| Vincent Driessen | b25ab83 | 2010-02-20 11:21:23 +0100 | [diff] [blame] | 146 | gitflow_is_initialized() { | 
| Vincent Driessen | 1d8bb0d | 2010-02-20 16:46:38 +0100 | [diff] [blame] | 147 | gitflow_has_master_configured                    && \ | 
|  | 148 | gitflow_has_develop_configured                   && \ | 
|  | 149 | [ "$(git config --get gitflow.branch.master)" !=    \ | 
|  | 150 | "$(git config --get gitflow.branch.develop)" ] && \ | 
|  | 151 | gitflow_has_prefixes_configured | 
| Vincent Driessen | b25ab83 | 2010-02-20 11:21:23 +0100 | [diff] [blame] | 152 | } | 
|  | 153 |  | 
| Vincent Driessen | d72e4ac | 2010-02-16 21:33:51 +0100 | [diff] [blame] | 154 | # loading settings that can be overridden using git config | 
|  | 155 | gitflow_load_settings() { | 
| Vincent Driessen | cf6e92a | 2010-02-19 19:44:48 +0100 | [diff] [blame] | 156 | export DOT_GIT_DIR=$(git rev-parse --git-dir >/dev/null 2>&1) | 
| Vincent Driessen | c1598bf | 2010-02-20 16:52:48 +0100 | [diff] [blame] | 157 | export MASTER_BRANCH=$(git config --get gitflow.branch.master) | 
|  | 158 | export DEVELOP_BRANCH=$(git config --get gitflow.branch.develop) | 
| Vincent Driessen | d72e4ac | 2010-02-16 21:33:51 +0100 | [diff] [blame] | 159 | export ORIGIN=$(git config --get gitflow.origin || echo origin) | 
| Vincent Driessen | d72e4ac | 2010-02-16 21:33:51 +0100 | [diff] [blame] | 160 | } | 
|  | 161 |  | 
| Vincent Driessen | d099126 | 2010-02-06 21:19:07 +0100 | [diff] [blame] | 162 | # | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 163 | # gitflow_resolve_nameprefix | 
| Vincent Driessen | d099126 | 2010-02-06 21:19:07 +0100 | [diff] [blame] | 164 | # | 
|  | 165 | # Inputs: | 
|  | 166 | # $1 = name prefix to resolve | 
|  | 167 | # $2 = branch prefix to use | 
|  | 168 | # | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 169 | # Searches branch names from git_local_branches() to look for a unique | 
| Vincent Driessen | 21c7aa9 | 2010-02-16 20:57:35 +0100 | [diff] [blame] | 170 | # branch name whose name starts with the given name prefix. | 
| Vincent Driessen | d099126 | 2010-02-06 21:19:07 +0100 | [diff] [blame] | 171 | # | 
|  | 172 | # There are multiple exit codes possible: | 
|  | 173 | # 0: The unambiguous full name of the branch is written to stdout | 
|  | 174 | #    (success) | 
|  | 175 | # 1: No match is found. | 
|  | 176 | # 2: Multiple matches found. These matches are written to stderr | 
|  | 177 | # | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 178 | gitflow_resolve_nameprefix() { | 
| Vincent Driessen | f46e290 | 2010-02-15 23:01:52 +0100 | [diff] [blame] | 179 | local name=$1 | 
|  | 180 | local prefix=$2 | 
|  | 181 | local matches | 
|  | 182 | local num_matches | 
| Vincent Driessen | d099126 | 2010-02-06 21:19:07 +0100 | [diff] [blame] | 183 |  | 
|  | 184 | # first, check if there is a perfect match | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 185 | if has "$(git_local_branches)" "$prefix$name"; then | 
| Vincent Driessen | d099126 | 2010-02-06 21:19:07 +0100 | [diff] [blame] | 186 | echo "$name" | 
|  | 187 | return 0 | 
|  | 188 | fi | 
|  | 189 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 190 | matches=$(echo "$(git_local_branches)" | grep "^$prefix$name") | 
| Vincent Driessen | d099126 | 2010-02-06 21:19:07 +0100 | [diff] [blame] | 191 | num_matches=$(echo "$matches" | wc -l) | 
|  | 192 | if [ -z "$matches" ]; then | 
|  | 193 | # no prefix match, so take it literally | 
|  | 194 | warn "No branch matches prefix '$name'" | 
|  | 195 | return 1 | 
|  | 196 | else | 
|  | 197 | if [ $num_matches -eq 1 ]; then | 
|  | 198 | echo "${matches#$prefix}" | 
|  | 199 | return 0 | 
|  | 200 | else | 
|  | 201 | # multiple matches, cannot decide | 
|  | 202 | warn "Multiple branches match prefix '$name':" | 
|  | 203 | for match in $matches; do | 
|  | 204 | warn "- $match" | 
|  | 205 | done | 
|  | 206 | return 2 | 
|  | 207 | fi | 
|  | 208 | fi | 
|  | 209 | } | 
|  | 210 |  | 
| Vincent Driessen | 55c1553 | 2010-02-22 07:28:27 +0100 | [diff] [blame] | 211 | # | 
|  | 212 | # Assertions for use in git-flow subcommands | 
|  | 213 | # | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 214 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 215 | require_git_repo() { | 
| Vincent Driessen | d72e4ac | 2010-02-16 21:33:51 +0100 | [diff] [blame] | 216 | if ! git rev-parse --git-dir >/dev/null 2>&1; then | 
| Vincent Driessen | c1598bf | 2010-02-20 16:52:48 +0100 | [diff] [blame] | 217 | die "fatal: Not a git repository" | 
|  | 218 | fi | 
|  | 219 | } | 
|  | 220 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 221 | require_gitflow_initialized() { | 
| Vincent Driessen | c1598bf | 2010-02-20 16:52:48 +0100 | [diff] [blame] | 222 | if ! gitflow_is_initialized; then | 
|  | 223 | die "fatal: Not a gitflow-enabled repo yet. Please run \"git flow init\" first." | 
| Vincent Driessen | d72e4ac | 2010-02-16 21:33:51 +0100 | [diff] [blame] | 224 | fi | 
|  | 225 | } | 
|  | 226 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 227 | require_clean_working_tree() { | 
|  | 228 | git_is_clean_working_tree | 
| Vincent Driessen | f46e290 | 2010-02-15 23:01:52 +0100 | [diff] [blame] | 229 | local result=$? | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 230 | if [ $result -eq 1 ]; then | 
|  | 231 | die "fatal: Working tree contains unstaged changes. Aborting." | 
|  | 232 | fi | 
|  | 233 | if [ $result -eq 2 ]; then | 
|  | 234 | die "fatal: Index contains uncommited changes. Aborting." | 
|  | 235 | fi | 
|  | 236 | } | 
|  | 237 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 238 | require_local_branch() { | 
| Vincent Driessen | 6ee6223 | 2010-02-22 07:45:24 +0100 | [diff] [blame] | 239 | if ! git_local_branch_exists $1; then | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 240 | die "fatal: Local branch '$1' does not exist and is required." | 
|  | 241 | fi | 
|  | 242 | } | 
|  | 243 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 244 | require_remote_branch() { | 
|  | 245 | if ! has $1 $(git_remote_branches); then | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 246 | die "Remote branch '$1' does not exist and is required." | 
|  | 247 | fi | 
|  | 248 | } | 
|  | 249 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 250 | require_branch() { | 
|  | 251 | if ! has $1 $(git_all_branches); then | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 252 | die "Branch '$1' does not exist and is required." | 
|  | 253 | fi | 
|  | 254 | } | 
|  | 255 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 256 | require_branch_absent() { | 
|  | 257 | if has $1 $(git_all_branches); then | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 258 | die "Branch '$1' already exists. Pick another name." | 
|  | 259 | fi | 
|  | 260 | } | 
|  | 261 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 262 | require_tag_absent() { | 
|  | 263 | if has $1 $(git_all_tags); then | 
| Vincent Driessen | 1a2868b | 2010-02-07 23:23:16 +0100 | [diff] [blame] | 264 | die "Tag '$1' already exists. Pick another name." | 
|  | 265 | fi | 
|  | 266 | } | 
|  | 267 |  | 
| Vincent Driessen | 7832d6e | 2010-02-21 21:31:03 +0100 | [diff] [blame] | 268 | require_branches_equal() { | 
|  | 269 | require_local_branch "$1" | 
|  | 270 | require_remote_branch "$2" | 
|  | 271 | git_compare_branches "$1" "$2" | 
| Vincent Driessen | f46e290 | 2010-02-15 23:01:52 +0100 | [diff] [blame] | 272 | local status=$? | 
| Vincent Driessen | c3607ac | 2010-02-05 19:53:45 +0100 | [diff] [blame] | 273 | if [ $status -gt 0 ]; then | 
|  | 274 | warn "Branches '$1' and '$2' have diverged." | 
|  | 275 | if [ $status -eq 1 ]; then | 
|  | 276 | die "And branch '$1' may be fast-forwarded." | 
|  | 277 | elif [ $status -eq 2 ]; then | 
|  | 278 | # Warn here, since there is no harm in being ahead | 
|  | 279 | warn "And local branch '$1' is ahead of '$2'." | 
|  | 280 | else | 
|  | 281 | die "Branches need merging first." | 
|  | 282 | fi | 
|  | 283 | fi | 
|  | 284 | } |