I'm not ready to enforce that all tips must be new quite yet.
[git-central.git] / server / update-stable
1 #!/bin/sh
2
3 #
4 # This enforces stable/candidate/topic patterns.
5 #
6 # Specifically:
7 #
8 # * stable must be moved 1 merge commit at a time (see below)
9 # * candidates/topics are frozen once merged into stable
10 # * topics are frozen once merged into candidates
11 #
12 # For DAG aesthetics, we prefer stable only moving in the approved way,
13 # which is via empty (no change) merge commits. The rationale is that
14 # in the DAG we want a simple, one-commit move from each release to the
15 # next.
16 #
17 # We started out with:
18 #
19 # * -- A                stable
20 #  \    \
21 #   \    * -- * -- B    topic1
22 #    \           /
23 #     * -- * -- *       topic2
24 #
25 # And then publishing stable was a matter of fast-forwarding
26 # from A to B.
27 #
28 # In a complicated (non-rebased) DAG, this becomes hard to follow,
29 # so want we want instead is:
30 #
31 # * -- A ----------- C  stable
32 #  \    \           /
33 #   \    * -- * -- B    topic1
34 #    \           /
35 #     * -- * -- *       topic2
36 #
37 # Where commit C lists as it's first parent the prior stable
38 # commit and as it's second parent the release candidate. No
39 # other parents are allowed (e.g. no octopus merges here, which
40 # would insinuate qa didn't happen on the merged result).
41 #
42 # Also, we want to enforce that C does not actually introduce
43 # any diffs to the files between B and C--otherwise this changes
44 # would not have appeared in QA.
45 #
46
47 refname="$1"
48 oldrev="$2"
49 newrev="$3"
50
51 . $(dirname $0)/functions
52 set_change_type
53
54 case "$refname" in
55         refs/heads/*)
56                 short_refname=${refname##refs/heads/}
57                 ;;
58         *)
59                 exit 0
60                 ;;
61 esac
62
63 if [ "$change_type" == "delete" ] ; then
64         exit 0
65 fi
66
67 if [ "$change_type" == "create" -a "$short_refname" != "stable" ] ; then
68         not_on_stable=$(git rev-list stable..$newrev)
69         if [ "$not_on_stable" == "" ] ; then
70                 echo "----------------------------------------------------"
71                 echo
72                 echo "Creating a branch must include new commits"
73                 echo
74                 echo "----------------------------------------------------"
75                 exit 1
76         fi
77 fi
78
79 # create/delete is okay
80 if [ "$change_type" != "update" ] ; then
81         exit 0
82 fi
83
84 # # The tip must always be new
85 # already=$(git branch --contains "$newrev")
86 # if [ "$already" != "" ] ; then
87 #       already=${already##  }
88 #       echo "----------------------------------------------------"
89 #       echo
90 #       echo "$short_refname is already referred to by $already"
91 #       echo
92 #       echo "----------------------------------------------------"
93 #       exit 1
94 #fi
95
96 if [ "$short_refname" == "stable" ] ; then
97         # Stable enforcement
98
99         # read backwards:
100         # - all commits from old..new
101         # - unless they were already pointed to by a branch
102         # = all new commits on stable
103         count=$(git rev-parse --not --branches | git rev-list --stdin $oldrev..$newrev | wc -l)
104         if [ "$count" -ne "1" ] ; then
105                 echo "----------------------------------------------------"
106                 echo
107                 echo "Moving stable must entail a single commit"
108                 echo
109                 echo "----------------------------------------------------"
110                 exit 1
111         fi
112
113         number_of_parents=$(git rev-list --no-walk --parents $newrev | sed 's/ /\n/g' | grep -v $newrev | wc -l)
114         if [ "$number_of_parents" -ne "2" ] ; then
115                 echo "----------------------------------------------------"
116                 echo
117                 echo "Moving stable must entail a merge commit"
118                 echo
119                 echo "----------------------------------------------------"
120                 exit 1
121         fi
122
123         first_parent=$(git rev-list --no-walk --parents $newrev | sed 's/ /\n/g' | grep -v $newrev | head --lines=1)
124         if [ "$first_parent" != "$oldrev" ] ; then
125                 echo "----------------------------------------------------"
126                 echo
127                 echo "Moving stable must have the previous stable as the first parent"
128                 echo
129                 echo "----------------------------------------------------"
130                 exit 1
131         fi
132
133         second_parent=$(git rev-list --no-walk --parents $newrev | sed 's/ /\n/g' | grep -v $newrev | tail --lines=1)
134         changed_lines=$(git diff $second_parent..$newrev | wc -l)
135         if [ "$changed_lines" -ne "0" ] ; then
136                 echo "----------------------------------------------------"
137                 echo
138                 echo "Moving stable must not result in any changes from $second_parent"
139                 echo
140                 echo "----------------------------------------------------"
141                 exit 1
142         fi
143 else
144         # Check if candidate/topic is already in stable
145         git branch --contains "$oldrev" | grep stable >/dev/null
146         if [ $? -eq 0 ] ; then
147                 echo "----------------------------------------------------"
148                 echo
149                 echo "$short_refname has been merged into stable"
150                 echo
151                 echo "----------------------------------------------------"
152                 exit 1
153         fi
154
155         # For now candidates can mix amongst each other so early exit
156         case "$refname" in
157                 refs/heads/candidate*)
158                         exit 0
159                         ;;
160                 *)
161                         ;;
162         esac
163
164         # Check if topic is already in candidates
165         candidate=$(git branch --contains "$oldrev" | grep -oP "candidate.*" --max-count=1)
166         if [ $? -eq 0 ] ; then
167                 echo "----------------------------------------------------"
168                 echo
169                 echo "$short_refname has been merged into $candidate"
170                 echo
171                 echo "----------------------------------------------------"
172                 exit 1
173         fi
174 fi
175