#! /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 3 /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}\\>(.*\\"$tmpdir/notice" <&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