blob: 33274053347f4eec2f27dd8bceca967b89ae02d5 [file] [log] [blame]
Vincent Driessenc3607ac2010-02-05 19:53:45 +01001#
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:
Vincent Driessenddb350b2010-07-09 09:42:09 +02006# http://nvie.com/git-model
Vincent Driessenc3607ac2010-02-05 19:53:45 +01007#
8# Feel free to contribute to this project at:
9# http://github.com/nvie/gitflow
10#
Vincent Driessend72acba2010-04-04 16:10:17 +020011# Copyright 2010 Vincent Driessen. All rights reserved.
12#
13# Redistribution and use in source and binary forms, with or without
14# modification, are permitted provided that the following conditions are met:
15#
16# 1. Redistributions of source code must retain the above copyright notice,
17# this list of conditions and the following disclaimer.
18#
19# 2. Redistributions in binary form must reproduce the above copyright
20# notice, this list of conditions and the following disclaimer in the
21# documentation and/or other materials provided with the distribution.
22#
23# THIS SOFTWARE IS PROVIDED BY VINCENT DRIESSEN ``AS IS'' AND ANY EXPRESS OR
24# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
26# EVENT SHALL VINCENT DRIESSEN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
30# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33#
34# The views and conclusions contained in the software and documentation are
35# those of the authors and should not be interpreted as representing official
36# policies, either expressed or implied, of Vincent Driessen.
Vincent Driessenc3607ac2010-02-05 19:53:45 +010037#
38
Vincent Driessen7832d6e2010-02-21 21:31:03 +010039#
40# Common functionality
41#
42
Vincent Driessenc3607ac2010-02-05 19:53:45 +010043# shell output
44warn() { echo "$@" >&2; }
45die() { warn "$@"; exit 1; }
46
Vincent Driessen1b471a62011-02-04 08:25:38 +010047escape() {
Randy Merrillafbf92c2012-02-08 09:33:04 -080048 echo "$1" | sed 's/\([\.\$\*]\)/\\\1/g'
Vincent Driessen1b471a62011-02-04 08:25:38 +010049}
50
Vincent Driessenc3607ac2010-02-05 19:53:45 +010051# set logic
52has() {
Vincent Driessenf46e2902010-02-15 23:01:52 +010053 local item=$1; shift
Vincent Driessen1b471a62011-02-04 08:25:38 +010054 echo " $@ " | grep -q " $(escape $item) "
Vincent Driessenc3607ac2010-02-05 19:53:45 +010055}
56
57# basic math
58min() { [ "$1" -le "$2" ] && echo "$1" || echo "$2"; }
59max() { [ "$1" -ge "$2" ] && echo "$1" || echo "$2"; }
60
61# basic string matching
62startswith() { [ "$1" != "${1#$2}" ]; }
Vincent Driessene0d8af32010-02-22 12:21:36 +010063endswith() { [ "$1" != "${1%$2}" ]; }
Vincent Driessenc3607ac2010-02-05 19:53:45 +010064
65# convenience functions for checking shFlags flags
Vincent Driessenf46e2902010-02-15 23:01:52 +010066flag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; }
67noflag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -ne $FLAGS_TRUE ]; }
Vincent Driessenc3607ac2010-02-05 19:53:45 +010068
69#
70# Git specific common functionality
71#
72
Jerome Baum5bca8d92012-09-25 15:46:44 +020073git_do() {
74 # equivalent to git, used to indicate actions that make modifications
75 if flag show_commands; then
76 echo "git $@" >&2
77 fi
78 git "$@"
79}
80
Adam Gibbinscf3da5a2010-08-22 19:13:34 +010081git_local_branches() { git branch --no-color | sed 's/^[* ] //'; }
82git_remote_branches() { git branch -r --no-color | sed 's/^[* ] //'; }
83git_all_branches() { ( git branch --no-color; git branch -r --no-color) | sed 's/^[* ] //'; }
Vincent Driessen7832d6e2010-02-21 21:31:03 +010084git_all_tags() { git tag; }
85
Vincent Driessen55c15532010-02-22 07:28:27 +010086git_current_branch() {
Adam Gibbinscf3da5a2010-08-22 19:13:34 +010087 git branch --no-color | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g'
Vincent Driessen55c15532010-02-22 07:28:27 +010088}
89
90git_is_clean_working_tree() {
91 if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then
92 return 1
93 elif ! git diff-index --cached --quiet --ignore-submodules HEAD --; then
94 return 2
95 else
96 return 0
97 fi
98}
99
100git_repo_is_headless() {
101 ! git rev-parse --quiet --verify HEAD >/dev/null 2>&1
102}
103
104git_local_branch_exists() {
105 has $1 $(git_local_branches)
106}
107
Emre Berge Ergenekon62c339e2011-11-27 22:07:57 -0800108git_remote_branch_exists() {
109 has $1 $(git_remote_branches)
110}
111
Vincent Driessen55c15532010-02-22 07:28:27 +0100112git_branch_exists() {
113 has $1 $(git_all_branches)
114}
115
116git_tag_exists() {
117 has $1 $(git_all_tags)
118}
119
120#
121# git_compare_branches()
122#
123# Tests whether branches and their "origin" counterparts have diverged and need
124# merging first. It returns error codes to provide more detail, like so:
125#
126# 0 Branch heads point to the same commit
127# 1 First given branch needs fast-forwarding
128# 2 Second given branch needs fast-forwarding
129# 3 Branch needs a real merge
130# 4 There is no merge base, i.e. the branches have no common ancestors
131#
132git_compare_branches() {
133 local commit1=$(git rev-parse "$1")
134 local commit2=$(git rev-parse "$2")
135 if [ "$commit1" != "$commit2" ]; then
136 local base=$(git merge-base "$commit1" "$commit2")
137 if [ $? -ne 0 ]; then
138 return 4
139 elif [ "$commit1" = "$base" ]; then
140 return 1
141 elif [ "$commit2" = "$base" ]; then
142 return 2
143 else
144 return 3
145 fi
146 else
147 return 0
148 fi
149}
150
151#
152# git_is_branch_merged_into()
153#
154# Checks whether branch $1 is succesfully merged into $2
155#
156git_is_branch_merged_into() {
157 local subject=$1
158 local base=$2
Brian St. Pierre5ff0b472010-11-03 16:40:58 -0400159 local all_merges="$(git branch --no-color --contains $subject | sed 's/^[* ] //')"
Vincent Driessen55c15532010-02-22 07:28:27 +0100160 has $base $all_merges
161}
162
163#
164# gitflow specific common functionality
165#
166
Vincent Driessenb25ab832010-02-20 11:21:23 +0100167# check if this repo has been inited for gitflow
168gitflow_has_master_configured() {
169 local master=$(git config --get gitflow.branch.master)
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100170 [ "$master" != "" ] && git_local_branch_exists "$master"
Vincent Driessenb25ab832010-02-20 11:21:23 +0100171}
172
173gitflow_has_develop_configured() {
174 local develop=$(git config --get gitflow.branch.develop)
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100175 [ "$develop" != "" ] && git_local_branch_exists "$develop"
Vincent Driessenb25ab832010-02-20 11:21:23 +0100176}
177
Vincent Driessen1d8bb0d2010-02-20 16:46:38 +0100178gitflow_has_prefixes_configured() {
179 git config --get gitflow.prefix.feature >/dev/null 2>&1 && \
180 git config --get gitflow.prefix.release >/dev/null 2>&1 && \
181 git config --get gitflow.prefix.hotfix >/dev/null 2>&1 && \
182 git config --get gitflow.prefix.support >/dev/null 2>&1 && \
183 git config --get gitflow.prefix.versiontag >/dev/null 2>&1
184}
185
Vincent Driessenb25ab832010-02-20 11:21:23 +0100186gitflow_is_initialized() {
Vincent Driessen1d8bb0d2010-02-20 16:46:38 +0100187 gitflow_has_master_configured && \
188 gitflow_has_develop_configured && \
189 [ "$(git config --get gitflow.branch.master)" != \
190 "$(git config --get gitflow.branch.develop)" ] && \
191 gitflow_has_prefixes_configured
Vincent Driessenb25ab832010-02-20 11:21:23 +0100192}
193
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100194# loading settings that can be overridden using git config
195gitflow_load_settings() {
Vincent Driessena7a89cd2011-04-17 08:41:36 +0200196 export DOT_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100197 export MASTER_BRANCH=$(git config --get gitflow.branch.master)
198 export DEVELOP_BRANCH=$(git config --get gitflow.branch.develop)
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100199 export ORIGIN=$(git config --get gitflow.origin || echo origin)
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100200}
201
Vincent Driessend0991262010-02-06 21:19:07 +0100202#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100203# gitflow_resolve_nameprefix
Vincent Driessend0991262010-02-06 21:19:07 +0100204#
205# Inputs:
206# $1 = name prefix to resolve
207# $2 = branch prefix to use
208#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100209# Searches branch names from git_local_branches() to look for a unique
Vincent Driessen21c7aa92010-02-16 20:57:35 +0100210# branch name whose name starts with the given name prefix.
Vincent Driessend0991262010-02-06 21:19:07 +0100211#
212# There are multiple exit codes possible:
213# 0: The unambiguous full name of the branch is written to stdout
214# (success)
215# 1: No match is found.
216# 2: Multiple matches found. These matches are written to stderr
217#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100218gitflow_resolve_nameprefix() {
Vincent Driessenf46e2902010-02-15 23:01:52 +0100219 local name=$1
220 local prefix=$2
221 local matches
222 local num_matches
Vincent Driessend0991262010-02-06 21:19:07 +0100223
224 # first, check if there is a perfect match
Vincent Driessen5b05ad72010-05-27 11:51:50 -0700225 if git_local_branch_exists "$prefix$name"; then
Vincent Driessend0991262010-02-06 21:19:07 +0100226 echo "$name"
227 return 0
228 fi
229
Vincent Driessen1b471a62011-02-04 08:25:38 +0100230 matches=$(echo "$(git_local_branches)" | grep "^$(escape "$prefix$name")")
Vincent Driessend0991262010-02-06 21:19:07 +0100231 num_matches=$(echo "$matches" | wc -l)
232 if [ -z "$matches" ]; then
233 # no prefix match, so take it literally
234 warn "No branch matches prefix '$name'"
235 return 1
236 else
237 if [ $num_matches -eq 1 ]; then
238 echo "${matches#$prefix}"
239 return 0
240 else
241 # multiple matches, cannot decide
242 warn "Multiple branches match prefix '$name':"
243 for match in $matches; do
244 warn "- $match"
245 done
246 return 2
247 fi
248 fi
249}
250
Vincent Driessen55c15532010-02-22 07:28:27 +0100251#
252# Assertions for use in git-flow subcommands
253#
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100254
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100255require_git_repo() {
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100256 if ! git rev-parse --git-dir >/dev/null 2>&1; then
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100257 die "fatal: Not a git repository"
258 fi
259}
260
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100261require_gitflow_initialized() {
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100262 if ! gitflow_is_initialized; then
263 die "fatal: Not a gitflow-enabled repo yet. Please run \"git flow init\" first."
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100264 fi
265}
266
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100267require_clean_working_tree() {
268 git_is_clean_working_tree
Vincent Driessenf46e2902010-02-15 23:01:52 +0100269 local result=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100270 if [ $result -eq 1 ]; then
271 die "fatal: Working tree contains unstaged changes. Aborting."
272 fi
273 if [ $result -eq 2 ]; then
274 die "fatal: Index contains uncommited changes. Aborting."
275 fi
276}
277
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100278require_local_branch() {
Vincent Driessen6ee62232010-02-22 07:45:24 +0100279 if ! git_local_branch_exists $1; then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100280 die "fatal: Local branch '$1' does not exist and is required."
281 fi
282}
283
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100284require_remote_branch() {
285 if ! has $1 $(git_remote_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100286 die "Remote branch '$1' does not exist and is required."
287 fi
288}
289
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100290require_branch() {
291 if ! has $1 $(git_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100292 die "Branch '$1' does not exist and is required."
293 fi
294}
295
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100296require_branch_absent() {
297 if has $1 $(git_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100298 die "Branch '$1' already exists. Pick another name."
299 fi
300}
301
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100302require_tag_absent() {
Jannis Leidel8f280e02011-03-28 18:48:39 +0200303 for tag in $(git_all_tags); do
304 if [ "$1" = "$tag" ]; then
305 die "Tag '$1' already exists. Pick another name."
306 fi
307 done
Vincent Driessen1a2868b2010-02-07 23:23:16 +0100308}
309
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100310require_branches_equal() {
311 require_local_branch "$1"
312 require_remote_branch "$2"
313 git_compare_branches "$1" "$2"
Vincent Driessenf46e2902010-02-15 23:01:52 +0100314 local status=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100315 if [ $status -gt 0 ]; then
316 warn "Branches '$1' and '$2' have diverged."
317 if [ $status -eq 1 ]; then
318 die "And branch '$1' may be fast-forwarded."
319 elif [ $status -eq 2 ]; then
320 # Warn here, since there is no harm in being ahead
321 warn "And local branch '$1' is ahead of '$2'."
322 else
323 die "Branches need merging first."
324 fi
325 fi
326}