Skip to content

Instantly share code, notes, and snippets.

@HaleTom
Last active August 20, 2016 10:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HaleTom/275f28403828b9b9b93d313990fc94f4 to your computer and use it in GitHub Desktop.
Save HaleTom/275f28403828b9b9b93d313990fc94f4 to your computer and use it in GitHub Desktop.
Patch gems then build them
#!/bin/bash
# Usage: gem-patch
# Code updates: https://gist.github.com/HaleTom/275f28403828b9b9b93d313990fc94f4
# Features:
# Work around `patch` returning non-zero if some patch hunks are already applied
# Apply all patches in $patch_dir (in order) to their corresponding gem(s)
# Build a gem only after all patches have been applied
# Only build the gem if it was patched
# Robust error handling
patch_dir="$HOME/lib/gem-patch"
# Patches are assumed to be made with git patch
# Files are to be named gem-name or gem-name.patch-explanation
set -e -u
shopt -s nullglob # Globs are '' when no files match a pattern
gems_dir="$(gem environment gemdir)/gems"
if ! compgen -G "$patch_dir/*" > /dev/null; then
echo "Couldn't find any patches located in $patch_dir. Quitting." 2>&1
exit 1
fi
# Save the current "gemname-1.2.3" so that when it changes to a new one
# (ie, all patches have been applied) it can be built only once
function build_prev_if_needed {
if [[ ${prev_gem_ver:="${gem_ver:=''}"} != "$gem_ver" ]]; then
# We've moved on to another gem, build the last one
( cd "$gems_dir/$prev_gem_ver" &&
gem build "${prev_gem_ver%%-[-0-9.]*}.gemspec"
)
fi
prev_gem_ver="$gem_ver"
}
for patch in "$patch_dir"/*; do
gem_name=$(basename "${patch%%[.]*}") found_one=false
# $gem_dir becomes "rails-5.0.0.1" from find at end of loop
while read -d '' -r gem_dir; do
found_one=true
# Build the previously seen gem if we've moved on to a new one
gem_ver=${gem_dir##$gems_dir/}
echo -n "$gem_ver (patch $(basename "$patch")): "
# If we could reverse the patch, then it has already been applied; skip it
if patch --dry-run --reverse -d "$gem_dir" -fp1 --ignore-whitespace -i "$patch" >/dev/null 2>&1; then
echo "skipping (already applied)"
continue
else # patch not yet applied
echo "patching..."
# Patch returns non-zero if some hunks have already been applied
if ! patch -d "$gem_dir" -fsp1 --ignore-whitespace -i "$patch"; then
# Check that the patch was fully applied by pretending to reverse it
if patch --dry-run --reverse -d "$gem_dir" -fp1 --ignore-whitespace -i "$patch" >/dev/null 2>&1; then
echo "Ignoring failure: hunk(s) were already applied"
else
echo "Patch failed for $gem_dir" >&2; exit 1;
fi
fi
build_prev_if_needed
fi
done < <(find "$gems_dir" -maxdepth 1 -type d -regex ".*/$gem_name-[-0-9.]+" -print0)
if [[ $found_one != true ]]; then
echo "Fatal: Patch file '$(basename "$patch")': Couldn't find any gem sources named $gems_dir/$(basename "$patch")*" >&2; exit 1
fi
done # $gem_dir is now blank
gem_ver=''
build_prev_if_needed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment