blob: 4f2a85aae68803661d24900cc01428c7a815f656 [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 Driessen27592dd2010-02-06 14:45:39 +010021 typeset 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 Driessen27592dd2010-02-06 14:45:39 +010034flag() { typeset FLAG; eval FLAG='$FLAGS_'$1; [ $FLAG -eq $FLAGS_TRUE ]; }
35noflag() { typeset 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
42LOCAL_BRANCHES=$(git branch | sed 's/^[* ] //')
43REMOTE_BRANCHES=$(git branch -r | sed 's/^[* ] //')
44ALL_BRANCHES="$LOCAL_BRANCHES $REMOTE_BRANCHES"
Vincent Driessen1a2868b2010-02-07 23:23:16 +010045ALL_TAGS=$(git tag)
Vincent Driessenc3607ac2010-02-05 19:53:45 +010046
Vincent Driessend0991262010-02-06 21:19:07 +010047#
48# resolve_nameprefix
49#
50# Inputs:
51# $1 = name prefix to resolve
52# $2 = branch prefix to use
53#
54# Searches branch names from LOCAL_BRANCHES to look for a unique branch
55# name whose name starts with the given name prefix.
56#
57# There are multiple exit codes possible:
58# 0: The unambiguous full name of the branch is written to stdout
59# (success)
60# 1: No match is found.
61# 2: Multiple matches found. These matches are written to stderr
62#
63resolve_nameprefix() {
64 typeset name="$1"
65 typeset prefix="$2"
66 typeset matches
67 typeset -i num_matches
68
69 # first, check if there is a perfect match
70 if has "$LOCAL_BRANCHES" "$prefix$name"; then
71 echo "$name"
72 return 0
73 fi
74
75 matches="$(echo "$LOCAL_BRANCHES" | grep "^$prefix$name")"
76 num_matches=$(echo "$matches" | wc -l)
77 if [ -z "$matches" ]; then
78 # no prefix match, so take it literally
79 warn "No branch matches prefix '$name'"
80 return 1
81 else
82 if [ $num_matches -eq 1 ]; then
83 echo "${matches#$prefix}"
84 return 0
85 else
86 # multiple matches, cannot decide
87 warn "Multiple branches match prefix '$name':"
88 for match in $matches; do
89 warn "- $match"
90 done
91 return 2
92 fi
93 fi
94}
95
96gitflow_current_branch() {
97 git branch | grep '^\* ' | grep -v 'no branch' | sed 's/^* //g'
98}
99
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100100gitflow_test_clean_working_tree() {
101 if ! git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then
102 return 1
103 elif ! git diff-index --cached --quiet --ignore-submodules HEAD --; then
104 return 2
105 else
106 return 0
107 fi
108}
109
110gitflow_require_clean_working_tree() {
111 gitflow_test_clean_working_tree
Vincent Driessen27592dd2010-02-06 14:45:39 +0100112 typeset -i result=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100113 if [ $result -eq 1 ]; then
114 die "fatal: Working tree contains unstaged changes. Aborting."
115 fi
116 if [ $result -eq 2 ]; then
117 die "fatal: Index contains uncommited changes. Aborting."
118 fi
119}
120
121gitflow_require_local_branch() {
122 if ! has $1 $LOCAL_BRANCHES; then
123 die "fatal: Local branch '$1' does not exist and is required."
124 fi
125}
126
127gitflow_require_remote_branch() {
128 if ! has $1 $REMOTE_BRANCHES; then
129 die "Remote branch '$1' does not exist and is required."
130 fi
131}
132
133gitflow_require_branch() {
134 if ! has $1 $ALL_BRANCHES; then
135 die "Branch '$1' does not exist and is required."
136 fi
137}
138
139gitflow_require_branch_absent() {
140 if has $1 $ALL_BRANCHES; then
141 die "Branch '$1' already exists. Pick another name."
142 fi
143}
144
Vincent Driessen1a2868b2010-02-07 23:23:16 +0100145gitflow_require_tag_absent() {
146 if has $1 $ALL_TAGS; then
147 die "Tag '$1' already exists. Pick another name."
148 fi
149}
150
Vincent Driessen5fa47582010-02-09 00:31:33 +0100151gitflow_tag_exists() {
152 has $1 $ALL_TAGS
153}
154
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100155#
156# gitflow_test_branches_equal()
157#
158# Tests whether branches and their "origin" counterparts have diverged and need
159# merging first. It returns error codes to provide more detail, like so:
160#
161# 0 Branch heads point to the same commit
162# 1 First given branch needs fast-forwarding
163# 2 Second given branch needs fast-forwarding
164# 3 Branch needs a real merge
165#
166gitflow_test_branches_equal() {
Vincent Driessen27592dd2010-02-06 14:45:39 +0100167 typeset commit1=$(git rev-parse "$1")
168 typeset commit2=$(git rev-parse "$2")
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100169 if [ "$commit1" != "$commit2" ]; then
Vincent Driessen27592dd2010-02-06 14:45:39 +0100170 typeset base=$(git merge-base "$commit1" "$commit2")
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100171 if [ "$commit1" = "$base" ]; then
172 return 1
173 elif [ "$commit2" = "$base" ]; then
174 return 2
175 else
176 return 3
177 fi
178 else
179 return 0
180 fi
181}
182
183gitflow_require_branches_equal() {
184 gitflow_require_local_branch "$1"
185 gitflow_require_remote_branch "$2"
186 gitflow_test_branches_equal "$1" "$2"
Vincent Driessen27592dd2010-02-06 14:45:39 +0100187 typeset -i status=$?
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100188 if [ $status -gt 0 ]; then
189 warn "Branches '$1' and '$2' have diverged."
190 if [ $status -eq 1 ]; then
191 die "And branch '$1' may be fast-forwarded."
192 elif [ $status -eq 2 ]; then
193 # Warn here, since there is no harm in being ahead
194 warn "And local branch '$1' is ahead of '$2'."
195 else
196 die "Branches need merging first."
197 fi
198 fi
199}
200
201#
202# gitflow_is_branch_merged_into()
203#
204# Checks whether branch $1 is succesfully merged into $2
205#
206gitflow_is_branch_merged_into() {
Vincent Driessen27592dd2010-02-06 14:45:39 +0100207 typeset subject=$1
208 typeset base=$2
209 typeset all_merges=$(git branch --contains $subject | sed 's/^[* ] //')
210 has $base $all_merges
Vincent Driessenc3607ac2010-02-05 19:53:45 +0100211}