2 # make-srpm: srpm builder, helps build srpm out of rcs sources
3 # git helper; export correct git release and patches ready to
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/>.
24 # git checkout $ORIGIN (or HEAD) as a tarfile $2 with path prefix $1
25 # but only checkout $SOURCE_PATHS if specified
27 # by HEAD here we mean to select current checkout of current branch
28 git archive
--format=tar --prefix="$1/" "${ORIGIN:-HEAD}" ${SOURCE_PATHS} |\
32 # git checkout $ORIGIN (or HEAD) as directory $1
33 # but only checkout $SOURCE_PATHS if specified
37 git archive
--format=tar "${ORIGIN:-HEAD}" ${SOURCE_PATHS} |\
44 awk -f /dev
/stdin
"$@" <<'#AWK'
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
++) {
64 if (length
(path
) > best_path_len
) {
65 best_path_len
=length
(path
);
70 # remember best answer for next time if we try this branch again as we
71 # may if branches re-merge
73 seen
[rev]=best_path
"\n" rev;
82 ("git rev-parse " ARGV
[1] "^{commit}") | getline _parent
;
85 ("git rev-parse " ARGV
[2] "^{commit}") | getline _child
;
88 # initialize parent to exist so we dont filter it out
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
++) {
96 nodes
[$1]=nodes
[$1] " " $i;
100 printf("%s",paths
(_child
));
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
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;
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
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/^/|/"
131 echo "Combined summary"
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
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
() {
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");
172 function no_empty() {
180 if (in_hunk > 0) in_hunk--;
185 if (! in_hunk) diff=$3;
188 /^deleted file mode / {
189 if (! in_hunk && diff!="") deleted=$0;
193 if (! in_hunk && diff!="") created=$0;
197 if (! in_hunk && diff!="") indx=$0;
208 else c1=strtonum(substr($2, p+1));
212 else in_hunk=strtonum(substr($3, p+1));
214 if (c1 > in_hunk) in_hunk=c1;
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
234 local SUFFIX
=".patch"
235 while getopts "o:s:" OPT
238 o
) OUTDIR
="$OPTARG";;
239 s
) SUFFIX
="$OPTARG";;
242 shift $
(($OPTIND - 1))
246 local REV
="`git rev-parse "$1^
{commit
}"`"
247 local END
="`git rev-parse "$2^
{commit
}"`"
250 PATCH_NO
=${START_PATCH_NO:-0}
253 printf -v _PATCH_NO
"%04d" $PATCH_NO
254 if git_diff_rev
"$REV"..
"$sha" "$@" > "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
256 echo "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
257 PATCH_NO
=$
(( PATCH_NO
+ 1 ))
258 else rm -f "$OUTDIR/$_PATCH_NO-$sha.$SUFFIX"
261 done < <( git_linearize
"$REV" "$END" )
266 # 2. and some patches,
267 # 3. and patches.list and patches.apply
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+
276 VERSION
="${2:-${VERSION:-$Version}}"
277 : ${TMPDIR:=$SRC_EXPORT}
280 THIS|LOCAL|
"") _RELEASE
=HEAD
; LOCAL
=1;;
281 ?
*) _RELEASE
="$RELEASE"; LOCAL
="";;
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")"
297 RELEASE_SUFFIX
="GIT_`git show --pretty=format:"%h
" "$_RELEASE" | head -n 1`"
300 # append date if we are including uncomitted files
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"
322 > "$TMPDIR/patches.list"
324 # if [ "$ORIGIN_COMMIT" != "$RELEASE_COMMIT" ]
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"
331 # also uncomitted changes?
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"
343 PATCH_NO
="`expr "${PATCH_NO:-0}" \+ 1`"
344 echo "Patch$PATCH_NO: $PATCH_NAME" >> "$TMPDIR/patches.list"
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"
354 PATCH_NO
="`expr "${PATCH_NO:-0}" \+ 1`"
355 echo "Patch$PATCH_NO: $PATCH_NAME" >> "$TMPDIR/patches.list"
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"