blob: 5b2a4f682ddcc402dbec89d3688c099d28fa72b9 [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:
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
15# shell output
16warn() { echo "$@" >&2; }
17die() { warn "$@"; exit 1; }
18
19# set logic
20has() {
Vincent Driessenf46e2902010-02-15 23:01:52 +010021 local item=$1; shift
Vincent Driessenc3607ac2010-02-05 19:53:45 +010022 echo " $@ " | grep -q " $item "
23}
24
25# basic math
26min() { [ "$1" -le "$2" ] && echo "$1" || echo "$2"; }
27max() { [ "$1" -ge "$2" ] && echo "$1" || echo "$2"; }
28
29# basic string matching
30startswith() { [ "$1" != "${1#$2}" ]; }
31endswith() { [ "$1" != "${1#$2}" ]; }
32
33# convenience functions for checking shFlags flags
Vincent Driessenf46e2902010-02-15 23:01:52 +010034flag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; }
35noflag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -ne $FLAGS_TRUE ]; }
Vincent Driessenc3607ac2010-02-05 19:53:45 +010036
37#
38# Git specific common functionality
39#
40
41# get all available branches
Vincent Driessen21c7aa92010-02-16 20:57:35 +010042gitflow_local_branches() { git branch | sed 's/^[* ] //'; }
43gitflow_remote_branches() { git branch -r | sed 's/^[* ] //'; }
Vincent Driessen49094bd2010-02-18 12:05:01 +010044gitflow_all_branches() { git branch -a | sed 's/^[* ] //'; }
Vincent Driessen21c7aa92010-02-16 20:57:35 +010045gitflow_all_tags() { git tag; }
Vincent Driessenc3607ac2010-02-05 19:53:45 +010046
Vincent Driessend72e4ac2010-02-16 21:33:51 +010047# loading settings that can be overridden using git config
48gitflow_load_settings() {
49 export GIT_DIR=$(git rev-parse --git-dir >/dev/null 2>&1)
50 export MASTER_BRANCH=$(git config --get gitflow.branch.master || echo master)
51 export DEVELOP_BRANCH=$(git config --get gitflow.branch.develop || echo develop)
52 export ORIGIN=$(git config --get gitflow.origin || echo origin)
53 export README=$(git config --get gitflow.readme || echo README)
54}
55
Vincent Driessend0991262010-02-06 21:19:07 +010056#
57# resolve_nameprefix
58#
59# Inputs:
60# $1 = name prefix to resolve
61# $2 = branch prefix to use
62#
Vincent Driessen21c7aa92010-02-16 20:57:35 +010063# Searches branch names from gitflow_local_branches() to look for a unique
64# branch name whose name starts with the given name prefix.
Vincent Driessend0991262010-02-06 21:19:07 +010065#
66# There are multiple exit codes possible:
67# 0: The unambiguous full name of the branch is written to stdout
68# (success)
69# 1: No match is found.
70# 2: Multiple matches found. These matches are written to stderr
71#
72resolve_nameprefix() {
Vincent Driessenf46e2902010-02-15 23:01:52 +010073 local name=$1
74 local prefix=$2
75 local matches
76 local num_matches
Vincent Driessend0991262010-02-06 21:19:07 +010077
78 # first, check if there is a perfect match
Vincent Driessen21c7aa92010-02-16 20:57:35 +010079 if has "$(gitflow_local_branches)" "$prefix$name"; then
Vincent Driessend0991262010-02-06 21:19:07 +010080 echo "$name"
81 return 0
82 fi
83
Vincent Driessen21c7aa92010-02-16 20:57:35 +010084 matches=$(echo "$(gitflow_local_branches)" | grep "^$prefix$name")
Vincent Driessend0991262010-02-06 21:19:07 +010085 num_matches=$(echo "$matches" | wc -l)
86 if [ -z "$matches" ]; then
87 # no prefix match, so take it literally
88 warn "No branch matches prefix '$name'"
89 return 1
90 else
91 if [ $num_matches -eq 1 ]; then
92 echo "${matches#$prefix}"
93 return 0
94 else
95 # multiple matches, cannot decide
96 warn "Multiple branches match prefix '$name':"
97 for match in $matches; do
98 warn "- $match"
99 done
100 return 2
101 fi
102 fi
103}
104
105gitflow_current_branch() {
106 git branch | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g'
107}
108
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100109gitflow_test_clean_working_tree() {
110 if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then
111 return 1
112 elif ! git diff-index --cached --quiet --ignore-submodules HEAD --; then
113 return 2
114 else
115 return 0
116 fi
117}
118
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100119gitflow_require_git_repo() {
120 if ! git rev-parse --git-dir >/dev/null 2>&1; then
121 die "Not a git repository"
122 fi
123}
124
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100125gitflow_require_clean_working_tree() {
126 gitflow_test_clean_working_tree
Vincent Driessenf46e2902010-02-15 23:01:52 +0100127 local result=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100128 if [ $result -eq 1 ]; then
129 die "fatal: Working tree contains unstaged changes. Aborting."
130 fi
131 if [ $result -eq 2 ]; then
132 die "fatal: Index contains uncommited changes. Aborting."
133 fi
134}
135
Vincent Driessen49094bd2010-02-18 12:05:01 +0100136gitflow_branch_exists() {
137 has $1 $(gitflow_all_branches)
138}
139
140gitflow_tag_exists() {
141 has $1 $(gitflow_all_tags)
142}
143
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100144gitflow_require_local_branch() {
Vincent Driessen21c7aa92010-02-16 20:57:35 +0100145 if ! has $1 $(gitflow_local_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100146 die "fatal: Local branch '$1' does not exist and is required."
147 fi
148}
149
150gitflow_require_remote_branch() {
Vincent Driessen21c7aa92010-02-16 20:57:35 +0100151 if ! has $1 $(gitflow_remote_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100152 die "Remote branch '$1' does not exist and is required."
153 fi
154}
155
156gitflow_require_branch() {
Vincent Driessen21c7aa92010-02-16 20:57:35 +0100157 if ! has $1 $(gitflow_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100158 die "Branch '$1' does not exist and is required."
159 fi
160}
161
162gitflow_require_branch_absent() {
Vincent Driessen21c7aa92010-02-16 20:57:35 +0100163 if has $1 $(gitflow_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100164 die "Branch '$1' already exists. Pick another name."
165 fi
166}
167
Vincent Driessen1a2868b2010-02-07 23:23:16 +0100168gitflow_require_tag_absent() {
Vincent Driessen21c7aa92010-02-16 20:57:35 +0100169 if has $1 $(gitflow_all_tags); then
Vincent Driessen1a2868b2010-02-07 23:23:16 +0100170 die "Tag '$1' already exists. Pick another name."
171 fi
172}
173
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100174#
175# gitflow_test_branches_equal()
176#
177# Tests whether branches and their "origin" counterparts have diverged and need
178# merging first. It returns error codes to provide more detail, like so:
179#
180# 0 Branch heads point to the same commit
181# 1 First given branch needs fast-forwarding
182# 2 Second given branch needs fast-forwarding
183# 3 Branch needs a real merge
Vincent Driessen49094bd2010-02-18 12:05:01 +0100184# 4 There is no merge base, i.e. the branches have no common ancestors
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100185#
186gitflow_test_branches_equal() {
Vincent Driessenf46e2902010-02-15 23:01:52 +0100187 local commit1=$(git rev-parse "$1")
188 local commit2=$(git rev-parse "$2")
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100189 if [ "$commit1" != "$commit2" ]; then
Vincent Driessenf46e2902010-02-15 23:01:52 +0100190 local base=$(git merge-base "$commit1" "$commit2")
Vincent Driessen49094bd2010-02-18 12:05:01 +0100191 if [ $? -ne 0 ]; then
192 return 4
193 elif [ "$commit1" = "$base" ]; then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100194 return 1
195 elif [ "$commit2" = "$base" ]; then
196 return 2
197 else
198 return 3
199 fi
200 else
201 return 0
202 fi
203}
204
205gitflow_require_branches_equal() {
206 gitflow_require_local_branch "$1"
207 gitflow_require_remote_branch "$2"
208 gitflow_test_branches_equal "$1" "$2"
Vincent Driessenf46e2902010-02-15 23:01:52 +0100209 local status=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100210 if [ $status -gt 0 ]; then
211 warn "Branches '$1' and '$2' have diverged."
212 if [ $status -eq 1 ]; then
213 die "And branch '$1' may be fast-forwarded."
214 elif [ $status -eq 2 ]; then
215 # Warn here, since there is no harm in being ahead
216 warn "And local branch '$1' is ahead of '$2'."
217 else
218 die "Branches need merging first."
219 fi
220 fi
221}
222
223#
224# gitflow_is_branch_merged_into()
225#
226# Checks whether branch $1 is succesfully merged into $2
227#
228gitflow_is_branch_merged_into() {
Vincent Driessenf46e2902010-02-15 23:01:52 +0100229 local subject=$1
230 local base=$2
231 local all_merges=$(git branch --contains $subject | sed 's/^[* ] //')
Vincent Driessen27592dd2010-02-06 14:45:39 +0100232 has $base $all_merges
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100233}