Last active
November 22, 2019 04:13
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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