From 5d5b230c5a93aab45d0b0abb1f9c75e5e90d1b97 Mon Sep 17 00:00:00 2001 From: Sebastian Lackner Date: Thu, 7 Aug 2014 03:26:33 +0200 Subject: [PATCH] gitapply.sh: Rewrite script to improve performance on huge font patches. --- debian/tools/gitapply.sh | 688 +++++++++++++++------------------------ 1 file changed, 258 insertions(+), 430 deletions(-) diff --git a/debian/tools/gitapply.sh b/debian/tools/gitapply.sh index 35ab8eb1..658a6f28 100755 --- a/debian/tools/gitapply.sh +++ b/debian/tools/gitapply.sh @@ -19,38 +19,14 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA # -# Setup parser variables nogit=0 -lineno=0 -verbose=0 -patch_mode=0 -patch_tmpfile="" - -# Macros -abort() -{ - if [ ! -z "$patch_tmpfile" ]; then - rm "$patch_tmpfile" - patch_tmpfile="" - fi - echo "[PATCH:$lineno] ERR: $1" >&2 - exit 1 -} - -invalid_parser_state() -{ - abort "Invalid parser state (patch_mode=$patch_mode)!" -} - -warning() -{ - echo "[PATCH:$lineno] WRN: $1" >&2 -} +tmpfile="" +# Show usage information about gitapply script usage() { echo "" - echo "Usage: ./gitapply [--nogit] [-v] [-d DIRECTORY]" + echo "Usage: ./gitapply.sh [--nogit] [-d DIRECTORY]" echo "" echo "Reads patch data from stdin and applies the patch to the current" echo "directory or the directory given via commandline." @@ -60,9 +36,41 @@ usage() echo "" } +# Critical error, abort +abort() +{ + if [ ! -z "$tmpfile" ]; then + rm "$tmpfile" + tmpfile="" + fi + echo "[PATCH] ERR: $1" >&2 + exit 1 +} + +# Show a warning +warning() +{ + echo "[PATCH] WRN: $1" >&2 +} + +# Calculate git sha1 hash gitsha1() { - echo -en "blob $(du -b "$1" | cut -f1)\x00" | cat - "$1" | sha1sum | cut -d' ' -f1 + if [ -f "$1" ]; then + echo -en "blob $(du -b "$1" | cut -f1)\x00" | cat - "$1" | sha1sum | cut -d' ' -f1 + else + echo "0000000000000000000000000000000000000000" + fi +} + +# Determine size of a file (or zero, if it doesn't exist) +filesize() +{ + local size=$(du -b "$1" | cut -f1) + if [ -z "$size" ]; then + size="0" + fi + echo "$size" } @@ -76,7 +84,6 @@ while [[ $# > 0 ]]; do ;; -v) - verbose=1 ;; --directory=*) @@ -87,8 +94,7 @@ while [[ $# > 0 ]]; do ;; -R) - echo "Reverse applying patches not supported yet with this tool." >&2 - exit 1 + abort "Reverse applying patches not supported yet with this tool." ;; --help) @@ -108,21 +114,21 @@ if [ "$nogit" -eq 0 ] && command -v git >/dev/null 2>&1; then exit 1 fi -# Check for missing depdencies -for dependency in awk chmod cut dd du gzip hexdump patch sha1sum; do - if ! command -v "$dependency" >/dev/null 2>&1; then - echo "Missing dependency: $dependency - please install this program and try again." >&2 - exit 1 - fi -done - -# Detect BSD +# Detect BSD - we check this first to error out as early as possible if gzip -V 2>&1 | grep "BSD" &> /dev/null; then echo "This script is not compatible with *BSD utilities. Please install git," >&2 echo "which provides the same functionality and will be used instead." >&2 exit 1 fi +# Check for missing depdencies +for dependency in awk cut dd du grep gzip hexdump patch sha1sum; do + if ! command -v "$dependency" >/dev/null 2>&1; then + echo "Missing dependency: $dependency - please install this program and try again." >&2 + exit 1 + fi +done + # Decode base85 git data, prepend with a gzip header awk_b85=' BEGIN{ @@ -193,7 +199,7 @@ BEGIN{ if (and(cmd, 0x10)){ cp_size = get_byte(); } if (and(cmd, 0x20)){ cp_size += lshift(get_byte(), 8); } if (and(cmd, 0x40)){ cp_size += lshift(get_byte(), 16); } - if (cp_size == 0) cp_size = 0x10000; + if (cp_size == 0){ cp_size = 0x10000; } if (cp_offs + cp_size > src_size || cp_size > dst_size){ exit 1; } printf("1 %d %d\n", cp_offs, cp_size); dst_size -= cp_size; @@ -207,405 +213,227 @@ BEGIN{ printf("E 0 0\n"); }' -# Parse lines of the patch -while IFS= read -r line; do - (( lineno++ )) - - # In verbose mode we print each line of the patch to stdout - if [ "$verbose" -ne 0 ]; then - echo "$lineno: $line" - fi - - # MODE 1: Parse header - # Fall-through to 2 - if [ "$patch_mode" -eq 1 ]; then - if [[ "$line" =~ ^---\ (.*)$ ]]; then - patch_oldname="${BASH_REMATCH[1]}" - echo "$line" >> "$patch_tmpfile" - continue - - elif [[ "$line" =~ ^\+\+\+\ (.*)$ ]]; then - patch_newname="${BASH_REMATCH[1]}" - echo "$line" >> "$patch_tmpfile" - continue - - elif [ "${line:0:8}" == "old mode" ] || [ "${line:0:17}" == "deleted file mode" ]; then - # ignore - echo "$line" >> "$patch_tmpfile" - continue - - elif [[ "$line" =~ ^new\ mode\ ([0-9]*)$ ]] || [[ "$line" =~ ^new\ file\ mode\ ([0-9]*)$ ]]; then - patch_filemode="${BASH_REMATCH[1]}" - echo "$line" >> "$patch_tmpfile" - continue - - elif [ "${line:0:8}" == "new mode" ] || [ "${line:0:13}" == "new file mode" ]; then - patch_errors+=("$lineno: Unable to parse header line '$line'.") - patch_invalid=1 - echo "$line" >> "$patch_tmpfile" - continue - - elif [ "${line:0:9}" == "copy from" ] || [ "${line:0:7}" == "copy to" ]; then - patch_errors+=("$lineno: Copy header not implemented yet.") - patch_invalid=1 - echo "$line" >> "$patch_tmpfile" - continue - - elif [ "${line:0:7}" == "rename " ]; then - patch_errors+=("$lineno: Patch rename header not implemented yet.") - patch_invalid=1 - echo "$line" >> "$patch_tmpfile" - continue - - elif [ "${line:0:16}" == "similarity index" ] || [ "${line:0:19}" == "dissimilarity index" ]; then - # ignore - echo "$line" >> "$patch_tmpfile" - continue - - elif [[ "$line" =~ ^index\ ([a-fA-F0-9]*)\.\.([a-fA-F0-9]*) ]]; then - patch_oldsha1="${BASH_REMATCH[1]}" - patch_newsha1="${BASH_REMATCH[2]}" - echo "$line" >> "$patch_tmpfile" - continue - - elif [ "${line:0:6}" == "index " ]; then - patch_errors+=("$lineno: Unable to parse header line '$line'.") - patch_invalid=1 - echo "$line" >> "$patch_tmpfile" - continue - - else - # Remove first path components, which are always a/ and b/ for git patches - if [[ "$patch_oldname" =~ ^a/(.*)$ ]]; then - patch_oldname="${BASH_REMATCH[1]}" - elif [ "$patch_oldname" != "/dev/null" ]; then - abort "Old name doesn't start with a/." - fi - if [[ "$patch_newname" =~ ^b/(.*)$ ]]; then - patch_newname="${BASH_REMATCH[1]}" - elif [ "$patch_newname" != "/dev/null" ]; then - abort "New name doesn't start with b/." - fi - - patch_mode=2 - # fall-through - fi - fi - - # MODE 2: Decide between binary and textual patch data - # Fall-through to 200, 0 - if [ "$patch_mode" -eq 2 ]; then - if [[ "$line" == "GIT binary patch" ]]; then - - if [ -z "$patch_oldsha1" ] || [ -z "$patch_newsha1" ]; then - patch_errors+=("$lineno: Missing index header, sha1 sums required for binary patch.") - patch_invalid=1 - fi - - if [ "$patch_oldname" != "$patch_newname" ]; then - patch_errors+=("$lineno: Stripped old- and new name doesn't match for binary patch.") - patch_invalid=1 - fi - - if [ "$patch_invalid" -ne 0 ]; then - for error in "${patch_errors[@]}"; do echo "$error" >&2; done - abort "Unable to continue." - fi - - patch_mode=100 - continue - - elif [ "${line:0:4}" == "@@ -" ]; then - # We count the number of lines added/removed for informational purposes - patch_total_add=0 - patch_total_rem=0 - - patch_mode=200 - # fall-through - - elif [ "${line:0:11}" == "diff --git " ]; then - - if [ "$patch_oldname" != "$patch_newname" ]; then - patch_errors+=("$lineno: Stripped old- and new name doesn't match.") - patch_invalid=1 - fi - - if [ "$patch_invalid" -ne 0 ]; then - for error in "${patch_errors[@]}"; do echo "$error" >&2; done - abort "Unable to continue." - fi - - if [ ! -z "$patch_filemode" ]; then - echo "patching $patch_newname" - chmod "${patch_filemode: -3}" "$patch_oldname" # we ignore failures for now - fi - - patch_mode=0 - # fall-through - - elif [ ! -z "$line" ]; then - abort "Unknown patch format." - fi - fi - - # MODE 100: Decide between binary literal/delta patch - if [ "$patch_mode" -eq 100 ]; then - if [[ "$line" =~ ^(literal|delta)\ ([0-9]+)$ ]]; then - binary_patch_type="${BASH_REMATCH[1]}" - binary_patch_size="${BASH_REMATCH[2]}" - - # Check shasum if its not a patch creating a new file - if [ "$patch_oldsha1" != "0000000000000000000000000000000000000000" ] || [ "$binary_patch_type" == "delta" ] || [ -f "$patch_oldname" ]; then - if [ -f "$patch_oldname" ]; then - sha=$(gitsha1 "$patch_oldname") - else - sha="0000000000000000000000000000000000000000" - fi - if [ "$patch_oldsha1" != "$sha" ]; then - echo "$lineno: Expected $patch_oldsha1" >&2 - echo "$lineno: Got $sha" >&2 - abort "Unable to continue because of sha1 mismatch of original file." - fi - fi - - # Is it a patch deleting this file - if [ "$patch_newsha1" == "0000000000000000000000000000000000000000" ] && [ "$binary_patch_size" -eq 0 ] && [ "$binary_patch_type" == "literal" ]; then - echo "patching $patch_newname" - - # Apply the patch: Just delete the file - if [ -f "$patch_oldname" ] && ! rm "$patch_oldname"; then - abort "Unable to delete file $patch_oldname." - fi - - # We are ready with this one - patch_mode=0 - continue - fi - - # Clear temporary file, we will use it to decode the binary block - echo -en "" > "$patch_tmpfile" - patch_mode=101 - continue - - else - abort "Only literal/delta patches are supported." - fi - fi - - # MODE 101: Decode data - if [ "$patch_mode" -eq 101 ]; then - if [ ! -z "$line" ]; then - # Append this binary chunk in the temp file - echo "$line" >> "$patch_tmpfile" - - else - echo "patching $patch_newname" - - decoded_tmpfile=$(mktemp) - if [ ! -f "$decoded_tmpfile" ]; then - abort "Unable to create temporary file for patch." - fi - - # Decode base85 and add a gzip header - awk "$awk_b85" < "$patch_tmpfile" | gzip -dc > "$decoded_tmpfile" 2>/dev/null - - # The new temp file replaces the old one - rm "$patch_tmpfile" - patch_tmpfile="$decoded_tmpfile" - - # Ensure that resulting binary patch has the correct size - if [ "$binary_patch_size" -ne "$(du -b "$patch_tmpfile" | cut -f 1)" ]; then - abort "Uncompressed binary patch has wrong size." - fi - - # Apply git delta path - if [ "$binary_patch_type" == "delta" ]; then - - decoded_tmpfile=$(mktemp) - if [ ! -f "$decoded_tmpfile" ]; then - abort "Unable to create temporary file for patch." - fi - - binary_patch_complete=0 - binary_patch_destsize="" - - while read cmd arg1 arg2; do - if [ "$cmd" == "S" ]; then - binary_patch_destsize="$arg2" - [ "$arg1" -eq "$(du -b "$patch_oldname" | cut -f 1)" ] || break - - elif [ "$cmd" == "1" ]; then - dd if="$patch_oldname" bs=1 skip="$arg1" count="$arg2" >> "$decoded_tmpfile" 2>/dev/null || break - - elif [ "$cmd" == "2" ]; then - dd if="$patch_tmpfile" bs=1 skip="$arg1" count="$arg2" >> "$decoded_tmpfile" 2>/dev/null || break - - elif [ "$cmd" == "E" ]; then - binary_patch_complete=1 - - else break; fi - done < <(hexdump -v -e '32/1 "%02X" "\n"' "$patch_tmpfile" | awk "$awk_gitpatch") - - # The new temp file replaces the old one - rm "$patch_tmpfile" - patch_tmpfile="$decoded_tmpfile" - - if [ "$binary_patch_complete" -ne 1 ]; then - abort "Unable to parse full patch." - elif [ "$binary_patch_destsize" -ne "$(du -b "$patch_tmpfile" | cut -f 1)" ]; then - abort "Unpacked delta patch has wrong size." - fi - - elif [ "$binary_patch_type" != "literal" ]; then - invalid_parser_state - fi - - # Check shasum if its not a patch creating a new file - sha=$(gitsha1 "$patch_tmpfile") - if [ "$patch_newsha1" != "$sha" ]; then - echo "$lineno: Expected $patch_newsha1" >&2 - echo "$lineno: Got $sha" >&2 - abort "Unable to continue because of sha1 mismatch after applying the patch." - fi - - if ! cp "$patch_tmpfile" "$patch_oldname"; then - abort "Unable to replace original file." - fi - if [ ! -z "$patch_filemode" ]; then - chmod "${patch_filemode: -3}" "$patch_oldname" # we ignore failures for now - fi - - # We're ready with this patch - patch_mode=0 - continue - fi - fi - - # MODE 200: Text patch - # Fall-through to 0 - if [ "$patch_mode" -eq 200 ]; then - hunk="^@@\ -(([0-9]+),)?([0-9]+)\ \+(([0-9]+),)?([0-9]+)\ @@" - if [[ "$line" =~ $hunk ]]; then - # ${BASH_REMATCH[2]} - source line - # ${BASH_REMATCH[5]} - end line - hunk_src_lines="${BASH_REMATCH[3]}" - hunk_dst_lines="${BASH_REMATCH[6]}" - - if [ "$hunk_src_lines" -eq 0 ] && [ "$hunk_dst_lines" -eq 0 ]; then - abort "Empty hunk doesn't make sense." - fi - - # Start of a new hunk, append it - echo "$line" >> "$patch_tmpfile" - patch_mode=201 - continue - - elif [ "${line:0:2}" == "\\ " ]; then - # ignore - echo "$line" >> "$patch_tmpfile" - continue - - else - echo "patching $patch_newname" - - # Try to apply the patch using 'patch' - if ! patch -p1 -s -f < "$patch_tmpfile"; then - abort "Patch did not apply, aborting." - fi - - patch_mode=0 - # fall-through - fi - fi - - # MODE 201: Wait until we reach the end of a hunk - if [ "$patch_mode" -eq 201 ]; then - # These lines are part of a hunk, append it - echo "$line" >> "$patch_tmpfile" - - if [ "${line:0:1}" == " " ] && [ "$hunk_src_lines" -gt 0 ] && [ "$hunk_dst_lines" -gt 0 ]; then - (( hunk_src_lines-- )) - (( hunk_dst_lines-- )) - - elif [ "${line:0:1}" == "-" ] && [ "$hunk_src_lines" -gt 0 ]; then - (( hunk_src_lines-- )) - (( patch_total_rem++ )) - - elif [ "${line:0:1}" == "+" ] && [ "$hunk_dst_lines" -gt 0 ]; then - (( hunk_dst_lines-- )) - (( patch_total_add++ )) - - elif [ "${line:0:2}" == "\\ " ]; then - continue # ignore "\\ No newline ..." - - else - abort "Unexpected line in hunk." - fi - - # If it was the last line of this hunk then go back to mode 200 - if [ "$hunk_src_lines" -eq 0 ] && [ "$hunk_dst_lines" -eq 0 ]; then - patch_mode=200 - continue - fi - fi - - # MODE 0: Search for patch header - if [ "$patch_mode" -eq 0 ]; then - if [[ "$line" =~ ^diff\ --git\ ([^ ]*)\ ([^ ]*)$ ]]; then - - # Is this patch valid? The array will contain a list of detected errors - patch_invalid=0 - patch_errors=() - - # Setup name and sha1 sum variables - patch_oldname="${BASH_REMATCH[1]}" - patch_newname="${BASH_REMATCH[2]}" - patch_oldsha1="" - patch_newsha1="" - - # Filemode - patch_filemode="" - - if [ ! -z "$patch_tmpfile" ]; then - rm "$patch_tmpfile" - fi - patch_tmpfile=$(mktemp) - if [ ! -f "$patch_tmpfile" ]; then - abort "Unable to create temporary file for patch." - fi - echo "$line" >> "$patch_tmpfile" - - patch_mode=1 - continue - - elif [ "${line:0:4}" == "@@ -" ] || [ "${line:0:4}" == "--- " ] || [ "${line:0:4}" == "+++ " ]; then +# Create a temporary file containing the patch - NOTE: even if the user +# provided a filename it still makes sense to work with a temporary file, +# to avoid changes of the content while this script is active. +tmpfile=$(mktemp) +if [ ! -f "$tmpfile" ]; then + tmpfile="" + abort "Unable to create temporary file for patch." +elif ! cat > "$tmpfile"; then + abort "Patch truncated." +fi + +# Go through the different patch sections +lastoffset=1 +for offset in $(awk '/^diff --git /{ print FNR; }' "$tmpfile"); do + + # Check part between end of last patch and start of current patch + if [ "$lastoffset" -gt "$offset" ]; then + abort "Unable to split patch. Is this a proper git patch?" + elif [ "$lastoffset" -lt "$offset" ]; then + tmpoffset=$((offset - 1)) + if sed -n "$lastoffset,$tmpoffset p" "$tmpfile" | grep -q '^\(@@ -\|--- \|+++ \)'; then abort "Patch corrupted or not created with git." fi fi -done + # Find out the size of the patch header + tmpoffset=$((offset + 1)) + tmpoffset=$(sed -n "$tmpoffset,\$ p" "$tmpfile" | + awk '!/^(--- |\+\+\+ |old |new |copy |rename |similarity |index |GIT |literal |delta )/{ exit 42; } END{ print FNR; }') + if [ "$?" -ne 42 ]; then + tmpoffset=$((tmpoffset + 1)) + fi + hdroffset=$((offset + tmpoffset)) -# Apply last text patch (if any) -if [ "$patch_mode" -eq 200 ]; then - echo "patching $patch_newname" + # Parse all important fields of the header + patch_oldname="" + patch_newname="" + patch_oldsha1="" + patch_newsha1="" + patch_is_binary=0 + patch_binary_type="" + patch_binary_size="" - # Try to apply the patch using 'patch' - if ! patch -p1 -s -f < "$patch_tmpfile"; then - abort "Patch did not apply, aborting." + tmpoffset=$((hdroffset - 1)) + while IFS= read -r line; do + if [ "$line" == "GIT binary patch" ]; then + patch_is_binary=1 + + elif [[ "$line" =~ ^diff\ --git\ ([^ ]*)\ ([^ ]*)$ ]]; then + patch_oldname="${BASH_REMATCH[1]}" + patch_newname="${BASH_REMATCH[2]}" + + elif [[ "$line" =~ ^---\ (.*)$ ]]; then + patch_oldname="${BASH_REMATCH[1]}" + + elif [[ "$line" =~ ^\+\+\+\ (.*)$ ]]; then + patch_newname="${BASH_REMATCH[1]}" + + elif [[ "$line" =~ ^index\ ([a-fA-F0-9]*)\.\.([a-fA-F0-9]*) ]]; then + patch_oldsha1="${BASH_REMATCH[1]}" + patch_newsha1="${BASH_REMATCH[2]}" + + elif [[ "$line" =~ ^(literal|delta)\ ([0-9]+)$ ]]; then + patch_binary_type="${BASH_REMATCH[1]}" + patch_binary_size="${BASH_REMATCH[2]}" + + fi + done < <(sed -n "$offset,$tmpoffset p" "$tmpfile") + + # Remove first path components, which are always a/ and b/ for git patches + if [[ "$patch_oldname" =~ ^a/(.*)$ ]]; then + patch_oldname="${BASH_REMATCH[1]}" + elif [ "$patch_oldname" != "/dev/null" ]; then + abort "Old name doesn't start with a/." + fi + if [[ "$patch_newname" =~ ^b/(.*)$ ]]; then + patch_newname="${BASH_REMATCH[1]}" + elif [ "$patch_newname" != "/dev/null" ]; then + abort "New name doesn't start with b/." fi - patch_mode=0 + # Short progress message + echo "patching $patch_newname" + + # If its a textual patch, then use 'patch' to apply it. + if [ "$patch_is_binary" -eq 0 ]; then + + # Find end of textual patch + tmpoffset=$(sed -n "$hdroffset,\$ p" "$tmpfile" | awk '!/^(@| |+|-|\\)/{ exit 42; } END{ print FNR; }') + if [ "$?" -ne 42 ]; then + tmpoffset=$((tmpoffset + 1)) + fi + lastoffset=$((hdroffset + tmpoffset - 1)) + + # Apply textual patch + tmpoffset=$((lastoffset - 1)) + if ! sed -n "$offset,$tmpoffset p" "$tmpfile" | patch -p1 -s -f; then + abort "Textual patch did not apply, aborting." + fi + + continue + fi + + # It is a binary patch - check that requirements are fulfilled + if [ "$patch_binary_type" != "literal" ] && [ "$patch_binary_type" != "delta" ]; then + abort "Unknown binary patch type." + + elif [ -z "$patch_oldsha1" ] || [ -z "$patch_newsha1" ]; then + abort "Missing index header, sha1 sums required for binary patch." + + elif [ "$patch_oldname" != "$patch_newname" ]; then + abort "Stripped old and new name doesn't match for binary patch." + fi + + # Ensure that checksum of old file matches + sha=$(gitsha1 "$patch_oldname") + if [ "$patch_oldsha1" != "$sha" ]; then + abort "Checksum mismatch for $patch_oldname (expected: $patch_oldsha1, got $sha)." + fi + + # Find end of binary patch + tmpoffset=$(sed -n "$hdroffset,\$ p" "$tmpfile" | awk '!/^[A-Za-z]/{ exit 42; } END{ print FNR; }') + if [ "$?" -ne 42 ]; then + tmpoffset=$((tmpoffset + 1)) + fi + lastoffset=$((hdroffset + tmpoffset - 1)) + + # Special case - deleting the whole file + if [ "$patch_newsha1" == "0000000000000000000000000000000000000000" ] && + [ "$patch_binary_size" -eq 0 ] && [ "$patch_binary_type" == "literal" ]; then + + # Applying the patch just means deleting the file + if [ -f "$patch_oldname" ] && ! rm "$patch_oldname"; then + abort "Unable to delete file $patch_oldname." + fi + + continue + fi + + # Create temporary file for literal patch + literal_tmpfile=$(mktemp) + if [ ! -f "$literal_tmpfile" ]; then + abort "Unable to create temporary file for binary patch." + fi + + # Decode base85 and gzip compression + tmpoffset=$((lastoffset - 1)) + sed -n "$hdroffset,$tmpoffset p" "$tmpfile" | awk "$awk_b85" | gzip -dc > "$literal_tmpfile" 2>/dev/null + if [ "$patch_binary_size" -ne "$(filesize "$literal_tmpfile")" ]; then + rm "$literal_tmpfile" + abort "Uncompressed binary patch has wrong size." + fi + + # Convert delta to literal patch + if [ "$binary_patch_type" == "delta" ]; then + + # Create new temporary file for literal patch + delta_tmpfile="$literal_tmpfile" + literal_tmpfile=$(mktemp) + if [ ! -f "$literal_tmpfile" ]; then + rm "$delta_tmpfile" + abort "Unable to create temporary file for binary patch." + fi + + patch_binary_complete=0 + patch_binary_destsize=0 + + while read cmd arg1 arg2; do + if [ "$cmd" == "S" ]; then + [ "$arg1" -eq "$(filesize "$patch_oldname")" ] || break + patch_binary_destsize="$arg2" + + elif [ "$cmd" == "1" ]; then + dd if="$patch_oldname" bs=1 skip="$arg1" count="$arg2" >> "$literal_tmpfile" 2>/dev/null || break + + elif [ "$cmd" == "2" ]; then + dd if="$delta_tmpfile" bs=1 skip="$arg1" count="$arg2" >> "$literal_tmpfile" 2>/dev/null || break + + elif [ "$cmd" == "E" ]; then + patch_binary_complete=1 + + else break; fi + done < <(hexdump -v -e '32/1 "%02X" "\n"' "$delta_tmpfile" | awk "$awk_gitpatch") + + rm "$delta_tmpfile" + + if [ "$patch_binary_complete" -eq 0 ]; then + rm "$literal_tmpfile" + abort "Unable to parse full patch." + + elif [ "$patch_binary_destsize" -ne "$(filesize "$literal_tmpfile")" ]; then + rm "$literal_tmpfile" + abort "Unpacked delta patch has wrong size." + fi + fi + + # Ensure that checksum of literal patch matches + sha=$(gitsha1 "$literal_tmpfile") + if [ "$patch_newsha1" != "$sha" ]; then + rm "$literal_tmpfile" + abort "Checksum mismatch for patched $patch_newname (expected: $patch_newsha1, got $sha)." + fi + + # Apply the patch - copy literal patch to destination patch + if ! cp "$literal_tmpfile" "$patch_newname"; then + rm "$literal_tmpfile" + abort "Unable to replace $patch_newname with patched file." + fi + + rm "$literal_tmpfile" +done + +# Check last remaining part for unparsed patches +if sed -n "$lastoffset,\$ p" "$tmpfile" | grep -q '^\(@@ -\|--- \|+++ \)'; then + abort "Patch corrupted or not created with git." fi -# Make sure we're not just parsing a patch -if [ "$patch_mode" -ne 0 ]; then - abort "File ended in the middle of a patch!" -fi - -# Clean up temp files if any -if [ ! -z "$patch_tmpfile" ]; then - rm "$patch_tmpfile" - patch_tmpfile="" +# Delete temp file (if any) +if [ ! -z "$tmpfile" ]; then + rm "$tmpfile" + tmpfile="" fi # Success