1#!/bin/sh
2
3# This is a git pre-push hook script.
4# It limits what you can push toward https://github.com/erlang/otp.git
5#
6# To activate, make a copy as .git/hooks/pre-push in your repo.
7
8# Called by "git push"
9# after it has checked the remote status, but before anything has been
10# pushed.  If this script exits with a non-zero status nothing will be pushed.
11#
12# This hook is called with the following parameters:
13#
14# $1 -- Name of the remote to which the push is being done
15# $2 -- URL to which the push is being done
16#
17# If pushing without using a named remote those arguments will be equal.
18#
19# Information about the commits which are being pushed is supplied as lines to
20# the standard input in the form:
21#
22#   <local ref> <local sha1> <remote ref> <remote sha1>
23#
24
25# Bump this version to give users an update notification.
26PRE_PUSH_SCRIPT_VERSION=2
27
28NEW_RELEASES="23 22 21 20 19 18 17"
29OLD_RELEASES="r16 r15 r14 r13"
30RELEASES="$NEW_RELEASES $OLD_RELEASES"
31
32# First commit on master, not allowed in other branches
33MASTER_ONLY=740b29ecc21c73a4bf4ebfc494490865d3c31978
34
35# Number of commits and files allowed in one push by this script
36NCOMMITS_MAX=100
37NFILES_MAX=100
38
39
40# Example testing this script for "git push upstream OTP-20.3.8.2":
41#
42#> null=0000000000000000000000000000000000000000
43#> echo "refs/tags/OTP-20.3.8.2 dummysha refs/tags/OTP-20.3.8.2 $null" | scripts/pre-push upstream https://github.com/erlang/otp.git
44
45# Example to test "git push upstream master"
46#
47#> local_sha=`git rev-parse master`
48#> remote_sha=`git rev-parse upstream/master`
49#> echo "refs/heads/master $local_sha refs/heads/master $remote_sha" | scripts/pre-push upstream https://github.com/erlang/otp.git
50
51
52remote="$1"
53url="$2"
54
55null=0000000000000000000000000000000000000000
56
57#echo "pre-push hook: remote=$remote"
58#echo "pre-push hook: url=$url"
59
60red_on() {
61    printf '%b' "\033[31m"
62}
63
64red_off() {
65    printf '%b' "\033[0m"
66}
67
68if [ "$url" = 'https://github.com/erlang/otp.git' -o "$url" = 'git@github.com:erlang/otp.git' -o "$url" = 'git@github.com:erlang/otp' ]
69then
70    if [ $remote = "$url" ]; then
71	red_on
72        echo "$0 says:"
73        echo "***"
74        echo "*** Push to $url without using a named remote is NOT ALLOWED!!!!"
75        echo "***"
76	red_off
77        exit 1
78    fi
79    IFS=' '
80    while read local_ref local_sha remote_ref remote_sha
81    do
82	#echo "pre-push hook:  local_ref=$local_ref"
83	#echo "pre-push hook: remote_ref=$remote_ref"
84	#echo "pre-push hook:  local_sha=$local_sha"
85	#echo "pre-push hook: remote_sha=$remote_sha"
86
87	if [ "$local_sha" = $null ]
88	then
89	    red_on
90            echo "$0 says:"
91            echo "***"
92            echo "*** DELETE push to '$remote' NOT ALLOWED!!!!!"
93            echo "***"
94	    red_off
95            exit 1
96	fi
97	if [ "$local_ref" != "$remote_ref" ]
98	then
99	    red_on
100	    echo "$0 says:"
101	    echo "***"
102	    echo "*** RENAME push: $local_ref pushed as $remote_ref to '$remote' NOT ALLOWED!!!!"
103	    echo "***"
104	    red_off
105	    exit 1
106	fi
107	case "$remote_ref" in
108            refs/heads/master | refs/heads/maint | refs/heads/maint-[0-9][0-9] | refs/heads/maint-r[0-9][0-9])
109		branch=${remote_ref#refs/heads/}
110                if [ "$remote_sha" = $null ]
111                then
112		    red_on
113                    echo "$0 says:"
114		    echo "***"
115		    echo "*** UNKNOWN BRANCH: '$branch' does not exist at '$remote'!!!!"
116		    echo "***"
117		    red_off
118		    exit 1
119                fi
120		if ! git log -1 --oneline $remote_sha > /dev/null 2>&1
121		then
122		    red_on
123		    echo "$0 says:"
124		    echo "***"
125		    echo "*** The top of '$branch' at '$remote' ($remote_sha)"
126		    echo "*** does not exist locally!!!"
127		    echo "*** You probably need to refresh local '$branch' and redo merge."
128		    echo "***"
129		    red_off
130		    exit 1
131		fi
132		if ! git merge-base --is-ancestor $remote_sha $local_sha
133		then
134		    red_on
135		    echo "$0 says:"
136		    echo "***"
137		    echo "*** FORCE push branch to '$remote' NOT ALLOWED!!!"
138		    echo "***"
139		    red_off
140		    exit 1
141		fi
142                if [ $remote_ref != refs/heads/master -a "$MASTER_ONLY" ] && git merge-base --is-ancestor $MASTER_ONLY $local_sha
143                then
144		    THIS_SCRIPT=`git rev-parse --git-path hooks/pre-push`
145		    THIS_SCRIPT=`realpath $THIS_SCRIPT`
146		    if git show refs/remotes/$remote/master:scripts/pre-push | diff -q --context=0 $THIS_SCRIPT - > /dev/null 2>&1
147		    then
148			red_on
149			echo "$0 says:"
150			echo "***"
151			echo "*** INVALID MERGE: Commit $MASTER_ONLY should not be reachable from '$branch'!!!!"
152			echo "***                You have probably merged master into '$branch' by mistake"
153			echo "***"
154			red_off
155			exit 1
156		    else
157			red_on
158			echo "$0 says:"
159			echo "***"
160			echo "*** The pre-push hook of this OTP repo needs updating."
161			echo "*** Do it by executing the following command:"
162			echo "***"
163			echo "***     git show refs/remotes/$remote/master:scripts/pre-push > $THIS_SCRIPT"
164			echo "***"
165			echo "*** And then retry the push."
166			echo "***"
167			red_off
168			exit 1
169		    fi
170                fi
171                if [ ${remote_ref#refs/heads/maint-} != $remote_ref ] && git merge-base --is-ancestor refs/remotes/$remote/maint $local_sha
172                then
173		    red_on
174		    echo "$0 says:"
175		    echo "***"
176		    echo "*** INVALID MERGE: Branch maint should not be reachable from '$branch'!!!!"
177                    echo "***                You have probably merged maint into '$branch' by mistake."
178		    echo "***"
179		    red_off
180		    exit 1
181                fi
182                if [ $remote_ref = refs/heads/maint -o $remote_ref = refs/heads/master ]; then
183                    for x in $RELEASES; do
184                        if ! git merge-base --is-ancestor refs/remotes/$remote/maint-$x $local_sha; then
185                            echo "$0 says:"
186		            echo "***"
187		            echo "*** WARNING: Branch '$remote/maint-$x' is not reachable from '$branch'!!!!"
188                            echo "***          Someone needs to merge 'maint-$x' forward and push."
189		            echo "***"
190                        fi
191                    done
192                fi
193                if [ $remote_ref = refs/heads/master ] && ! git merge-base --is-ancestor refs/remotes/$remote/maint $local_sha
194                then
195		    red_on
196                    echo "$0 says:"
197		    echo "***"
198		    echo "*** INVALID PUSH: Branch '$remote/maint' is not reachable from master!!!!"
199                    echo "***               Someone needs to merge maint forward to master and push."
200		    echo "***"
201		    red_off
202		    exit 1
203                fi
204		NCOMMITS=`git rev-list --count $remote_sha..$local_sha`
205		if [ $NCOMMITS -gt $NCOMMITS_MAX ]
206		then
207		    red_on
208		    echo "$0 says:"
209		    echo "***"
210		    echo "*** HUGE push: $NCOMMITS commits (> $NCOMMITS_MAX) to '$branch' at '$remote' NOT ALLOWED!!!!"
211		    echo "***"
212		    red_off
213		    exit 1
214		fi
215		NFILES=`git diff --name-only $remote_sha $local_sha | wc --lines`
216		if [ $NFILES -gt $NFILES_MAX ]
217		then
218		    red_on
219		    echo "$0 says:"
220		    echo "***"
221		    echo "*** HUGE push: $NFILES changed files (> $NFILES_MAX) to '$branch' at '$remote' NOT ALLOWED!!!!"
222		    echo "***"
223		    red_off
224		    exit 1
225		fi
226		;;
227	    refs/tags/OTP-*)
228		tag=${remote_ref#refs/tags/}
229		REL="UNKNOWN"
230		for x in $NEW_RELEASES; do
231		    if [ ${tag#OTP-$x.} != $tag ]
232		    then
233			REL=$x
234			break
235		    fi
236		done
237		if [ $REL = "UNKNOWN" ]
238		then
239		    red_on
240		    echo "$0 says:"
241		    echo "***"
242		    echo "*** Unknown OTP release number in tag '$tag'"
243		    echo "***"
244		    red_off
245		    exit 1
246		fi
247		if [ "$remote_sha" != $null ]
248		then
249		    red_on
250		    echo "$0 says:"
251		    echo "***"
252		    echo "*** FORCE push tag to '$remote' NOT ALLOWED!!!"
253		    echo "*** Tag '$tag' already exists at '$remote'."
254		    echo "***"
255		    red_off
256		    exit 1
257		fi
258		;;
259            refs/heads/*)
260		branch=${remote_ref#refs/heads/}
261		red_on
262		echo "$0 says:"
263		echo "***"
264		echo "*** UNKNOWN branch name: '$branch' pushed to '$remote' NOT ALLOWED!!!!"
265		echo "***"
266		red_off
267		exit 1
268		;;
269	    refs/tags/*)
270		tag=${remote_ref#refs/tags/}
271		red_on
272		echo "$0 says:"
273		echo "***"
274		echo "*** UNKNOWN tag name: '$tag' pushed to '$remote' NOT ALLOWED!!!!"
275		echo "***"
276		red_off
277		exit 1
278		;;
279	    *)
280		red_on
281		echo "$0 says:"
282		echo "***"
283		echo "*** STRANGE ref: '$remote_ref' pushed to '$remote' NOT ALLOWED!!!!"
284		echo "***"
285		red_off
286		exit 1
287		;;
288	esac
289    done
290
291    echo "$0: OK"
292
293    THIS_SCRIPT=`git rev-parse --git-path hooks/pre-push`
294    THIS_SCRIPT=`realpath $THIS_SCRIPT`
295    if git show refs/remotes/$remote/master:scripts/pre-push | diff --context=0 $THIS_SCRIPT - | grep -q PRE_PUSH_SCRIPT_VERSION > /dev/null 2>&1
296    then
297	echo ""
298	echo "NOTE: There is a newer version of the pre-push hook in this OTP repo."
299	echo "      You can install it by executing the following command:"
300	echo
301	echo "     git show refs/remotes/$remote/master:scripts/pre-push > $THIS_SCRIPT"
302	echo
303    fi
304else
305    echo "$0: No checks done for remote '$remote' at $url."
306fi
307
308exit 0
309
310