#!/bin/bash # # (c) 2012 - 2016 PrydeWorX # Sven Eden, PrydeWorX - Bardowick, Germany # yamakuzure@users.sourceforge.net # # 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 3 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, see . # # History and ChangePWX_ERR_LOG: # Version Date Maintainer Change(s) # 0.0.1 2012-12-29 sed, PrydeWorX First Design. # 0.0.2 2013-01-08 sed, PrydeWorX First working version. # 0.1.0 2013-01-10 sed, PrydeWorX Initial private release. # 0.1.1 2013-06-01 sed, PrydeWorX Use functions from pwx_git_funcs.sh. # 0.2.0 2014-09-02 sed, PrydeWorX Use GNU enhanced getopt for command # line parsing. # 0.3.0 2016-11-18 sed, PrydeWorX Added option -T|--theirs to force # merge errors to be resolved by # throwing away all local changes. # 0.3.1 2017-03-22 sed, PrydeWorX Show full part of a patch that is to be # deleted after manually fixing merge # conflicts. # Show tried command if we fail with # full rejects. # 0.4.0 2017-04-24 sed, PrydeWorX Remove some remote-is-right-automatisms # 0.5.0 2017-07-04 sed, PrydeWorX If normal processing fails, use # check_tree.pl to generate diffs for the # specific commit, and apply them after # letting the user have a look. # Common functions PROGDIR="$(readlink -f $(dirname $0))" source ${PROGDIR}/pwx_git_funcs.sh # Version, please keep this current VERSION="0.5.0" # Global values to be filled in: PATCH_DIR="${PROGDIR}/patches" TAG_TO_USE="" # Editor to edit individual patches when needed PWX_EDIT=/usr/bin/kate # The options for the git am command: GIT_AM_OPTS="--committer-date-is-author-date" # Options and the corresponding help text OPT_SHORT=hi:T OPT_LONG=help,input:,theirs HELP_TEXT="Usage: $0 [OPTIONS] Take all commit patches from the input directory and apply them to the local tree. They are assumed to be from tag of some source tree. OPTIONS: -h|--help Show this help and exit. -i|--input : Path to where to patches are. The default is to read from the subdirectory 'patches' of the current directory. Notes: - When the script succeeds, it adds a line to the commit - file \"${PWX_COMMIT_FILE}\" of the form: -merged " # ========================================= # === Use getopt (GNU enhanced version) === # ========================================= # Check the version first, so we do not run into trouble getopt --test > /dev/null if [[ $? -ne 4 ]]; then echo "ERROR: getopt is not the GNU enhanced version." exit 1 fi # Store the output so we can check for errors. OPT_PARSED=$(getopt --options $OPT_SHORT --longoptions $OPT_LONG --name "$0" -- "$@") if [[ $? -ne 0 ]]; then # getopt has already complained about wrong arguments to stdout exit 2 fi # Use eval with "$OPT_PARSED" to properly handle the quoting eval set -- "$OPT_PARSED" # -------------------- # --- Handle input --- # -------------------- while true; do case "$1" in -h|--help) echo "$HELP_TEXT" exit 0 ;; -i|--input) PATCH_DIR="$2" shift 2 ;; --) shift break ;; *) echo "Something went mysteriously wrong." exit 3 ;; esac done # At this point we must have and left if [[ $# -ne 2 ]]; then echo "$HELP_TEXT" exit 4 fi # So these must be it: SOURCE_DIR="$1" TAG_TO_USE="$2" if [[ ! -d "$SOURCE_DIR" ]]; then echo "$SOURCE_DIR does not exist" echo echo "$HELP_TEXT" exit 5 fi # =========================================== # === The PATCH_DIR directory must exist. === # =========================================== if [[ -n "$PATCH_DIR" ]]; then if [[ ! -d "$PATCH_DIR" ]]; then echo "ERROR: $PATCH_DIR does not exist" exit 4 fi else echo "You have set the patch directory to be" echo "an empty string. Where should I find the" echo "patches, then?" exit 5 fi # ============================================================= # === We need two file lists. === # === A) The list of root files. === # === These have not been used to generate commit === # === patches and must be ignored by 'git am'. === # === B) The patches to work with. === # ============================================================= echo -n "Building file lists ..." # --- File list a) Root files declare -a root_files=( $(find ./ -mindepth 1 -maxdepth 1 -type f \ -not -name '*~' -and \ -not -name '*.diff' -and \ -not -name '*.orig' -and \ -not -name '*.bak' -printf "%f ") ) # --- File list b) Patch files # --- Here we might find patches that failed for single files. Those # --- must not clutter the list, they wouldn't apply anyway. declare -a patch_files=( $(find "$PATCH_DIR"/ -mindepth 1 -maxdepth 1 -type f \ -name '????-*.patch' -and -not -name '*-failed_patch_for-*' \ -printf "%f\n" | sort -n) ) echo " done" # --- Add an error log file to the list of temp files PWX_ERR_LOG="/tmp/pwx_git_applier_$$.log" add_temp "$PWX_ERR_LOG" touch $PWX_ERR_LOG || die "Unable to create $PWX_ERR_LOG" # =================================================== # === Build a basic exclude string to begin with. === # =================================================== basic_excludes="" for e in "${root_files[@]}" ; do if [[ "" = "$(echo -n "$basic_excludes" | \ grep -P "^\sexclude=$e")" ]]; then basic_excludes+=" --exclude=$e" fi done # ============================================ # === Main loop over the found patch files === # ============================================ for p in "${patch_files[@]}" ; do # For further processing the number and the full path # are needed. pnum=${p%%-*} psrc="${PATCH_DIR}/${p}" # We start with normal 3-way-patching GIT_USE_TWP="-3 " # ==================================================== # === Step 1) Reset the exclude list of root files === # ==================================================== excludes="$basic_excludes" # ============================================== # === Step 2) Start applying the patch as is === # ============================================== echo -n "Applying $p ..." git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc 1>/dev/null 2>$PWX_ERR_LOG res=$? echo " done [$res]" # =========================================================== # === Step 3) Look for reasons to not use 3-way patching === # === Symptom : "could not build fake ancestor" === # === Reason : No common root can be built === # === Resolution: Do not use "-3" option === # =========================================================== if [[ 0 -ne $res ]] && \ [[ $(grep -c "could not build fake ancestor" $PWX_ERR_LOG) -gt 0 ]]; then echo -n "Trying again without 3-way-patching ..." GIT_USE_TWP="" git am --abort 1>/dev/null 2>&1 git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc 1>/dev/null 2>$PWX_ERR_LOG res=$? echo " done [$res]" fi # ==================================================================== # === Step 4) Look for more files to exclude === # === Symptom : "error: : does not exist in index" === # === Reason : The file to patch isn't there === # === Resolution: Exclude the offending file(s) === # ==================================================================== if [[ 0 -ne $res ]] && \ [[ $(grep -c "does not exist in index" $PWX_ERR_LOG) -gt 0 ]]; then declare -a nff_files=( $( \ grep "does not exist in index" $PWX_ERR_LOG | \ cut -d ':' -f 2) ) for nff in "${nff_files[@]}" ; do echo "Excluding $nff ..." excludes+=" --exclude=$nff" # A special an evil case is a rename copy of something non-existent. # git am then needs *two* excludes, one for the (non-existing) # source and one for the still not existing target. nff_tgt="$(grep -A 1 "copy from $nff" $psrc | \ grep "copy to " | \ cut -d ' ' -f 3)" if [[ "x" != "x$nff_tgt" ]]; then echo "Excluding $nff_tgt ..." excludes+=" --exclude=$nff_tgt" fi done echo -n "Trying again without non-existing files ..." git am --abort 1>/dev/null 2>&1 git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc 1>/dev/null 2>$PWX_ERR_LOG res=$? echo " done [$res]" unset nff_files fi # ==================================================================== # === Step 5) If this still doesn't work, let check_tree.pl check === # === out the commit and build diffs for the touched files === # === against the tree. Then let the user have a chance to === # === decide what to apply before continuing. === # ==================================================================== if [[ 0 -ne $res ]] && \ [[ $(grep -c "patch does not apply" $PWX_ERR_LOG) -gt 0 ]]; then res=0 xCommit="$(head -n 1 $psrc | cut -d ' ' -f 2)" echo "git am failed to apply the patch automatically:" echo -e "\n--------" cat $PWX_ERR_LOG echo -e "--------\n" echo "Building patch diffs for commit $xCommit ..." # We need to know which files are relevant: xFiles="" xPatches="" for pF in $(grep "^diff \--git" $psrc | cut -d ' ' -f 3,4 | \ while read a b ; do \ echo -e "$a\n$b" | cut -d '/' -f 2- ; \ done | sort -u) ; do xFiles+="$pF " xPatches+="patches/${pF//\//_}.patch " done # If we have our lists, do it: if [[ "x" != "x$xFiles" ]] && [[ "x" != "x$xPatches" ]]; then ./check_tree.pl "$SOURCE_DIR" "$xCommit" $xFiles # Let's see which patches got built xResult="" for xP in $xPatches ; do echo -n "Checkin $xP : " if [[ -f "$xP" ]]; then echo "present" xResult+="$xP " else echo "missing" fi done # So, if no patches have been found, this whole thing # can be skipped. if [[ "x" = "x$xResult" ]]; then echo "No relevant patches found." echo -n "Skipping $psrc" git am --skip else # Okay, something to do found. echo "Please edit/delete the diffs as they are" echo "and then close $PWX_EDIT to continue" echo " ... to skip, just delete all patches" $PWX_EDIT $xResult 1>/dev/null 2>&1 # Apply patch-by-patch and add to git. have_patch=0 for xP in $xResult ; do while [[ -f "$xP" ]]; do have_patch=1 echo "Applying $xP ..." patch -p 1 -i $xP if [[ 0 -ne $? ]]; then echo "Something is wrong with $xP" $PWX_EDIT $xP 1>/dev/null 2>&1 else rm -f $xP fi done done if [[ 0 -eq $have_patch ]]; then echo "All patches deleted." git am --skip else git add man src git status echo "Patch: $psrc" echo -e -n "\nDoes this look in order to you? [Y/n]" read answer if [[ "xn" = "x$answer" ]]; then echo "Okay, then see what you can do" exit 0 fi echo -n "Finishing $psrc ..." git am --continue 1>/dev/null 2>$PWX_ERR_LOG res=$? echo " done [$res]" fi fi else echo "ERROR: Could not get file list from $psrc" echo echo "You have to do this by hand. Sorry." exit 1 fi fi # =========================================== # === Step 6) Exit if we couldn't help it === # =========================================== if [[ $res -ne 0 ]]; then echo -e "\n ==> $psrc can not be applied" echo " ==> The command that failed was:" echo "git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc" echo -e " ==> The Error message is:\n" cat $PWX_ERR_LOG echo -e "\nPlease try to apply the remaining parts by hand" echo "and then finish with 'git am --continue'" cleanup exit $res fi # ================================== # === Step 7) Clean up behind us === # ================================== # The patch is applied or irrelevant, remove it. rm -f $psrc # Remove merge debris find -iname '*.orig' | xargs rm -f # Remove temp files cleanup done