Skip to content

Instantly share code, notes, and snippets.

@kskmori
Last active November 22, 2019 04:13
Show Gist options
  • Save kskmori/51a74c41f7a01b1f7964fec83841de0c to your computer and use it in GitHub Desktop.
Save kskmori/51a74c41f7a01b1f7964fec83841de0c to your computer and use it in GitHub Desktop.
a git extention to import patches from the RPM spec file to a git branch
#!/bin/sh
#
# git spec-import
# a git extention to import patches from the RPM spec file to a git branch
#
# Copyright(c) 2019 Keisuke MORI ([email protected])
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# sample .gitconfig:
#
#[spec-import "resource-agents.spec"]
# basecommitmacro = upstream_version
# excluderegex = ^Patch100[0-9]$
# dist = .el8
#[spec-import "pacemaker.spec"]
# basecommitmacro = commit
# excluderegex =
# dist = .el8
#[spec-import "fence-agents.spec"]
# basecommittagformat = v%{VERSION}
# excluderegex = ^Patch100[0-9]$
# dist = .el8
# # workaround for rpmspec failure
# rpmspecsed = /^Requires: %/d
#[spec-import "corosync.spec"]
# basecommittagformat = v%{VERSION}
# excluderegex =
# dist = .el8
#[spec-import "pcs.spec"]
# basecommittagformat = %{VERSION}
# excluderegex =
# dist = .el8
# # workaround for rpmspec failure
# rpmspecsed = /^Suggests: \\|^Requires: (/d
#
function git_apply_patch()
{
local pfile
pfile="$1"
pfilepath="${PATCHDIR}/${pfile}"
if grep '^From ' "${pfilepath}" >/dev/null 2>&1 ; then
# mbox format generated by git format-patch
git am "$pfilepath" || git_am_try_resolve || error_exit "ERROR: failed to apply $pfilepath"
else
# regular diff format
git apply --index "$pfilepath" || error_exit "ERROR: failed to apply $pfilepath"
git commit -m "$pfile" || error_exit "ERROR: failed to commit $pfile"
fi
}
function git_am_try_resolve()
{
rc=1
while [ $rc -ne 0 ]; do
patch -p1 < $(git rev-parse --git-dir)/rebase-apply/patch || return 1
git add -u .
git am --resolved
rc=$?
done
}
function merge_backport_branches()
{
git checkout $BASE_COMMIT || error_exit "ERROR: base commit not found: $BASE_COMMIT"
# TODO: when already exists
git checkout -b $BRANCH_BASE
git checkout -b $BRANCH_NAME
grep '^Patch[0-9]*:' $SPECFILE | while read pline; do
set -- $pline
pnum="$(echo $1 | tr -d ':')"
pfile="$2"
pbranch="${pnum}-${pfile}"
# skip excluded patches
expr match "$pnum" "$EXCLUDE_REGEX" >/dev/null && continue
git branch | grep "$pbranch" >/dev/null 2>&1;
rc=$?
if [ $rc -ne 0 ]; then
git checkout -b $pbranch
git_apply_patch $pfile
git checkout $BRANCH_NAME
fi
git merge $pbranch
done || error_exit "ERROR: merge failed"
}
function git_branch_delete()
{
git branch -D "$1" 2>&1 | grep -v "^error: branch .* not found."
}
function delete_backport_branches()
{
# try to checkout to the base of the branch, or to master if not determined
[ -z "$BASE_COMMIT" ] && BASE_COMMIT="$(git rev-parse $BRANCH_BASE)" || BASE_COMMIT=""
[ -z "$BASE_COMMIT" ] && BASE_COMMIT="master"
git checkout $BASE_COMMIT || error_exit "ERROR: base commit not found: $BASE_COMMIT"
git_branch_delete $BRANCH_BASE
git_branch_delete $BRANCH_NAME
grep '^Patch[0-9]*:' $SPECFILE | while read pline; do
set -- $pline
pnum="$(echo $1 | tr -d ':')"
pfile="$2"
pbranch="${pnum}-${pfile}"
git_branch_delete $pbranch
done
}
function error_exit()
{
echo "$@"
exit 1
}
function on_exit()
{
# clean up temporarly files if exists
[ -f "$TMPSPECFILE" ] && rm -f "$TMPSPECFILE"
}
function read_config()
{
local section base_commit_macro exclude_regex dist
local branch_name
section="spec-import.$(basename $SPECFILE)"
dist=$(git config --get "${section}.dist")
rpmspecopts=$(git config --get "${section}.rpmspecopts")
rpmspec_sed=$(git config --get "${section}.rpmspecsed")
base_commit_macro=$(git config --get "${section}.basecommitmacro")
base_commit_tagformat=$(git config --get "${section}.basecommittagformat")
exclude_regex=$(git config --get "${section}.excluderegex")
# use modified TMPSPECFILE if the spec file can not be parsed
# by rpmspec command
if [ -n "$rpmspec_sed" ]; then
TMPSPECFILE="${SPECFILE}.git-import"
sed -e "$rpmspec_sed" "$SPECFILE" > "$TMPSPECFILE"
SPECFILE="$TMPSPECFILE"
fi
# build rpmspec command line;
# - needs to be eval'ed to pass the quoted arguments properly
rpmspec="rpmspec -D '%_sourcedir $PATCHDIR'"
[ -n "$dist" ] && rpmspec="$rpmspec -D '%dist $dist'"
[ -n "$rpmspecopts" ] && rpmspec="$rpmspec $rpmspecopts"
branch_name="$(eval $rpmspec -q --queryformat='%{VERSION}-%{RELEASE}' --srpm ${SPECFILE})"
[ -n "$branch_name" ] && BRANCH_NAME="$branch_name"
[ -n "$base_commit_macro" ] && BASE_COMMIT=$(grep "^%global $base_commit_macro " ${SPECFILE} | awk '{print $3}')
[ -n "$base_commit_tagformat" ] && BASE_COMMIT="$(eval $rpmspec -q --queryformat='$base_commit_tagformat' --srpm ${SPECFILE})"
[ -n "$exclude_regex" ] && EXCLUDE_REGEX="$exclude_regex"
}
function dump_config()
{
echo "SPECFILE = $SPECFILE"
echo "BRANCH_NAME = $BRANCH_NAME"
echo "BRANCH_BASE = $BRANCH_BASE"
echo "BASE_COMMIT = $BASE_COMMIT"
echo "EXCLUDE_REGEX = $EXCLUDE_REGEX"
}
function usage()
{
echo "usage: $0 [-b branch_name] [-m] [-C] [-D] SPECFILE"
exit 0
}
### Main
## parse args
opt_branch=""
opt_check=0
opt_delete=0
opt_currentbase=0
while getopts "b:mCD" opt; do
case $opt in
b) opt_branch="$OPTARG";;
m) opt_currentbase=1;;
C) opt_check=1;;
D) opt_delete=1;;
*) usage;;
esac
done
shift $(($OPTIND - 1))
[ $# -ne 0 ] || usage
SPECFILE="$1"
PATCHDIR="$(dirname $SPECFILE)"
TMPSPECFILE=""
trap -- on_exit EXIT
## config variabes to customize for the spec file
BRANCH_NAME='spec-import'
BASE_COMMIT=""
EXCLUDE_REGEX=""
read_config
# override BRANCH_NAME if specified
[ -n "$opt_branch" ] && BRANCH_NAME="$opt_branch"
BRANCH_BASE="${BRANCH_NAME}-base"
# force to branch from the current commit if specified
[ $opt_currentbase -ne 0 ] && BASE_COMMIT=""
if [ $opt_check -ne 0 ]; then
dump_config
exit 0
fi
if [ $opt_delete -ne 0 ]; then
delete_backport_branches
exit 0
fi
if [ $opt_currentbase -eq 0 ] && [ -z "$BASE_COMMIT" ]; then
echo "ERROR: base commit can not be determined"
echo " use -m option to force to branch from the current commit,"
echo " or add 'basecommitmacro' or 'basecommittagformat' parameter to .gitconfig"
usage
fi
merge_backport_branches
echo
echo "Succeeded."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment