add and remove empty files via patch/unified diff
[Samba/vfs_proxy.git] / packaging4 / utils / make-srpm_git
blob99d061d6b1ff335ca0f363ac11ea15e5ab2a6f6a
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",paths(_child));
101 exit;
103 #AWK
107 # thanks to thiago and pasky (#git 19th August 2008) for this:
108 # output tags that are parent-ish to $1 (or HEAD).
109 # restrict to tags matching glob ORIGINAL_PATTERN if specified
110 git_parent_tags() {
111 # sed presumes that multiple tags are like (tag: ...) (tag: ...)
112 # git log --decorate --reverse --pretty=oneline "${1:-$RELEASE}" |\
113 # sed -ne 'There;:here;s/^(tag: refs\/tags\///;Tlook;{s/)/\n/;P;D};:look;s/\((tag: refs\/tags\/\)/\n\1/;D;'
115 # more reliable but slower
116 git tag -l ${ORIGIN_PATTERN:+"$ORIGIN_PATTERN"} | sort | while read tag
118 test "$(git merge-base "$tag" "${1:-HEAD}")" = "$(git rev-parse "$tag^{commit}")" && echo $tag;
119 done
122 # do a diff between two git revisions, also output the log of the commits included
123 # output to stdout, return true unless the patch was empty
124 # probably invoke as:
125 # git_rev_diff old_tag..new_tag -- file/path other/file/path
126 git_diff_rev() {
127 # patch v2.5.4 complains when it finds patches in the pre-amble even if they are indented!
128 # and some of the samba comments contain indented patches
129 git log --pretty=fuller "$@" | sed -e "s/^/|/"
130 echo
131 echo "Combined summary"
132 echo
133 # pgas on irc #bash isn't sure how he helped, but grep is great at passing
134 # text verbatim while showing in the exit code if the text was empty!
135 git diff -p --stat --summary "$@" | git_fix_empty_files | grep '^'
138 # Because unified diff's need at least some context, they can't create or delete
139 # empty files, so git_patch doesn't try to represent creation or deletion through
140 # unified diff's.
141 # git_fix_empty_files looks for such creation and deletion and manages it as a
142 # two stage process, making a 1 line first and then an empty file
143 git_fix_empty_files() {
144 set +x
145 awk '
146 #! /usr/bin/awk -f
148 function do_empty() {
149 if (diff!="" && deleted!="" && indx!="") {
150 printf("Make patch give the file 1 line\n");
151 printf("--- %s\n",diff);
152 printf("+++ %s\n",diff);
153 printf("@@ -0,0 +1 @@\n+\n");
154 printf("Make patch delete the 1 line file\n");
155 printf("--- %s\t\n",diff);
156 printf("+++ /dev/null\t1970-01-01 01:00:00.000000000\n");
157 printf("@@ -1 +0,0 @@\n-\n");
159 if (diff!="" && created!="" && indx!="") {
160 printf("Make patch create the file with 1 line\n");
161 printf("--- %s\n",diff);
162 printf("+++ %s\n",diff);
163 printf("@@ -0,0 +1 @@\n+\n");
164 printf("Make patch create delete the line but keep the file\n");
165 printf("--- %s\n",diff);
166 printf("+++ %s\n",diff);
167 printf("@@ -1 +0,0 @@\n-\n");
169 no_empty();
172 function no_empty() {
173 diff="";
174 deleted="";
175 created="";
176 indx="";
180 if (in_hunk > 0) in_hunk--;
183 /^diff --git / {
184 do_empty();
185 if (! in_hunk) diff=$3;
188 /^deleted file mode / {
189 if (! in_hunk && diff!="") deleted=$0;
192 /^new file mode / {
193 if (! in_hunk && diff!="") created=$0;
196 /^index / {
197 if (! in_hunk && diff!="") indx=$0;
200 /^--- / {
201 no_empty();
204 /^@@ / {
205 # read the hunk size
206 p=index($2,",");
207 if (p==0) c1=1;
208 else c1=strtonum(substr($2, p+1));
210 p=index($3,",");
211 if (p==0) in_hunk=1;
212 else in_hunk=strtonum(substr($3, p+1));
214 if (c1 > in_hunk) in_hunk=c1;
218 print;
221 END {
222 do_empty();
224 #AWK'
227 # like git format-patch except iterates a revision path between $1 and $2 and
228 # outputs diff's between all trees. Takes extra -- file/path arguments too,
229 # or in fact any argument accepted by git diff AND git log, but must appear
230 # after $1 and $2
231 git_format_diff() {
232 OPTIND=1
233 local OUTDIR="."
234 local SUFFIX=".patch"
235 while getopts "o:s:" OPT
237 case "$OPT" in
238 o) OUTDIR="$OPTARG";;
239 s) SUFFIX="$OPTARG";;
240 esac
241 done
242 shift $(($OPTIND - 1))
244 local sha
245 local PATCH_NO
246 local REV="`git rev-parse "$1^{commit}"`"
247 local END="`git rev-parse "$2^{commit}"`"
248 shift 2
250 PATCH_NO=${START_PATCH_NO:-0}
251 while read sha
253 printf -v _PATCH_NO "%04d" $PATCH_NO
254 if git_diff_rev "$REV".."$sha" "$@" > "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
255 then
256 echo "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
257 PATCH_NO=$(( PATCH_NO + 1 ))
258 else rm -f "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
260 REV="$sha"
261 done < <( git_linearize "$REV" "$END" )
264 # output:
265 # 1. a source tar,
266 # 2. and some patches,
267 # 3. and patches.list and patches.apply
268 # based on:
269 # ORIGIN which we will try and guess at using
270 # ORIGIN_PATTERN if we don't know ORIGIN and can't guess from RELEASE
271 # RELEASE which will default to THIS+
273 prepare_git_src() {
274 PATCH_LEVEL=1
275 NAME="${1:-$Name}"
276 VERSION="${2:-${VERSION:-$Version}}"
277 : ${TMPDIR:=$SRC_EXPORT}
279 case "$RELEASE" in
280 THIS|LOCAL|"") _RELEASE=HEAD; LOCAL=1;;
281 ?*) _RELEASE="$RELEASE"; LOCAL="";;
282 esac
283 RELEASE_COMMIT="$(git rev-parse "$_RELEASE^{commit}")"
285 # remember this and provide it during rpm config and build time
286 VERSION_INFO="`git show --pretty=format:"%h %ct %H %cd" $_RELEASE 2>/dev/null | head -1`"
288 : ${ORIGIN:="`git_parent_tags "$_RELEASE" | sort | tail -n 1`"}
289 ORIGIN_COMMIT="`git rev-parse $ORIGIN^{commit}`"
290 test -z "$ORIGIN" -o "$(git merge-base "$ORIGIN_COMMIT" "$RELEASE_COMMIT")" != "$ORIGIN_COMMIT" && \
291 die "Can't find origin $ORIGIN to use as pristine source for $_RELEASE"
293 # try and find a tag for $_RELEASE. If RELEASE is not tagged, then append
294 # the git hash to the name of the previous tag
295 if test "$(git rev-parse "$RELEASE_COMMIT")" != "$(git rev-parse "$ORIGIN_COMMIT")"
296 then
297 RELEASE_SUFFIX="GIT_`git show --pretty=format:"%h" "$_RELEASE" | head -n 1`"
300 # append date if we are including uncomitted files
301 if test -n "$LOCAL"
302 then
303 RELEASE_SUFFIX="`date +%Y%m%d`_${RELEASE_SUFFIX:+${RELEASE_SUFFIX}_}local"
306 # remove a "release-" prefix from the name
307 RELEASE_NAME="$NAME-${ORIGIN/#release-}${RELEASE_SUFFIX:+_$RELEASE_SUFFIX}"
309 # if GIT_ORIGIN is like origin/blah then we can't use it as a filename part
310 ORIGIN_NAME="$NAME-${ORIGIN//\//-}"
312 TAR_PREFIX="$ORIGIN_NAME"
313 TAR_NAME="$TAR_PREFIX.tar.gz"
315 SPEC_VERSION="$VERSION"
316 git_archive "$ORIGIN_NAME" "$TMPDIR/$TAR_NAME"
318 # generate patches, also restrict patches to $SOURCE_PATHS
319 test -d "$TMPDIR" || die "Can't prepare srpm files in $TMPDIR"
321 # make patches
322 > "$TMPDIR/patches.list"
324 # if [ "$ORIGIN_COMMIT" != "$RELEASE_COMMIT" ]
325 # then
326 git_format_diff -o "$TMPDIR" -s "$Name-$VERSION.patch" \
327 $ORIGIN $_RELEASE ${SOURCE_PATHS:+-- $SOURCE_PATHS} |\
328 sed -e "s/^.*\///" -e "s/^\(0*\)\([0-9]*\)/Patch\2: \1\2/">> "$TMPDIR/patches.list"
329 # fi
331 # also uncomitted changes?
332 if test -n "$LOCAL"
333 then
334 # also local uncomitted patches
335 set `wc -l "$TMPDIR/patches.list"`
336 PATCH_NO="$(( $1 + ${START_PATCH_NO:-0} ))"
338 # any local files in the current change set
339 PATCH_NAME="git-local-cached.patch"
340 git diff --cached ${SOURCE_PATHS:+-- $SOURCE_PATHS} > "$TMPDIR/$PATCH_NAME"
341 if test -s "$TMPDIR/$PATCH_NAME"
342 then
343 PATCH_NO="`expr "${PATCH_NO:-0}" \+ 1`"
344 echo "Patch$PATCH_NO: $PATCH_NAME" >> "$TMPDIR/patches.list"
345 else
346 rm "$TMPDIR/$PATCH_NAME"
349 # any local files NOT in the current change set
350 PATCH_NAME="git-local.patch"
351 git diff ${SOURCE_PATHS:+-- $SOURCE_PATHS} > "$TMPDIR/$PATCH_NAME"
352 if test -s "$TMPDIR/$PATCH_NAME"
353 then
354 PATCH_NO="`expr "${PATCH_NO:-0}" \+ 1`"
355 echo "Patch$PATCH_NO: $PATCH_NAME" >> "$TMPDIR/patches.list"
356 else
357 rm "$TMPDIR/$PATCH_NAME"
361 # generate patches.apply
362 sed -e "s/^Patch\([0-9]*\):.*/%patch\1 -p$PATCH_LEVEL/" < "$TMPDIR/patches.list" > "$TMPDIR/patches.apply"
364 SPEC_RELEASE="$RELEASE_SUFFIX"