#! /bin/bash
#############################################################################
# Copyright (c) 2004-2006,2008-2010 Novell, Inc.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#############################################################################
# git commit wrapper, generates unified commit messages from patch headers
_libdir=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
log_entry() {
local entry=$1
echo "$entry" | fmt --width 65 | sed -e '1s/^/- /' -e '2,$s/^/ /'
}
patch_meta() {
local patch=$1
subject=$(formail -c -x Subject < "$patch" \
| sed -e 's, *\[[#/ A-Za-z0-9-]*\],,')
subject=${subject##:}
subject=${subject## }
subject=${subject%.}
# allow one blank line before the References: header
set -- $(awk '
/^References:/ { sub(/^References:/, ""); print; exit }
/^$/ { if (++blank > 1) exit }' "$patch")
references="$*"
case "$references" in
None | none)
references=
esac
}
patch_log_entry() {
local patch=$1 subject references old_subj old_ref old_patch="$tmpdir/old"
git show "HEAD:$patch" >"$old_patch" 2>/dev/null
patch_meta "$old_patch"
old_subj="$subject"
old_ref="$references"
patch_meta "$patch"
if test -z "$subject"; then
# should not happen
log_entry "$patch: ${references:+($references).}"
elif test "$subject" != "$old_subj"; then
log_entry "$subject${references:+ ($references)}."
elif test "$references" != "$old_ref"; then
# a new bugzilla reference suggest a non-trivial change
log_entry "Update $patch${references:+ ($references)}."
else
log_entry "Refresh $patch."
fi
}
# Print log entries for given added/modified files
log_entries()
{
local file configs_updated=false symvers_updated=false
for file in "$@"; do
case "$file" in
config/*)
if ! $configs_updated; then
log_entry "Update config files."
configs_updated=true
fi
;;
patches.*)
patch_log_entry "$file"
;;
kabi/*/symvers-* | kabi/*/symtypes-* | kabi/*/symsets-* )
if ! $symvers_updated; then
log_entry "Update kabi files."
symvers_updated=true
fi
;;
series.conf)
# don't log changes in there
;;
*)
log_entry "$file: "
;;
esac
done
}
do_commit()
{
local message edit=--edit
if test -z "${added[*]}${modified[*]}${deleted[*]}"; then
echo "No modified files" >&2
exit 1
fi
if test "$1" = "--no-edit"; then
edit=
fi
message=$tmpdir/message
echo >"$message"
log_entries "${added[@]}" "${modified[@]}" >>"$message"
for file in "${deleted[@]}"; do
log_entry "Delete $file." >>"$message"
done
# If there is only one entry, remove the "-" bullet to make the git log
# prettier. If there are multiple entries, we give up, because
# scripts/gitlog2changes wouldn't know where to insert the bullets again
if test $(grep -c '^- ' "$message") = 1; then
sed -i 's/^[- ] //' "$message"
fi
if test -n "$edit" -a -e "$tmpdir/notice"; then
cat "$_" >>"$message"
fi
git commit "$@" -F $message $edit
}
# Check if series.conf contains only patch additions and print the
# added patches in order of appearance.
check_series_diff()
{
awk '
/^\+\+\+ / {
body = 1
next
}
!body || # ignore the patch header
/^[@ ]/ || # ignore line numbers and context
/^[-+][[:blank:]]*(#.*)?$/ { # ignore whitespace and comments
next
}
# added unguarded patches are OK
/^\+[[:blank:]]*patches\.[^[:blank:]]*\// {
print $2
next
}
# anything else is a problem
#{ print >"/dev/stderr" }
{ exit 1 }
'
}
# Check whether we are only adding new patches and possibly updating
# configs. At least one patch must be added.
only_patches()
{
local file have_patches=false
for file in "${modified[@]}"; do
case "$file" in
series.conf | config/* | supported.conf | blacklist.conf | kabi/severities)
;;
*)
return 1
esac
done
if test -n "${deleted[*]}"; then
return 1
fi
for file in "${added[@]}"; do
case "$file" in
patches.*/*)
have_patches=true
;;
*)
return 1
esac
done
if ! $have_patches; then
return 1
fi
git diff HEAD -- series.conf | check_series_diff >/dev/null || return
return 0
}
trim() {
local var="$*"
# remove leading whitespace characters
var="${var#"${var%%[![:space:]]*}"}"
# remove trailing whitespace characters
var="${var%"${var##*[![:space:]]}"}"
echo -n "$var"
}
# Insert part of the content added to a new series file back into the old
# series file
# splice_series <patch name> 3<old_series 4<new_series
splice_series()
{
local patch=$1
local old new old_eof new_eof
local state="matching"
IFS=
while true; do
if [ $state = "matching" ]; then
read -r -u 3 old || old_eof=1
read -r -u 4 new || new_eof=1
if [ -z "$old_eof" -a "$new_eof" ]; then
echo "Error: new series does not contain all" \
"lines from old series." > /dev/stderr
return 1
fi
echo "$new"
if [ "$old_eof" -a "$new_eof" ]; then
break
elif [ "$old_eof" -o "$new" != "$old" ]; then
if [ "$(trim "$new")" = "$patch" ]; then
state="just after patch"
else
state="diff"
fi
fi
elif [ $state = "diff" ]; then
read -r -u 4 new || new_eof=1
if [ "$new_eof" ]; then
echo "Error: new series does not contain all" \
"lines from old series." > /dev/stderr
return 1
fi
echo "$new"
if [ ! "$old_eof" -a "$new" = "$old" ]; then
state="matching"
elif [ "$(trim "$new")" = "$patch" ]; then
state="just after patch"
fi
elif [ $state = "just after patch" ]; then
read -r -u 4 new || new_eof=1
if [ "$new_eof" ]; then
break
elif [ ! "$old_eof" -a "$new" = "$old" ]; then
echo "$old"
state="after patch"
elif [ -z "$(trim "$new")" ]; then
echo "$new"
state="whitespace seen"
fi
elif [ $state = "whitespace seen" ]; then
read -r -u 4 new || new_eof=1
if [ "$new_eof" ]; then
break
elif [ ! "$old_eof" -a "$new" = "$old" ]; then
echo "$old"
state="after patch"
elif [ -z "$(trim "$new")" ]; then
echo "$new"
else
state="after whitespace"
fi
elif [ $state = "after whitespace" ]; then
read -r -u 4 new || new_eof=1
if [ "$new_eof" ]; then
break
elif [ ! "$old_eof" -a "$new" = "$old" ]; then
echo "$old"
state="after patch"
fi
elif [ $state = "after patch" ]; then
#echo "@@ state $state old <$old> eof <$old_eof> new <$new> eof <$new_eof>" > /dev/stderr
cat <&3
break
fi
done
if [ $state = "matching" ]; then
echo "Error: patch \"$patch\" not found in series." > /dev/stderr
return 1
fi
}
# Commit patches one by one for better bisectability
commit_single_patches()
{
local saved_index=$(git write-tree) patch series
local file added=() modified_aux=() deleted=()
git cat-file blob HEAD:series.conf > "$tmpdir"/old_series
cp series.conf "$tmpdir"/new_series
for file in "${modified[@]}"; do
case "$file" in
config/* | supported.conf | blacklist.conf | kabi/severities)
modified_aux=("${modified_aux[@]}" "$file")
esac
done
local modified=()
# reset the index
git read-tree HEAD
set -- $(git diff HEAD -- series.conf | check_series_diff)
if test $# -gt 1; then
# Fix the author date so that scripts/gitlog2changes can group
# the commits into a single rpm changelog entry
export GIT_AUTHOR_DATE=$(date -R)
log_entries "$@" "${modified_aux[@]}"
read -p 'Commit with these changelog messages? [Yn] '
case "$REPLY" in
"" | [Yy] | [Yy][Ee][Ss])
no_edit=--no-edit
esac
fi
# Commit patches except the last one
while test $# -gt 1; do
patch=$1
shift
# add a series.conf with a single new patch to the index
series=$(splice_series "$patch" \
3<"$tmpdir"/old_series 4<"$tmpdir"/new_series | \
git hash-object -w --stdin)
git read-tree $(git ls-tree HEAD | \
sed -r "s/(.*)\\<[0-9a-f]{40}\\>(.*\\<series\.conf)$/\1$series\2/" \
| git mktree)
git add "$patch"
added=("$patch")
cat >"$tmpdir/notice" <<EOF
# Patches are being committed one by one for better bisectability.
# There are $# more patches to commit.
EOF
if ! do_commit $no_edit --no-verify; then
# restore the index so that the user does not need to git add
# the patches again
git read-tree "$saved_index"
return 1
fi
done
rm -f "$tmpdir/notice"
# commit the last patch and possible config update together
patch=$1
git add "$patch"
added=("$patch")
modified=("${modified_aux[@]}")
if test -n "${modified[*]}"; then
no_edit=
fi
if ! do_commit $no_edit --no-verify -a; then
git read-tree "$saved_index"
return 1
fi
}
# do not run "main" code if script is being sourced rather than executed
[[ $0 != "$BASH_SOURCE" ]] && return
. "$_libdir"/wd-functions.sh
if ! $using_git; then
echo "ERROR: not in a git working directory."
exit 1
fi
"$_libdir"/check-cvs-add || exit 1
while [ -n "$1" ] ; do
case "$1" in
--no-edit) no_edit=--no-edit
;;
--amend) amend=yes
;;
*) echo Unknown argument "$1" >&2
;;
esac
shift
done
if [ -n "$amend" ]; then
if [ $(git rev-list HEAD^.. | wc -l) -gt 1 ]; then
echo "ERROR: cannot use --amend option on the merge commit HEAD."
exit 1
fi
git reset --soft HEAD^
fi
trap 'rm -rf "$tmpdir"' EXIT
tmpdir=$(mktemp -d /tmp/${0##*/}.XXXXXX)
if test -e "$(git rev-parse --git-dir)/MERGE_HEAD"; then
# Do not try to fabricate a commit message for merge commits, git itself
# does it better
git commit -a
exit
fi
added=($(git diff --name-only --diff-filter=A HEAD))
modified=($(git diff --name-only --diff-filter=MT HEAD))
deleted=($(git diff --name-only --diff-filter=D HEAD))
scripts/check-patch-dirs "${added[@]}" "${modified[@]}" || exit 1
if only_patches; then
git add -u
"$_libdir"/git-pre-commit || exit
commit_single_patches || exit
else
# FIXME: -a should not be the default
do_commit -a || exit
fi
branch=$(get_branch_name)
case "$branch" in
master | stable | vanilla | linux-next | openSUSE-??.? | \
SLE?? | SLE*-ARM| SLE??-SP? | SLE*-RT | SLE*-TD | SLE*-LTSS | \
cve/linux-* | packaging | scripts)
remote=$(get_git_remote "$branch")
user=$(get_git_user "$remote")
echo "after testing your changes, run"
echo " git push $remote HEAD:users/$user/$branch/for-next"
esac
# vim: et:sts=4:sw=4