Late commit of license change
[make-srpm.git] / make-srpm_git
blobbcf9f0ea79e35ad19c41f44af81db82d9c85cf1f
1 #! /bin/bash
2 # make-srpm: srpm builder, helps build srpm out of rcs sources
3 # git helper; export correct git release and patches ready to
4 # build src.rpm
6 # Copyright (C) 2008 Sam Liddicott <sam@liddicott.com>
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 git_toplevel() {
21 test -d .git
24 # git checkout $ORIGIN (or HEAD) as a tarfile $2 with path prefix $1
25 # but only checkout $SOURCE_PATHS if specified
26 git_archive() {
27 # by HEAD here we mean to select current checkout of current branch
28 git archive --format=tar --prefix="$1/" "${ORIGIN:-HEAD}" ${SOURCE_PATHS} |\
29 gzip > "$2"
32 # git checkout $ORIGIN (or HEAD) as directory $1
33 # but only checkout $SOURCE_PATHS if specified
34 git_export() {
35 exit 9
36 mkdir -p "$1" && \
37 git archive --format=tar "${ORIGIN:-HEAD}" ${SOURCE_PATHS} |\
38 ( if cd "$1"
39 then tar -xf -
40 fi )
43 git_linearize() {
44 awk -f /dev/stdin "$@" <<'#AWK'
45 #! /usr/bin/awk -f
47 # find-longest-path PARENT CHILD
48 # outputs the set of commits, choosing branches that result in the most number
49 # of commits (longest path) between PARENT and CHILD in that order
50 # Uses git rev-parse and git rev-list
52 # recursively find longest path from $child to $parent
53 # return answer in $path.
54 # There are no branches that dont lead to parent because of input filtering
55 function paths(rev, branches, branch, best_path, best_path_len, path, n) {
56 if (rev == _parent) return _parent;
58 if (rev in seen) return seen[rev];
60 split(nodes[rev], branches);
61 for(n=1; (n) in branches; n++) {
62 branch=branches[n];
63 path=paths(branch);
64 if (length(path) > best_path_len) {
65 best_path_len=length(path);
66 best_path=path;
70 # remember best answer for next time if we try this branch again as we
71 # may if branches re-merge
72 if (best_path_len) {
73 seen[rev]=best_path "\n" rev;
74 } else {
75 seen[rev]="";
77 return seen[rev];
81 BEGIN {
82 ("git rev-parse " ARGV[1] "^{commit}") | getline _parent;
83 ARGV[1]="";
85 ("git rev-parse " ARGV[2] "^{commit}") | getline _child;
86 ARGV[2]="";
88 # initialize parent to exist so we dont filter it out
89 nodes[_parent]="";
91 # read in a --topo-order --reverse tree but filter out anything whose
92 # parent is not already in (nodes) which will be primed by our top parent
93 while(("git rev-list --topo-order --reverse --parents ^" _parent " " _child) | getline) {
94 for(i=2; i<=NF; i++) {
95 if ($i in nodes) {
96 nodes[$1]=nodes[$1] " " $i;
100 printf("%s\n",paths(_child));
101 exit;
103 #AWK
107 # Takes a git url and returns the name of the repo probably using the same rules git clone does
108 # If URL ends in /.git then use parent folder name
109 # otherwise use basename with .git removed
110 git_basename() {
111 case "$1" in
112 */.git) basename `dirname $1` .git | sed -e "s/.*://";;
113 *) basename $1 .git;;
114 esac
117 # thanks to thiago and pasky (#git 19th August 2008) for this:
118 # output tags that are parent-ish to $1 (or HEAD).
119 # restrict to tags matching glob ORIGINAL_PATTERN if specified
120 git_parent_tags() {
121 # sed presumes that multiple tags are like (tag: ...) (tag: ...)
122 # git log --decorate --reverse --pretty=oneline "${1:-$RELEASE}" |\
123 # sed -ne 'There;:here;s/^(tag: refs\/tags\///;Tlook;{s/)/\n/;P;D};:look;s/\((tag: refs\/tags\/\)/\n\1/;D;'
125 # more reliable but slower
126 git tag -l ${ORIGIN_PATTERN:+"$ORIGIN_PATTERN"} | sort | while read tag
128 test "$(git merge-base "$tag" "${1:-HEAD}")" = "$(git rev-parse "$tag^{commit}")" && echo $tag;
129 done
132 # do a diff between two git revisions, also output the log of the commits included
133 # output to stdout, return true unless the patch was empty
134 # probably invoke as:
135 # git_rev_diff old_tag..new_tag -- file/path other/file/path
136 git_diff_rev() {
137 # patch v2.5.4 complains when it finds patches in the pre-amble even if they are indented!
138 # and some of the samba comments contain indented patches
139 git log --pretty=fuller "$@" | sed -e "s/^/|/"
140 echo
141 echo "Combined summary"
142 echo
143 # pgas on irc #bash isn't sure how he helped, but grep is great at passing
144 # text verbatim while showing in the exit code if the text was empty!
145 git diff -p --no-renames --stat --summary "$@" | git_fix_empty_files | check_empty_patch
148 # Patch requires some file content difference, but git also generates output
149 # when metadata changes. This function checks that the patch has material changes
150 function check_empty_patch() {
151 awk '
152 #! /usr/bin/awk -f
154 /^@@ / { frag++; }
156 { print; }
158 END {
159 if (frag) exit(0);
160 exit(1);
165 # Because unified diff's need at least some context, they can't create or delete
166 # empty files, so git_patch doesn't try to represent creation or deletion through
167 # unified diff's.
168 # git_fix_empty_files looks for such creation and deletion and manages it as a
169 # two stage process, making a 1 line first and then an empty file
170 git_fix_empty_files() {
171 set +x
172 awk '
173 #! /usr/bin/awk -f
175 function do_empty() {
176 if (diff!="" && deleted!="" && indx!="") {
177 printf("Make patch give the file 1 line\n");
178 printf("--- %s\n",diff);
179 printf("+++ %s\n",diff);
180 printf("@@ -0,0 +1 @@\n+\n");
181 printf("Make patch delete the 1 line file\n");
182 printf("--- %s\t\n",diff);
183 printf("+++ /dev/null\t1970-01-01 01:00:00.000000000\n");
184 printf("@@ -1 +0,0 @@\n-\n");
186 if (diff!="" && created!="" && indx!="") {
187 printf("Make patch create the file with 1 line\n");
188 printf("--- %s\n",diff);
189 printf("+++ %s\n",diff);
190 printf("@@ -0,0 +1 @@\n+\n");
191 printf("Make patch create delete the line but keep the file\n");
192 printf("--- %s\n",diff);
193 printf("+++ %s\n",diff);
194 printf("@@ -1 +0,0 @@\n-\n");
196 no_empty();
199 function no_empty() {
200 diff="";
201 deleted="";
202 created="";
203 indx="";
207 if (in_hunk > 0) in_hunk--;
210 /^diff --git / {
211 do_empty();
212 if (! in_hunk) diff=$3;
215 /^deleted file mode / {
216 if (! in_hunk && diff!="") deleted=$0;
219 /^new file mode / {
220 if (! in_hunk && diff!="") created=$0;
223 /^index / {
224 if (! in_hunk && diff!="") indx=$0;
227 /^--- / {
228 no_empty();
231 /^@@ / {
232 # read the hunk size
233 p=index($2,",");
234 if (p==0) c1=1;
235 else c1=strtonum(substr($2, p+1));
237 p=index($3,",");
238 if (p==0) in_hunk=1;
239 else in_hunk=strtonum(substr($3, p+1));
241 if (c1 > in_hunk) in_hunk=c1;
245 print;
248 END {
249 do_empty();
251 #AWK'
254 # like git format-patch except iterates a revision path between $1 and $2 and
255 # outputs diff's between all trees. Takes extra -- file/path arguments too,
256 # or in fact any argument accepted by git diff AND git log, but must appear
257 # after $1 and $2
258 git_format_diff() {
259 OPTIND=1
260 local OUTDIR="."
261 local SUFFIX=".patch"
262 while getopts "o:s:" OPT
264 case "$OPT" in
265 o) OUTDIR="$OPTARG";;
266 s) SUFFIX="$OPTARG";;
267 esac
268 done
269 shift $(($OPTIND - 1))
271 local sha
272 local PATCH_NO
273 local REV="`git rev-parse "$1^{commit}"`"
274 local END="`git rev-parse "$2^{commit}"`"
275 shift 2
277 PATCH_NO=${START_PATCH_NO:-0}
278 while read sha
280 printf -v _PATCH_NO "%04d" $PATCH_NO
281 if git_diff_rev "$REV".."$sha" "$@" > "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
282 then
283 echo "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
284 PATCH_NO=$(( PATCH_NO + 1 ))
285 else rm -f "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
287 REV="$sha"
288 done < <( git_linearize "$REV" "$END" )
291 # output:
292 # 1. a source tar,
293 # 2. and some patches,
294 # 3. and patches.list and patches.apply
295 # based on:
296 # ORIGIN which we will try and guess at using
297 # ORIGIN_PATTERN if we don't know ORIGIN and can't guess from RELEASE
298 # RELEASE which will default to THIS+
300 prepare_git_src() {
301 NAME="${1:-$Name}"
302 : ${TMPDIR:=$SRC_EXPORT}
304 case "$RELEASE" in
305 THIS|LOCAL|"") _RELEASE=HEAD; LOCAL=1;;
306 ?*) _RELEASE="$RELEASE"; LOCAL="";;
307 esac
308 RELEASE_COMMIT="$(git rev-parse "$_RELEASE^{commit}")"
310 # remember this and provide it during rpm config and build time
311 VERSION_INFO="`git show --pretty=format:"%h %ct %H %cd" $RELEASE_COMMIT 2>/dev/null | head -1`"
313 RELEASE_TAG="`git_parent_tags "$_RELEASE" | sort | tail -n 1 | grep '^' || echo HEAD`"
314 if [ -n "$VERSION_PATTERN" -a -z "$VERSION" ]
315 then
316 VERSION=`glob_match "$VERSION_PATTERN" "$RELEASE_TAG"`
318 VERSION="${2:-${VERSION:-$Version}}"
320 : ${ORIGIN:=$RELEASE_TAG}
321 ORIGIN_COMMIT="`git rev-parse $ORIGIN^{commit}`"
322 test -z "$ORIGIN" -o "$(git merge-base "$ORIGIN_COMMIT" "$RELEASE_COMMIT")" != "$ORIGIN_COMMIT" && \
323 die "Can't find origin $ORIGIN to use as pristine source for $_RELEASE"
325 # append date if we are including uncomitted files
326 if test -n "$LOCAL"
327 then
328 RELEASE_SUFFIX="${RELEASE_TAG//-/_}_`date +%Y%m%d`_g`git show --pretty=format:"%h" "$RELEASE_COMMIT" | head -n 1`_local"
329 else
330 RELEASE_SUFFIX=`git describe "$_RELEASE" 2>/dev/null | sed -e 's/^.*\///;s/-/_/g'`
331 RELEASE_SUFFIX="${RELEASE_SUFFIX#${RELEASE_TAG}_}"
332 if ! test -n "$RELEASE_SUFFIX"
333 then # git describe failed
334 RELEASE_SUFFIX="GIT_`git show --pretty=format:"%h" "$RELEASE_COMMIT" | head -n 1`"
338 # release tag is now part of release_suffix because of "git description" command
339 RELEASE_NAME="$NAME-$VERSION${RELEASE_SUFFIX:+_$RELEASE_SUFFIX}"
341 # if GIT_ORIGIN is like origin/blah then we can't use it as a filename part
342 ORIGIN_NAME="$NAME-${ORIGIN//\//-}"
344 TAR_PREFIX="$ORIGIN_NAME"
345 TAR_NAME="$TAR_PREFIX.tar.gz"
347 SPEC_VERSION="$VERSION"
348 git_archive "$ORIGIN_NAME" "$TMPDIR/$TAR_NAME"
350 # generate patches, also restrict patches to $SOURCE_PATHS
351 test -d "$TMPDIR" || die "Can't prepare srpm files in $TMPDIR"
353 # make patches
354 > "$TMPDIR/patches.list"
356 # if [ "$ORIGIN_COMMIT" != "$RELEASE_COMMIT" ]
357 # then
358 git_format_diff -o "$TMPDIR" -s "$Name-$VERSION.patch" \
359 $ORIGIN $_RELEASE ${SOURCE_PATHS:+-- $SOURCE_PATHS} |\
360 sed -e "s/^.*\///" -e "s/^\(0*\)\([0-9]*\)/Patch\2: \1\2/">> "$TMPDIR/patches.list"
361 # fi
363 # also uncomitted changes?
364 if test -n "$LOCAL"
365 then
366 # also local uncomitted patches
367 set `wc -l "$TMPDIR/patches.list"`
368 PATCH_NO="$(( $1 + ${START_PATCH_NO:-0} ))"
370 # any local files in the current change set
371 PATCH_NAME="git-local-cached.patch"
372 git diff --cached ${SOURCE_PATHS:+-- $SOURCE_PATHS} > "$TMPDIR/$PATCH_NAME"
373 if test -s "$TMPDIR/$PATCH_NAME"
374 then
375 PATCH_NO="`expr "${PATCH_NO:-0}" \+ 1`"
376 echo "Patch$PATCH_NO: $PATCH_NAME" >> "$TMPDIR/patches.list"
377 else
378 rm "$TMPDIR/$PATCH_NAME"
381 # any local files NOT in the current change set
382 PATCH_NAME="git-local.patch"
383 git diff ${SOURCE_PATHS:+-- $SOURCE_PATHS} > "$TMPDIR/$PATCH_NAME"
384 if test -s "$TMPDIR/$PATCH_NAME"
385 then
386 PATCH_NO="`expr "${PATCH_NO:-0}" \+ 1`"
387 echo "Patch$PATCH_NO: $PATCH_NAME" >> "$TMPDIR/patches.list"
388 else
389 rm "$TMPDIR/$PATCH_NAME"
393 # generate patches.apply
394 sed -e "s/^Patch\([0-9]*\):.*/%patch\1 -p$PATCH_LEVEL/" < "$TMPDIR/patches.list" > "$TMPDIR/patches.apply"
396 SPEC_RELEASE="$RELEASE_SUFFIX"