blob: 865591e88ec73186c380594576cc668c98a84d5a [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
Vincent Driessen7832d6e2010-02-21 21:31:03 +010015#
16# Common functionality
17#
18
Vincent Driessenc3607ac2010-02-05 19:53:45 +010019# shell output
20warn() { echo "$@" >&2; }
21die() { warn "$@"; exit 1; }
22
23# set logic
24has() {
Vincent Driessenf46e2902010-02-15 23:01:52 +010025 local item=$1; shift
Vincent Driessenc3607ac2010-02-05 19:53:45 +010026 echo " $@ " | grep -q " $item "
27}
28
29# basic math
30min() { [ "$1" -le "$2" ] && echo "$1" || echo "$2"; }
31max() { [ "$1" -ge "$2" ] && echo "$1" || echo "$2"; }
32
33# basic string matching
34startswith() { [ "$1" != "${1#$2}" ]; }
35endswith() { [ "$1" != "${1#$2}" ]; }
36
37# convenience functions for checking shFlags flags
Vincent Driessenf46e2902010-02-15 23:01:52 +010038flag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; }
39noflag() { local FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -ne $FLAGS_TRUE ]; }
Vincent Driessenc3607ac2010-02-05 19:53:45 +010040
41#
42# Git specific common functionality
43#
44
Vincent Driessen7832d6e2010-02-21 21:31:03 +010045git_local_branches() { git branch | sed 's/^[* ] //'; }
46git_remote_branches() { git branch -r | sed 's/^[* ] //'; }
47git_all_branches() { ( git branch; git branch -r) | sed 's/^[* ] //'; }
48git_all_tags() { git tag; }
49
Vincent Driessenb25ab832010-02-20 11:21:23 +010050# check if this repo has been inited for gitflow
51gitflow_has_master_configured() {
52 local master=$(git config --get gitflow.branch.master)
Vincent Driessen7832d6e2010-02-21 21:31:03 +010053 [ "$master" != "" ] && git_local_branch_exists "$master"
Vincent Driessenb25ab832010-02-20 11:21:23 +010054}
55
56gitflow_has_develop_configured() {
57 local develop=$(git config --get gitflow.branch.develop)
Vincent Driessen7832d6e2010-02-21 21:31:03 +010058 [ "$develop" != "" ] && git_local_branch_exists "$develop"
Vincent Driessenb25ab832010-02-20 11:21:23 +010059}
60
Vincent Driessen1d8bb0d2010-02-20 16:46:38 +010061gitflow_has_prefixes_configured() {
62 git config --get gitflow.prefix.feature >/dev/null 2>&1 && \
63 git config --get gitflow.prefix.release >/dev/null 2>&1 && \
64 git config --get gitflow.prefix.hotfix >/dev/null 2>&1 && \
65 git config --get gitflow.prefix.support >/dev/null 2>&1 && \
66 git config --get gitflow.prefix.versiontag >/dev/null 2>&1
67}
68
Vincent Driessenb25ab832010-02-20 11:21:23 +010069gitflow_is_initialized() {
Vincent Driessen1d8bb0d2010-02-20 16:46:38 +010070 gitflow_has_master_configured && \
71 gitflow_has_develop_configured && \
72 [ "$(git config --get gitflow.branch.master)" != \
73 "$(git config --get gitflow.branch.develop)" ] && \
74 gitflow_has_prefixes_configured
Vincent Driessenb25ab832010-02-20 11:21:23 +010075}
76
Vincent Driessend72e4ac2010-02-16 21:33:51 +010077# loading settings that can be overridden using git config
78gitflow_load_settings() {
Vincent Driessencf6e92a2010-02-19 19:44:48 +010079 export DOT_GIT_DIR=$(git rev-parse --git-dir >/dev/null 2>&1)
Vincent Driessenc1598bf2010-02-20 16:52:48 +010080 export MASTER_BRANCH=$(git config --get gitflow.branch.master)
81 export DEVELOP_BRANCH=$(git config --get gitflow.branch.develop)
Vincent Driessend72e4ac2010-02-16 21:33:51 +010082 export ORIGIN=$(git config --get gitflow.origin || echo origin)
Vincent Driessend72e4ac2010-02-16 21:33:51 +010083}
84
Vincent Driessend0991262010-02-06 21:19:07 +010085#
Vincent Driessen7832d6e2010-02-21 21:31:03 +010086# gitflow_resolve_nameprefix
Vincent Driessend0991262010-02-06 21:19:07 +010087#
88# Inputs:
89# $1 = name prefix to resolve
90# $2 = branch prefix to use
91#
Vincent Driessen7832d6e2010-02-21 21:31:03 +010092# Searches branch names from git_local_branches() to look for a unique
Vincent Driessen21c7aa92010-02-16 20:57:35 +010093# branch name whose name starts with the given name prefix.
Vincent Driessend0991262010-02-06 21:19:07 +010094#
95# There are multiple exit codes possible:
96# 0: The unambiguous full name of the branch is written to stdout
97# (success)
98# 1: No match is found.
99# 2: Multiple matches found. These matches are written to stderr
100#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100101gitflow_resolve_nameprefix() {
Vincent Driessenf46e2902010-02-15 23:01:52 +0100102 local name=$1
103 local prefix=$2
104 local matches
105 local num_matches
Vincent Driessend0991262010-02-06 21:19:07 +0100106
107 # first, check if there is a perfect match
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100108 if has "$(git_local_branches)" "$prefix$name"; then
Vincent Driessend0991262010-02-06 21:19:07 +0100109 echo "$name"
110 return 0
111 fi
112
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100113 matches=$(echo "$(git_local_branches)" | grep "^$prefix$name")
Vincent Driessend0991262010-02-06 21:19:07 +0100114 num_matches=$(echo "$matches" | wc -l)
115 if [ -z "$matches" ]; then
116 # no prefix match, so take it literally
117 warn "No branch matches prefix '$name'"
118 return 1
119 else
120 if [ $num_matches -eq 1 ]; then
121 echo "${matches#$prefix}"
122 return 0
123 else
124 # multiple matches, cannot decide
125 warn "Multiple branches match prefix '$name':"
126 for match in $matches; do
127 warn "- $match"
128 done
129 return 2
130 fi
131 fi
132}
133
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100134git_current_branch() {
Vincent Driessend0991262010-02-06 21:19:07 +0100135 git branch | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g'
136}
137
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100138git_is_clean_working_tree() {
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100139 if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then
140 return 1
141 elif ! git diff-index --cached --quiet --ignore-submodules HEAD --; then
142 return 2
143 else
144 return 0
145 fi
146}
147
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100148require_git_repo() {
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100149 if ! git rev-parse --git-dir >/dev/null 2>&1; then
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100150 die "fatal: Not a git repository"
151 fi
152}
153
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100154require_gitflow_initialized() {
Vincent Driessenc1598bf2010-02-20 16:52:48 +0100155 if ! gitflow_is_initialized; then
156 die "fatal: Not a gitflow-enabled repo yet. Please run \"git flow init\" first."
Vincent Driessend72e4ac2010-02-16 21:33:51 +0100157 fi
158}
159
Vincent Driessenef43cbd2010-02-21 11:20:05 +0100160git_repo_is_headless() {
161 ! git rev-parse --quiet --verify HEAD >/dev/null 2>&1
162}
163
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100164require_clean_working_tree() {
165 git_is_clean_working_tree
Vincent Driessenf46e2902010-02-15 23:01:52 +0100166 local result=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100167 if [ $result -eq 1 ]; then
168 die "fatal: Working tree contains unstaged changes. Aborting."
169 fi
170 if [ $result -eq 2 ]; then
171 die "fatal: Index contains uncommited changes. Aborting."
172 fi
173}
174
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100175git_local_branch_exists() {
176 has $1 $(git_local_branches)
Vincent Driessenb25ab832010-02-20 11:21:23 +0100177}
178
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100179git_branch_exists() {
180 has $1 $(git_all_branches)
Vincent Driessen49094bd2010-02-18 12:05:01 +0100181}
182
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100183git_tag_exists() {
184 has $1 $(git_all_tags)
Vincent Driessen49094bd2010-02-18 12:05:01 +0100185}
186
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100187require_local_branch() {
188 if ! git_local_branch_exists; then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100189 die "fatal: Local branch '$1' does not exist and is required."
190 fi
191}
192
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100193require_remote_branch() {
194 if ! has $1 $(git_remote_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100195 die "Remote branch '$1' does not exist and is required."
196 fi
197}
198
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100199require_branch() {
200 if ! has $1 $(git_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100201 die "Branch '$1' does not exist and is required."
202 fi
203}
204
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100205require_branch_absent() {
206 if has $1 $(git_all_branches); then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100207 die "Branch '$1' already exists. Pick another name."
208 fi
209}
210
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100211require_tag_absent() {
212 if has $1 $(git_all_tags); then
Vincent Driessen1a2868b2010-02-07 23:23:16 +0100213 die "Tag '$1' already exists. Pick another name."
214 fi
215}
216
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100217#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100218# git_compare_branches()
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100219#
220# Tests whether branches and their "origin" counterparts have diverged and need
221# merging first. It returns error codes to provide more detail, like so:
222#
223# 0 Branch heads point to the same commit
224# 1 First given branch needs fast-forwarding
225# 2 Second given branch needs fast-forwarding
226# 3 Branch needs a real merge
Vincent Driessen49094bd2010-02-18 12:05:01 +0100227# 4 There is no merge base, i.e. the branches have no common ancestors
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100228#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100229git_compare_branches() {
Vincent Driessenf46e2902010-02-15 23:01:52 +0100230 local commit1=$(git rev-parse "$1")
231 local commit2=$(git rev-parse "$2")
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100232 if [ "$commit1" != "$commit2" ]; then
Vincent Driessenf46e2902010-02-15 23:01:52 +0100233 local base=$(git merge-base "$commit1" "$commit2")
Vincent Driessen49094bd2010-02-18 12:05:01 +0100234 if [ $? -ne 0 ]; then
235 return 4
236 elif [ "$commit1" = "$base" ]; then
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100237 return 1
238 elif [ "$commit2" = "$base" ]; then
239 return 2
240 else
241 return 3
242 fi
243 else
244 return 0
245 fi
246}
247
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100248require_branches_equal() {
249 require_local_branch "$1"
250 require_remote_branch "$2"
251 git_compare_branches "$1" "$2"
Vincent Driessenf46e2902010-02-15 23:01:52 +0100252 local status=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100253 if [ $status -gt 0 ]; then
254 warn "Branches '$1' and '$2' have diverged."
255 if [ $status -eq 1 ]; then
256 die "And branch '$1' may be fast-forwarded."
257 elif [ $status -eq 2 ]; then
258 # Warn here, since there is no harm in being ahead
259 warn "And local branch '$1' is ahead of '$2'."
260 else
261 die "Branches need merging first."
262 fi
263 fi
264}
265
266#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100267# git_is_branch_merged_into()
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100268#
269# Checks whether branch $1 is succesfully merged into $2
270#
Vincent Driessen7832d6e2010-02-21 21:31:03 +0100271git_is_branch_merged_into() {
Vincent Driessenf46e2902010-02-15 23:01:52 +0100272 local subject=$1
273 local base=$2
274 local all_merges=$(git branch --contains $subject | sed 's/^[* ] //')
Vincent Driessen27592dd2010-02-06 14:45:39 +0100275 has $base $all_merges
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100276}