| #!/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 |
| } |