diff mbox series

libxfs-apply: allow stgit users to force-apply a patch

Message ID 20250220164933.GP21808@frogsfrogsfrogs (mailing list archive)
State New
Headers show
Series libxfs-apply: allow stgit users to force-apply a patch | expand

Commit Message

Darrick J. Wong Feb. 20, 2025, 4:49 p.m. UTC
From: Darrick J. Wong <djwong@kernel.org>

Currently, libxfs-apply handles merge conflicts in the auto-backported
patches in a somewhat unfriendly way -- either it applies completely
cleanly, or the user has to ^Z, find the raw diff file in /tmp, apply it
by hand, resume the process, and then tell it to skip the patch.

This is annoying, and I've long worked around that by using my handy
stg-force-import script that imports the patch with --reject, undoes the
partially-complete diff, uses patch(1) to import as much of the diff as
possible, and then starts an editor so the caller can clean up the rest.

When patches are fuzzy, patch(1) is /much/ less strict about applying
changes than stg-import.  Since Carlos sent in his own workaround for
guilt, I figured I might as well port stg-force-import into libxfs-apply
and contribute that.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
per maintainer request
---
 tools/libxfs-apply |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 62 insertions(+), 2 deletions(-)

Comments

Andrey Albershteyn Feb. 20, 2025, 5:58 p.m. UTC | #1
On 2025-02-20 08:49:33, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Currently, libxfs-apply handles merge conflicts in the auto-backported
> patches in a somewhat unfriendly way -- either it applies completely
> cleanly, or the user has to ^Z, find the raw diff file in /tmp, apply it
> by hand, resume the process, and then tell it to skip the patch.
> 
> This is annoying, and I've long worked around that by using my handy
> stg-force-import script that imports the patch with --reject, undoes the
> partially-complete diff, uses patch(1) to import as much of the diff as
> possible, and then starts an editor so the caller can clean up the rest.
> 
> When patches are fuzzy, patch(1) is /much/ less strict about applying
> changes than stg-import.  Since Carlos sent in his own workaround for
> guilt, I figured I might as well port stg-force-import into libxfs-apply
> and contribute that.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
> per maintainer request
> ---
>  tools/libxfs-apply |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 62 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/libxfs-apply b/tools/libxfs-apply
> index 097a695f942bb8..9fb31f74d5c9af 100755
> --- a/tools/libxfs-apply
> +++ b/tools/libxfs-apply
> @@ -297,6 +297,64 @@ fixup_header_format()
>  
>  }
>  
> +editor() {
> +	if [ -n "${EDITOR}" ]; then
> +		${EDITOR} "$@"
> +	elif [ -n "${VISUAL}" ]; then
> +		${VISUAL} "$@"
> +	elif command -v editor &>/dev/null; then
> +		editor "$@"
> +	elif command -v nano &>/dev/null; then
> +		nano "$@"
> +	else
> +		echo "No editor available, aborting messily."
> +		exit 1
> +	fi
> +}
> +
> +stg_force_import()
> +{
> +	local patch_name="$1"
> +	local patch="$2"
> +
> +	# Import patch to get the metadata even though the diff application
> +	# might fail due to stg import being very picky.  If the patch applies
> +	# cleanly, we're done.
> +	stg import --reject -n "${patch_name}" "${patch}" && return 0
> +
> +	local tmpfile="${patch}.stgit"
> +	rm -f "${tmpfile}"
> +
> +	# Erase whatever stgit managed to apply, then use patch(1)'s more
> +	# flexible heuristics.  Capture the output for later use.
> +	stg diff | patch -p1 -R
> +	patch -p1 < "${patch}" &> "${tmpfile}"
> +	cat "${tmpfile}"
> +
> +	# Attach any new files created by the patch
> +	grep 'create mode' "${patch}" | sed -e 's/^.* mode [0-7]* //g' | while read -r f; do
> +		git add "$f"
> +	done
> +
> +	# Remove any existing files deleted by the patch
> +	grep 'delete mode' "${patch}" | sed -e 's/^.* mode [0-7]* //g' | while read -r f; do
> +		git rm "$f"
> +	done
> +
> +	# Open an editor so the user can clean up the rejects.  Use readarray
> +	# instead of "<<<" because the latter picks up empty output as a single
> +	# line and does variable expansion...  stupid bash.
> +	readarray -t rej_files < <(grep 'saving rejects to' "${tmpfile}" | \
> +				   sed -e 's/^.*saving rejects to file //g')
> +	rm -f "${tmpfile}"
> +	if [ "${#rej_files[@]}" -gt 0 ]; then
> +		echo "Opening editor to deal with rejects.  Changes commit when you close the editor."
> +		editor "${rej_files[@]}"
> +	fi
> +
> +	stg refresh
> +}
> +
>  apply_patch()
>  {
>  	local _patch=$1
> @@ -385,11 +443,13 @@ apply_patch()
>  		stg import -n $_patch_name $_new_patch.2
>  		if [ $? -ne 0 ]; then
>  			echo "stgit push failed!"
> -			read -r -p "Skip or Fail [s|F]? " response
> -			if [ -z "$response" -o "$response" != "s" ]; then
> +			read -r -p "Skip, force Apply, or Fail [s|a|F]? " response
> +			if [ -z "$response" -o "$response" = "F" -o "$response" = "f" ]; then
>  				echo "Force push patch, fix and refresh."
>  				echo "Restart from commit $_current_commit"
>  				fail "Manual cleanup required!"
> +			elif [ "$response" = "a" -o "$response" = "A" ]; then
> +				stg_force_import "$_patch_name" "$_new_patch.2"
>  			else
>  				echo "Skipping. Manual series file cleanup needed!"
>  			fi
> 

Thanks! Works great, lgtm
Reviewed-by: Andrey Albershteyn <aalbersh@kernel.org>

Sending fix for stgit detection in reply to this patch
diff mbox series

Patch

diff --git a/tools/libxfs-apply b/tools/libxfs-apply
index 097a695f942bb8..9fb31f74d5c9af 100755
--- a/tools/libxfs-apply
+++ b/tools/libxfs-apply
@@ -297,6 +297,64 @@  fixup_header_format()
 
 }
 
