4557d76ad4ab0771efd783113259aa5f6ef2d99e
[git-central.git] / server / functions
1 #!/bin/sh
2
3 # Sets: new_commits
4 # Assumes: $oldrev $newrev $refname
5 #
6 # This is for use in post receive hooks, as it assumes the refname has moved and
7 # is now newrev, we need to discard it. This is down with bash string replace,
8 # as it will replace only the first match, compared to the canonical "grep -v"
9 # approach which will throw out multiple matches if the same commit is referred
10 # to by multiple branches.
11 #
12 # Excellent, excellent docs from Andy Parkin's email script
13 #
14 ##################################################
15 #
16 # Consider this:
17 #   1 --- 2 --- O --- X --- 3 --- 4 --- N
18 #
19 # O is $oldrev for $refname
20 # N is $newrev for $refname
21 # X is a revision pointed to by some other ref, for which we may
22 #   assume that an email has already been generated.
23 # In this case we want to issue an email containing only revisions
24 # 3, 4, and N.  Given (almost) by
25 #
26 #  git rev-list N ^O --not --all
27 #
28 # The reason for the "almost", is that the "--not --all" will take
29 # precedence over the "N", and effectively will translate to
30 #
31 #  git rev-list N ^O ^X ^N
32 #
33 # So, we need to build up the list more carefully.  git rev-parse
34 # will generate a list of revs that may be fed into git rev-list.
35 # We can get it to make the "--not --all" part and then filter out
36 # the "^N" with:
37 #
38 #  git rev-parse --not --all | grep -v N
39 #
40 # Then, using the --stdin switch to git rev-list we have effectively
41 # manufactured
42 #
43 #  git rev-list N ^O ^X
44 #
45 # This leaves a problem when someone else updates the repository
46 # while this script is running.  Their new value of the ref we're
47 # working on would be included in the "--not --all" output; and as
48 # our $newrev would be an ancestor of that commit, it would exclude
49 # all of our commits.  What we really want is to exclude the current
50 # value of $refname from the --not list, rather than N itself.  So:
51 #
52 #  git rev-parse --not --all | grep -v $(git rev-parse $refname)
53 #
54 # Get's us to something pretty safe (apart from the small time
55 # between refname being read, and git rev-parse running - for that,
56 # I give up)
57 #
58 #
59 # Next problem, consider this:
60 #   * --- B --- * --- O ($oldrev)
61 #          \
62 #           * --- X --- * --- N ($newrev)
63 #
64 # That is to say, there is no guarantee that oldrev is a strict
65 # subset of newrev (it would have required a --force, but that's
66 # allowed).  So, we can't simply say rev-list $oldrev..$newrev.
67 # Instead we find the common base of the two revs and list from
68 # there.
69 #
70 # As above, we need to take into account the presence of X; if
71 # another branch is already in the repository and points at some of
72 # the revisions that we are about to output - we don't want them.
73 # The solution is as before: git rev-parse output filtered.
74 #
75 # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
76 #
77 # Tags pushed into the repository generate nice shortlog emails that
78 # summarise the commits between them and the previous tag.  However,
79 # those emails don't include the full commit messages that we output
80 # for a branch update.  Therefore we still want to output revisions
81 # that have been output on a tag email.
82 #
83 # Luckily, git rev-parse includes just the tool.  Instead of using
84 # "--all" we use "--branches"; this has the added benefit that
85 # "remotes/" will be ignored as well.
86 #
87 ##################################################
88 function set_new_commits() {
89         nl=$'\n'
90
91         # Get all the current branches, not'd as we want only new ones
92         new_commits=$(git rev-parse --not --branches)
93
94         # Strip off the not current branch
95         new_hash=$(git rev-parse $refname)
96         new_commits=${new_commits/^$new_hash/}
97
98         # Put back newrev without the not
99         new_commits=${new_commits}${nl}${newrev}
100
101         # Put in ^oldrev if it's not a new branch
102         if [ "$oldrev" != "0000000000000000000000000000000000000000" ] ; then
103                 new_commits=${new_commits}${nl}^${oldrev}
104         fi
105
106         new_commits=${new_commits/$nl$nl/$nl}
107         new_commits=${new_commits/#$nl/}
108 }
109
110 # Sets: $change_type
111 # Assumes: $oldrev $newrev
112 #
113 # --- Interpret
114 # 0000->1234 (create)
115 # 1234->2345 (update)
116 # 2345->0000 (delete)
117 function set_change_type() {
118         if [ "$oldrev" == "0000000000000000000000000000000000000000" ] ; then
119                 change_type="create"
120         else
121                 if [ "$newrev" == "0000000000000000000000000000000000000000" ] ; then
122                         change_type="delete"
123                 else
124                         change_type="update"
125                 fi
126         fi
127 }
128
129 # Sets: $newrev_type $oldrev_type $rev $rev_type
130 # Assumes: $newrev $oldrev
131 # --- Get the revision types
132 function set_rev_types() {
133         newrev_type=$(git cat-file -t "$newrev" 2> /dev/null)
134         oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
135         if [ "$newrev" == "0000000000000000000000000000000000000000" ] ; then
136                 rev_type="$oldrev_type"
137                 rev="$oldrev"
138         else
139                 rev_type="$newrev_type"
140                 rev="$newrev"
141         fi
142 }
143
144 # Sets: $describe
145 # Assumes: $rev
146 #
147 # The email subject will contain the best description of the ref that we can build from the parameters
148 function set_describe() {
149         rev_to_describe="$rev"
150         if [ "$1" != "" ] ; then
151                 rev_to_describe="$1"
152         fi
153
154         describe=$(git describe $rev_to_describe 2>/dev/null)
155         if [ -z "$describe" ]; then
156                 describe=$rev_to_describe
157         fi
158 }
159
160 # Sets: $describe_tags
161 # Assumes: $rev
162 #
163 # The email subject will contain the best description of the ref that we can build from the parameters
164 function set_describe_tags() {
165         rev_to_describe="$rev"
166         if [ "$1" != "" ] ; then
167                 rev_to_describe="$1"
168         fi
169
170         describe_tags=$(git describe --tags $rev_to_describe 2>/dev/null)
171         if [ -z "$describe_tags" ]; then
172                 describe_tags=$rev_to_describe
173         fi
174 }
175
176 # Takes a lockfile path and command to execute once the lock is acquired.
177 #
178 # Retries every second for 5 minutes.
179 #
180 # with_lock "foo.lock" "echo a"             # Works
181 # with_lock "foo.lock" "echo b1 ; echo b2"  # Work
182 # function several() {
183 # echo several1 ; echo several2
184 # }
185 # with_lock "foo.lock" several              # Works
186 #
187 function with_lock() {
188         lockfile="$1"
189         block="$2"
190         with_lock_has_lock=1
191
192         trap with_lock_unlock_if_held INT TERM EXIT
193         lockfile -1 -r 300 "$lockfile"
194         with_lock_has_lock=$?
195         if [ $with_lock_has_lock -ne 0 ] ; then
196                 exit $?
197         fi
198         eval "$block"
199         rm -f "$lockfile"
200         with_lock_has_lock=1
201 }
202
203 function with_lock_unlock_if_held() {
204         if [[ "$with_lock_has_lock" -eq 0 ]] ; then
205                 rm -f "$lockfile"
206         fi
207 }
208
209