blob: 6684a14b1b4a00980c8afc0b2e61784c880a10d8 [file]
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#shellcheck disable=SC2034
INPUT_PATCH_FILE=""
#shellcheck disable=SC2034
INPUT_DIFF_FILE=""
#shellcheck disable=SC2034
INPUT_APPLIED_FILE=""
#shellcheck disable=SC2034
INPUT_APPLY_TYPE=""
PATCH_METHOD=""
PATCH_METHODS=("gitapply" "patchcmd")
PATCH_LEVEL=0
PATCH_HINT=""
PATCH_MODE="diff"
## @description Use curl to download the patch as a last resort
## @audience private
## @stability evolving
## @param patchloc
## @param output
## @return 0 got something
## @return 1 error
function generic_locate_patch
{
declare input=$1
declare output=$2
if [[ "${OFFLINE}" == true ]]; then
yetus_debug "generic_locate_patch: offline, skipping"
return 1
fi
if ! ${CURL} --silent -L \
--output "${output}" \
"${input}"; then
yetus_debug "generic_locate_patch: failed to download the patch."
return 1
fi
return 0
}
## @description Given a possible patch file, guess if it's a patch file
## @description only using the more intense verify if we really need to
## @audience private
## @stability evolving
## @param path to patch file to test
## @return 0 we think it's a patch file
## @return 1 we think it's not a patch file
function guess_patch_file
{
declare patch=$1
declare fileOutput
if [[ ! -f ${patch} ]]; then
return 1
fi
yetus_debug "Trying to guess if ${patch} is a patch file."
fileOutput=$("${FILE}" "${patch}")
if [[ $fileOutput =~ \ diff\ ]]; then
yetus_debug "file magic says it's a diff."
return 0
fi
fileOutput=$(head -n 1 "${patch}" | "${GREP}" -E "^(From [a-z0-9]* Mon Sep 17 00:00:00 2001)|(diff .*)|(Index: .*)$")
#shellcheck disable=SC2181
if [[ $? == 0 ]]; then
yetus_debug "first line looks like a patch file."
return 0
fi
patchfile_dryrun_driver "${patch}"
}
## @description Provide a hint on what tool should be used to process a patch file
## @description Sets PATCH_HINT to provide the hint. Will not do anything if
## @description PATCH_HINT or PATCH_METHOD is already set
## @audience private
## @stability evolving
## @replaceable no
## @param path to patch file to test
function patch_file_hinter
{
declare patch=$1
if [[ -z "${patch}" ]]; then
generate_stack
fi
if [[ -z "${PATCH_HINT}" ]] && [[ -z "${PATCH_METHOD}" ]]; then
if head -n 1 "${patch}" | "${GREP}" -q -E "^From [a-z0-9]* Mon Sep 17 00:00:00 2001" &&
"${GREP}" -q "^From: " "${patch}" &&
"${GREP}" -q "^Subject: \[PATCH" "${patch}" &&
"${GREP}" -q "^---" "${patch}"; then
PATCH_HINT="git"
return
fi
fi
}
## @description Given ${PATCH_OR_ISSUE}, determine what type of patch file is in use,
## @description and do the necessary work to place it into ${INPUT_PATCH_FILE}.
## @description If the system support diff files as well, put the diff version in
## @description ${INPUT_DIFF_FILE} so that any supported degraded modes work.
## @audience private
## @stability evolving
## @replaceable no
## @return 0 on success
## @return 1 on failure, may exit
function locate_patch
{
declare bugsys
declare patchfile=""
declare gotit=false
yetus_debug "locate patch"
if [[ -z "${PATCH_OR_ISSUE}" ]]; then
yetus_error "ERROR: No patch provided."
cleanup_and_exit 1
fi
INPUT_PATCH_FILE="${PATCH_DIR}/input.patch"
INPUT_DIFF_FILE="${PATCH_DIR}/input.diff"
echo "Processing: ${PATCH_OR_ISSUE}"
# it's a declarely provided file
if [[ -f ${PATCH_OR_ISSUE} ]]; then
patchfile="${PATCH_OR_ISSUE}"
PATCH_SYSTEM=generic
if [[ -f "${INPUT_PATCH_FILE}" ]]; then
if ! "${DIFF}" -q "${PATCH_OR_ISSUE}" "${INPUT_PATCH_FILE}" >/dev/null; then
rm "${INPUT_PATCH_FILE}"
fi
fi
else
# run through the bug systems. maybe they know?
for bugsys in "${BUGSYSTEMS[@]}"; do
if declare -f "${bugsys}_locate_patch" >/dev/null 2>&1; then
if "${bugsys}_locate_patch" \
"${PATCH_OR_ISSUE}" \
"${INPUT_PATCH_FILE}" \
"${INPUT_DIFF_FILE}"; then
if [[ -f "${INPUT_PATCH_FILE}" || -f "${INPUT_DIFF_FILE}" ]]; then
gotit=true
PATCH_SYSTEM=${bugsys}
else
# this situation can happen if the bugsystem forced a full build
# so not an error.
yetus_debug "Bugsystem ${bugsys} did not actually download a change."
fi
fi
fi
# did the bug system actually make us change our mind?
if [[ "${BUILDMODE}" == full ]]; then
return 0
fi
done
# ok, none of the bug systems know. let's see how smart we are
if [[ ${gotit} == false ]]; then
if ! generic_locate_patch "${PATCH_OR_ISSUE}" "${INPUT_PATCH_FILE}"; then
yetus_error "ERROR: Unsure how to process ${PATCH_OR_ISSUE}. Permissions missing?"
cleanup_and_exit 1
fi
PATCH_SYSTEM=generic
fi
fi
yetus_debug "Determined patch system to be ${PATCH_SYSTEM}"
if [[ ! -f "${INPUT_PATCH_FILE}"
&& -f "${patchfile}" ]]; then
if cp "${patchfile}" "${INPUT_PATCH_FILE}"; then
echo "Patch file ${patchfile} copied to ${PATCH_DIR}"
else
yetus_error "ERROR: Could not copy ${patchfile} to ${PATCH_DIR}"
cleanup_and_exit 1
fi
fi
}
## @description if patch-level zero, then verify we aren't
## @description just adding files
## @audience public
## @stability stable
## @param log filename
## @replaceable no
## @return $?
function patchfile_verify_zero
{
declare logfile=$1
shift
declare dir
declare changed_files1
declare changed_files2
declare filename
# don't return /dev/null
# see also similar code in change-analysis
# shellcheck disable=SC2016
changed_files1=$("${AWK}" 'function p(s){if(s!~"^/dev/null"&&s!~"^[[:blank:]]*$"){print s}}
/^diff --git / { p($3); p($4) }
/^(\+\+\+|---) / { p($2) }' "${INPUT_PATCH_FILE}" | sort -u)
# maybe we interpreted the patch wrong? check the log file
# shellcheck disable=SC2016
changed_files2=$("${GREP}" -E '^[cC]heck' "${logfile}" \
| "${AWK}" '{print $3}' \
| "${SED}" -e 's,\.\.\.$,,g')
for filename in ${changed_files1} ${changed_files2}; do
# leading prefix = bad
if [[ ${filename} =~ ^(a|b)/ ]]; then
return 1
fi
# touching an existing file is proof enough
# that pl=0 is good
if [[ -f ${filename} ]]; then
return 0
fi
dir=$(dirname "${filename}" 2>/dev/null)
if [[ -n ${dir} && -d ${dir} ]]; then
return 0
fi
done
# ¯\_(ツ)_/¯ - no way for us to know, all new files with no prefix!
yetus_error "WARNING: Patch only adds files; using patch level ${PATCH_LEVEL}"
return 0
}
## @description git apply dryrun
## @replaceable no
## @audience private
## @stability evolving
## @param path to patch file to dryrun
function gitapply_dryrun
{
declare patchfile=$1
declare prefixsize=${2:-0}
while [[ ${prefixsize} -lt 2
&& -z ${PATCH_METHOD} ]]; do
if yetus_run_and_redirect "${PATCH_DIR}/input-dryrun.log" \
"${GIT}" apply --binary -v --check "-p${prefixsize}" "${patchfile}"; then
PATCH_LEVEL=${prefixsize}
PATCH_METHOD=gitapply
break
fi
((prefixsize=prefixsize+1))
done
if [[ ${prefixsize} -eq 0 ]]; then
if ! patchfile_verify_zero "${PATCH_DIR}/input-dryrun.log"; then
PATCH_METHOD=""
PATCH_LEVEL=""
gitapply_dryrun "${patchfile}" 1
fi
fi
}
## @description patch patch dryrun
## @replaceable no
## @audience private
## @stability evolving
## @param path to patch file to dryrun
function patchcmd_dryrun
{
declare patchfile=$1
declare prefixsize=${2:-0}
while [[ ${prefixsize} -lt 2
&& -z ${PATCH_METHOD} ]]; do
# shellcheck disable=SC2153
if yetus_run_and_redirect "${PATCH_DIR}/input-dryrun.log" \
"${PATCH}" "-p${prefixsize}" -E --dry-run < "${patchfile}"; then
PATCH_LEVEL=${prefixsize}
PATCH_METHOD=patchcmd
break
fi
((prefixsize=prefixsize+1))
done
if [[ ${prefixsize} -eq 0 ]]; then
if ! patchfile_verify_zero "${PATCH_DIR}/input-dryrun.log"; then
PATCH_METHOD=""
PATCH_LEVEL=""
patchcmd_dryrun "${patchfile}" 1
fi
fi
}
## @description driver for dryrun methods
## @replaceable no
## @audience private
## @stability evolving
## @param path to patch file to dryrun
function patchfile_dryrun_driver
{
declare patchfile=$1
declare method
patch_file_hinter "${patchfile}"
#shellcheck disable=SC2153
for method in "${PATCH_METHODS[@]}"; do
if [[ -n "${PATCH_HINT}" ]] &&
[[ ! "${method}" =~ ${PATCH_HINT} ]]; then
continue
fi
if declare -f "${method}_dryrun" >/dev/null; then
"${method}_dryrun" "${patchfile}"
fi
if [[ -n ${PATCH_METHOD} ]]; then
break
fi
done
if [[ -n ${PATCH_METHOD} ]]; then
return 0
fi
return 1
}
## @description dryrun both PATCH and DIFF and determine which one to use.
## @description PATCH_MODE controls try-order: "diff" (default) avoids stale-file
## @description bugs from per-commit .patch stanzas (YETUS-983); "patch" restores
## @description the pre-YETUS-983 behavior for repos that need it.
## @replaceable no
## @audience private
## @stability evolving
function dryrun_both_files
{
declare first_file
declare first_type
declare second_file
declare second_type
if [[ "${PATCH_MODE}" == "patch" ]]; then
first_file="${INPUT_PATCH_FILE}"
first_type="patch"
second_file="${INPUT_DIFF_FILE}"
second_type="diff"
else
first_file="${INPUT_DIFF_FILE}"
first_type="diff"
second_file="${INPUT_PATCH_FILE}"
second_type="patch"
fi
if [[ -f "${first_file}" ]] && patchfile_dryrun_driver "${first_file}"; then
INPUT_APPLY_TYPE="${first_type}"
INPUT_APPLIED_FILE="${first_file}"
return 0
elif [[ -f "${second_file}" ]] && patchfile_dryrun_driver "${second_file}"; then
INPUT_APPLY_TYPE="${second_type}"
INPUT_APPLIED_FILE="${second_file}"
return 0
else
return 1
fi
}
## @description git patch apply
## @replaceable no
## @audience private
## @stability evolving
## @param path to patch file to apply
function gitapply_apply
{
declare patchfile=$1
declare extraopts
if [[ "${COMMITMODE}" = true ]]; then
extraopts="--whitespace=fix"
fi
echo "Applying the changes:"
yetus_run_and_redirect "${PATCH_DIR}/apply-patch-git-apply.log" \
"${GIT}" apply --binary ${extraopts} -v --stat --apply "-p${PATCH_LEVEL}" "${patchfile}"
${GREP} -v "^Checking" "${PATCH_DIR}/apply-patch-git-apply.log"
}
## @description patch patch apply
## @replaceable no
## @audience private
## @stability evolving
## @param path to patch file to apply
function patchcmd_apply
{
declare patchfile=$1
echo "Applying the patch:"
yetus_run_and_redirect "${PATCH_DIR}/apply-patch-patch-apply.log" \
"${PATCH}" "-p${PATCH_LEVEL}" -E < "${patchfile}"
cat "${PATCH_DIR}/apply-patch-patch-apply.log"
}
## @description driver for patch apply methods
## @replaceable no
## @audience private
## @stability evolving
## @param path to patch file to apply
function patchfile_apply_driver
{
declare patchfile=$1
declare gpg=$2
if declare -f "${PATCH_METHOD}_apply" >/dev/null; then
if ! "${PATCH_METHOD}_apply" "${patchfile}" "${gpg}"; then
return 1
fi
else
yetus_error "ERROR: Patching method ${PATCH_METHOD} does not have a way to apply patches!"
return 1
fi
return 0
}