+editor() {
+	if [ -n "${EDITOR}" ]; then
+		${EDITOR} "$@"
+	elif [ -n "${VISUAL}" ]; then
+		${VISUAL} "$@"
+	elif command -v editor &>/dev/null; then
+		editor "$@"
+	elif command -v nano &>/dev/null; then
+		nano "$@"
+	else
+		echo "No editor available, aborting messily."
+		exit 1
+	fi
+}
+
+stg_force_import()
+{
+	local patch_name="$1"
+	local patch="$2"
+
+	# Import patch to get the metadata even though the diff application
+	# might fail due to stg import being very picky.  If the patch applies
+	# cleanly, we're done.
+	stg import --reject -n "${patch_name}" "${patch}" && return 0
+
+	local tmpfile="${patch}.stgit"
+	rm -f "${tmpfile}"
+
+	# Erase whatever stgit managed to apply, then use patch(1)'s more
+	# flexible heuristics.  Capture the output for later use.
+	stg diff | patch -p1 -R
+	patch -p1 < "${patch}" &> "${tmpfile}"
+	cat "${tmpfile}"
+
+	# Attach any new files created by the patch
+	grep 'create mode' "${patch}" | sed -e 's/^.* mode [0-7]* //g' | while read -r f; do
+		git add "$f"
+	done
+
+	# Remove any existing files deleted by the patch
+	grep 'delete mode' "${patch}" | sed -e 's/^.* mode [0-7]* //g' | while read -r f; do
+		git rm "$f"
+	done
+
+	# Open an editor so the user can clean up the rejects.  Use readarray
+	# instead of "<<<" because the latter picks up empty output as a single
+	# line and does variable expansion...  stupid bash.
+	readarray -t rej_files < <(grep 'saving rejects to' "${tmpfile}" | \
+				   sed -e 's/^.*saving rejects to file //g')
+	rm -f "${tmpfile}"
+	if [ "${#rej_files[@]}" -gt 0 ]; then
+		echo "Opening editor to deal with rejects.  Changes commit when you close the editor."
+		editor "${rej_files[@]}"
+	fi
+
+	stg refresh
+}
+
 apply_patch()
 {
 	local _patch=$1
@@ -385,11 +443,13 @@  apply_patch()
 		stg import -n $_patch_name $_new_patch.2
 		if [ $? -ne 0 ]; then
 			echo "stgit push failed!"
-			read -r -p "Skip or Fail [s|F]? " response
-			if [ -z "$response" -o "$response" != "s" ]; then
+			read -r -p "Skip, force Apply, or Fail [s|a|F]? " response
+			if [ -z "$response" -o "$response" = "F" -o "$response" = "f" ]; then
 				echo "Force push patch, fix and refresh."
 				echo "Restart from commit $_current_commit"
 				fail "Manual cleanup required!"
+			elif [ "$response" = "a" -o "$response" = "A" ]; then
+				stg_force_import "$_patch_name" "$_new_patch.2"
 			else
 				echo "Skipping. Manual series file cleanup needed!"
 			fi