blob: 1b137391a401d8321c8da6dd32589276668d8788 [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() {
48 echo "$1" | sed 's/\([\.\+\$\*]\)/\\\1/g'
49}
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
Adam Gibbinscf3da5a2010-08-22 19:13:34 +010073git_local_branches() { git branch --no-color | sed 's/^[* ] //'; }
74git_remote_branches() { git branch -r --no-color | sed 's/^[* ] //'; }
75git_all_branches() { ( git branch --no-color; git branch -r --no-color) | sed 's/^[* ] //'; }
Vincent Driessen7832d6e2010-02-21 21:31:03 +010076git_all_tags() { git tag; }
77
Vincent Driessen55c15532010-02-22 07:28:27 +010078git_current_branch() {
Adam Gibbinscf3da5a2010-08-22 19:13:34 +010079 git branch --no-color | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g'
Vincent Driessen55c15532010-02-22 07:28:27 +010080}
81
82git_is_clean_working_tree() {
83 if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then
84 return 1
85 elif ! git diff-index --cached --quiet --ignore-submodules HEAD --; then
86 return 2
87 else
88 return 0
89 fi
90}
91
92git_repo_is_headless() {
93 ! git rev-parse --quiet --verify HEAD >/dev/null 2>&1
94}
95
96git_local_branch_exists() {
97 has $1 $(git_local_branches)
98}
99
Emre Berge Ergenekon62c339e2011-11-27 22:07:57 -0800100git_remote_branch_exists() {
101 has $1 $(git_remote_branches)
102}
103
Vincent Driessen55c15532010-02-22 07:28:27 +0100104git_branch_exists() {
105 has $1 $(git_all_branches)
106}
107
108git_tag_exists() {
109 has $1 $(git_all_tags)
110}
111
112#
113# git_compare_branches()
114#
115# Tests whether branches and their "origin" counterparts have diverged and need
116# merging first. It returns error codes to provide more detail, like so:
117#
118# 0 Branch heads point to the same commit
119# 1 First given branch needs fast-forwarding
120# 2 Second given branch needs fast-forwarding
121# 3 Branch needs a real merge
122# 4 There is no merge base, i.e. the branches have no common ancestors
123#
124git_compare_branches() {
125 local commit1=$(git rev-parse "$1")
126 local commit2=$(git rev-parse "$2")
127 if [ "$commit1" != "$commit2" ]; then
128 local base=$(git merge-base "$commit1" "$commit2")
129 if [ $? -ne 0 ]; then
130 return 4
131 elif [ "$commit1" = "$base" ]; then
132 return 1
133 elif [ "$commit2" = "$base" ]; then
134 return 2
135 else
136 return 3
137 fi
138 else
139 return 0
140 fi
141}
142
143#
144# git_is_branch_merged_into()
145#
146# Checks whether branch $1 is succesfully merged into $2
147#
148git_is_branch_merged_into() {
149 local subject=$1
150 local base=$2
Brian St. Pierre5ff0b472010-11-03 16:40:58 -0400151 local all_merges="$(git branch --no-color --contains $subject | sed 's/^[* ] //')"
Vincent Driessen55c15532010-02-22 07:28:27 +0100152 has $base $all_merges
153}
154
155#
156# gitflow specific common functionality
157#
158
Vincent Driessenb25ab832010-02-20 11:21:23 +0100159# check if this repo has been inited for gitflow
160gitflow_has_master_configured() {
161 local master=$(git config --get gitflow.branch.master)
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100162 [ "$master" != "" ] && git_local_branch_exists "$master"
Vincent Driessenb25ab832010-02-20 11:21:23 +0100163}
164
165gitflow_has_develop_configured() {
166 local develop=$(git config --get gitflow.branch.develop)
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100167 [ "$develop" != "" ] && git_local_branch_exists "$develop"
Vincent Driessenb25ab832010-02-20 11:21:23 +0100168}
169
Vincent Driessen1d8bb0d2010-02-20 16:46:38 +0100170gitflow_has_prefixes_configured() {
171 git config --get gitflow.prefix.feature >/dev/null 2>&1 && \
172 git config --get gitflow.prefix.release >/dev/null 2>&1 && \
173 git config --get gitflow.prefix.hotfix >/dev/null 2>&1 && \
174 git config --get gitflow.prefix.support >/dev/null 2>&1 && \
175 git config --get gitflow.prefix.versiontag >/dev/null 2>&1
176}
177
Peter van der Doescbe180f2011-12-23 20:06:48 -0500178gitflow_has_dirs_configured() {
179 git config --get gitflow.dir.main >/dev/null 2>&1 && \
180 git config --get gitflow.dir.hooks >/dev/null 2>&1
181}
182
Vincent Driessenb25ab832010-02-20 11:21:23 +0100183gitflow_is_initialized() {
Vincent Driessen1d8bb0d2010-02-20 16:46:38 +0100184 gitflow_has_master_configured && \
185 gitflow_has_develop_configured && \
186 [ "$(git config --get gitflow.branch.master)" != \
187 "$(git config --get gitflow.branch.develop)" ] && \
Peter van der Doescbe180f2011-12-23 20:06:48 -0500188 gitflow_has_prefixes_configured && \
189 gitflow_has_dirs_configured
Vincent Driessenb25ab832010-02-20 11:21:23 +0100190}
191
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100192# loading settings that can be overridden using git config
193gitflow_load_settings() {
Vincent Driessena7a89cd2011-04-17 08:41:36 +0200194 export DOT_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100195 export MASTER_BRANCH=$(git config --get gitflow.branch.master)
196 export DEVELOP_BRANCH=$(git config --get gitflow.branch.develop)
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100197 export ORIGIN=$(git config --get gitflow.origin || echo origin)
Peter van der Doescbe180f2011-12-23 20:06:48 -0500198 export GITFLOW_DIR_HOOKS=$(git config --get gitflow.dir.main)$(git config --get gitflow.dir.hooks)
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100199}
200
Vincent Driessend0991262010-02-06 21:19:07 +0100201#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100202# gitflow_resolve_nameprefix
Vincent Driessend0991262010-02-06 21:19:07 +0100203#
204# Inputs:
205# $1 = name prefix to resolve
206# $2 = branch prefix to use
207#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100208# Searches branch names from git_local_branches() to look for a unique
Vincent Driessen21c7aa92010-02-16 20:57:35 +0100209# branch name whose name starts with the given name prefix.
Vincent Driessend0991262010-02-06 21:19:07 +0100210#
211# There are multiple exit codes possible:
212# 0: The unambiguous full name of the branch is written to stdout
213# (success)
214# 1: No match is found.
215# 2: Multiple matches found. These matches are written to stderr
216#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100217gitflow_resolve_nameprefix() {
Vincent Driessenf46e2902010-02-15 23:01:52 +0100218 local name=$1
219 local prefix=$2
220 local matches
221 local num_matches
Vincent Driessend0991262010-02-06 21:19:07 +0100222
223 # first, check if there is a perfect match
Vincent Driessen5b05ad72010-05-27 11:51:50 -0700224 if git_local_branch_exists "$prefix$name"; then
Vincent Driessend0991262010-02-06 21:19:07 +0100225 echo "$name"
226 return 0
227 fi
228
Vincent Driessen1b471a62011-02-04 08:25:38 +0100229 matches=$(echo "$(git_local_branches)" | grep "^$(escape "$prefix$name")")
Vincent Driessend0991262010-02-06 21:19:07 +0100230 num_matches=$(echo "$matches" | wc -l)
231 if [ -z "$matches" ]; then
232 # no prefix match, so take it literally
233 warn "No branch matches prefix '$name'"
234 return 1
235 else
236 if [ $num_matches -eq 1 ]; then
237 echo "${matches#$prefix}"
238 return 0
239 else
240 # multiple matches, cannot decide
241 warn "Multiple branches match prefix '$name':"
242 for match in $matches; do
243 warn "- $match"
244 done
245 return 2
246 fi
247 fi
248}
249
Vincent Driessen55c15532010-02-22 07:28:27 +0100250#
251# Assertions for use in git-flow subcommands
252#
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100253
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100254require_git_repo() {
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100255 if ! git rev-parse --git-dir >/dev/null 2>&1; then
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100256 die "fatal: Not a git repository"
257 fi
258}
259
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100260require_gitflow_initialized() {
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100261 if ! gitflow_is_initialized; then
262 die "fatal: Not a gitflow-enabled repo yet. Please run \"git flow init\" first."
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100263 fi
264}
265
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100266require_clean_working_tree() {
267 git_is_clean_working_tree
Vincent Driessenf46e2902010-02-15 23:01:52 +0100268 local result=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100269 if [ $result -eq 1 ]; then
270 die "fatal: Working tree contains unstaged changes. Aborting."
271 fi
272 if [ $result -eq 2 ]; then
273 die "fatal: Index contains uncommited changes. Aborting."
274 fi
275}
276
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100277require_local_branch() {
Vincent Driessen6ee62232010-02-22 07:45:24 +0100278 if ! git_local_branch_exists $1; then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100279 die "fatal: Local branch '$1' does not exist and is required."
280 fi
281}
282
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100283require_remote_branch() {
284 if ! has $1 $(git_remote_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100285 die "Remote branch '$1' does not exist and is required."
286 fi
287}
288
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100289require_branch() {
290 if ! has $1 $(git_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100291 die "Branch '$1' does not exist and is required."
292 fi
293}
294
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100295require_branch_absent() {
296 if has $1 $(git_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100297 die "Branch '$1' already exists. Pick another name."
298 fi
299}
300
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100301require_tag_absent() {
Jannis Leidel8f280e02011-03-28 18:48:39 +0200302 for tag in $(git_all_tags); do
303 if [ "$1" = "$tag" ]; then
304 die "Tag '$1' already exists. Pick another name."
305 fi
306 done
Vincent Driessen1a2868b2010-02-07 23:23:16 +0100307}
308
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100309require_branches_equal() {
310 require_local_branch "$1"
311 require_remote_branch "$2"
312 git_compare_branches "$1" "$2"
Vincent Driessenf46e2902010-02-15 23:01:52 +0100313 local status=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100314 if [ $status -gt 0 ]; then
315 warn "Branches '$1' and '$2' have diverged."
316 if [ $status -eq 1 ]; then
317 die "And branch '$1' may be fast-forwarded."
318 elif [ $status -eq 2 ]; then
319 # Warn here, since there is no harm in being ahead
320 warn "And local branch '$1' is ahead of '$2'."
321 else
322 die "Branches need merging first."
323 fi
324 fi
325}