#!/bin/bash
#
# Try to rediff a given patch - unpack tarball, apply the patch with default fuzz and '--force'
# and diff old and new folders to a new patch.
#
# The patch should be provided as an argument; if no argument is specified and there is only one *patch file
# in the current folder, that patch file will be used.
#
# The working folder should contain a spec file from which we know how to apply the patch
#
# If patch fails to apply, the script leaves original and new folders, so you can analyze rejected hunks
#
# Author: Denis Silakov, ROSA, denis.silakov@rosalab.com
#

shopt -s nullglob

spec_count=$(ls -1 *spec 2>/dev/null | wc -l)
if [[ $spec_count != 1 ]]; then 
	echo "Can't detect spec file to work with - current folder either contains no spec files or contains several spec files."
	exit
fi

target_patch=$1
tarball=$2

if [[ $target_patch == '-h' || $target_patch == '--help' ]]; then
	echo "USAGE: $0 [patch_to_rediff] [source_tarball] "
	echo "		 * if patch is not specified and the current folder contains only one patch,"
	echo "		 then that patch will be rediffed"
	echo "		 * if tarball is not specified and the current folder contains only one tarball,"
	echo "		 then the patch will be applied to content of that tarball"
	echo "		 * all actions are performed in rediff_patch subfolder of the current folder"
	echo "		 if rediff fails, that subfolder will not be removed and you will be able to "
	echo "		 finalize rediff manually"

	exit 1
fi

# Remember number of patch files in current dir
patch_count=$(ls -1 *patch 2>/dev/null | wc -l)

if [[ "${target_patch}" == "" ]]; then
	# if no patch is provided as script argument and there is only a single patch in the folder,
	# let's proceed with that patch. Otherwise, exit
	if [[ $patch_count != 1 ]]; then 
		if [[ $patch_count == 0 ]]; then
			echo "No patch specified as an argument and current directory contains no patches"
		else
			echo "No patch specified as an argument and current directory contains more than one patch file"
		fi
		exit 1
	else
		target_patch=$(ls -1 *patch)
		echo "No patch specified as an argument, processing ${target_patch}"
	fi
fi

# Detect how the patch should be applied
grep -q '%apply_patches' *.spec
if [[ $? == 0 ]]; then
	# %apply_patches is used
	patch_cmd="patch -p1 --force"
elif [[ $patch_count == 1 ]]; then
	# only one patch and no %apply_patches, so we can expect the only %patch command in the spec
	patch_cmd=$(grep '^%patch' *spec | cut -f1,2 -d- | sed 's/^%patch[[:digit:]]*/patch --force /')
else
	# grep for Patch: record in spec corresponding to our patch
	# drop name from the beginning of the patch name,
	# since one can use %name or %{name} in patch name in spec
	project=$(basename "$PWD")
	patch_num=$(grep "^Patch.*${target_patch/$project/}" *spec | sed 's/^Patch\([[:digit:]]*\).*$/\1/')

	# now grep for necessary %patch command
	patch_cmd=$(grep "\%patch${patch_num}[[:space:]]\|\%patch${patch_num}\$" *spec | cut -f1,2 -d- | sed 's/^\%patch[[:digit:]]*/patch --force /')
fi

rm -rf rediff_patch
mkdir rediff_patch
pushd rediff_patch

# if we have relative path, add "../" to it
echo $tarball | grep -q "^/"
if [[ $? != 0 ]]; then
	tarball="../$tarball"
fi

# tarball is specified in cmd line, let's unpack it
if [[ $tarball =~ ".tar.gz" ]]; then 
	tar xzf $tarball
elif [[ $tarball =~ ".tgz" ]]; then 
	tar xzf $tarball
elif [[ $tarball =~ ".tar.Z" ]]; then 
	tar xZf $tarball
elif [[ $tarball =~ ".tar.bz2" ]]; then
	tar xjf $tarball
elif [[ $tarball =~ ".tbz2" ]]; then
	tar xjf $tarball
elif [[ $tarball =~ ".tar.xz" ]]; then 
	tar xJf $tarball
elif [[ $tarball =~ ".zip" ]]; then 
	unzip $tarball
elif [[ ! $tarball =~ "" ]]; then
	echo "Tarball type is not recognized, failed to unpack"
	popd
	rm -rf rediff_patch
	exit 1
fi

# Tarball is not specified - let's detect it
# We only support the situaiton with single tarball in the folder
if [[ -n $(echo ../*.tar.gz) ]]; then 
	tar xzf ../*tar.gz
elif [[ -n $(echo ../*.tgz) ]]; then 
	tar xzf ../*tgz
elif [[ -n $(echo ../*.tar.Z) ]]; then 
	tar xZf ../*tar.Z
elif [[ -n $(echo ../*.tar.bz2) ]]; then
	tar xjf ../*tar.bz2
elif [[ -n $(echo ../*.tbz2) ]]; then
	tar xjf ../*tbz2
elif [[ -n $(echo ../*.tar.xz) ]]; then 
	tar xJf ../*tar.xz
elif [[ -n $(echo ../*.zip) ]]; then 
	unzip ../*zip
fi

# Let's check how many folders exist in the current one after tarball unpacking
# We support only the situation when we have a single folder (except .git)
# which will be subjected to patching then
count=$(ls -l | grep -v .git | grep '^d' 2>/dev/null | wc -l)
if [[ $count != 1 ]]; then 
	if [[ $count == 0 ]]; then
		echo "No folders, something went wrong with unpacking of tarball"
	else
		echo "More than one folder found - it seems that we have a tarball bomb, this is not curently supported"
	fi
	popd
	exit 1
fi

# Create folder backup and try to apply the patch
folder=$(ls -l | grep '^d' | rev | cut -f1 -d\  | rev)
cp -r $folder ${folder}.orig
cp ../$target_patch $folder

cd $folder

failed=0
echo "$patch_cmd < $target_patch"
$patch_cmd < $target_patch
if [[ $? != 0 ]]; then
	failed=1
	echo "Rediff failed, you should rework the patch"
fi

cd ..


if [[ $failed == 0 ]]; then
	# Patch succeeded, let's recreate it
	rm -f $folder/$target_patch
	find $folder -name "*.orig" -delete
	diff -Naur ${folder}.orig $folder > ../${target_patch}.new

	# .. and replace patch command in spec
	# in case of %apply_patches, nothing to do
	grep -q '%apply_patches' ../*.spec
	if [[ $? != 0 ]]; then
		if [[ $patch_count == 1 ]]; then
			sed -i 's/^\%patch\(.*\) -p[[:digit:]]*/\%patch\1 -p1/' ../*spec
		else
			sed -i "s/^\%patch${patch_num} -p[[:digit:]]*/\%patch${patch_num} -p1/" ../*spec
		fi
	fi

	rm -rf ${folder}.orig $folder
else
	popd # out from rediff_patch folder
	exit 1
fi

popd # out from rediff_patch folder
rm -rf rediff_patch
