#!/usr/bin/env sh GITDIFFOPTS="$GITDIFFOPTS -p" usage() { script=$(basename "$0") cat << EOF usage: $script [OPTIONS] GITREVRANGE Prepare Git patches for distro packaging Basically \`git format-patch\` as a POSIX-compliant shell script, but using unified patch format instead of the UNIX mailbox format for output. The script does patch enumeration and automatic filename generation with commit title and patch number for commits of a given revision range selection. Use a software's Git-controlled sources to manage and generate patches for packaging it for a software distribution like ArchLinux, MSYS2, Ubuntu, etc. While it is probably okay to make quick (monkey-)patches that fix the software for a specific software distribution (and possibly break it for every other), it is better to make proper surgical-precision patches and attempt to have them accepted by the software developer ("upstream"). That's what this script helps with: Use Git on the sources, track patches as commits for creating a merge request, while generating packagaging patch files in unified-format for each commit, until the changes were accepted upstream. Options -h - show this message -i INITIAL_PATCH_NUMBER - initial patch number [optional] -o OUTPUT_DIRECTORY - output directory [optional] Environment Variables: AS_LIB - don't actually run the script. Can be used to import functions from this shell script. GITDIFFOPTS - optional arguments to supply to \`git diff\` GITLOGOPTS - optional arguments to supply to \`git log\` GITREVRANGE - revision range selector, e.g. HEAD...HEAD^ INITIAL_PATCH_NUMBER - number to start counting patches (upward) from OUTPUT_DIRECTORY - directory to store generated patch files under Examples: Output to stdut $script HEAD...v6 Output to directory $script -o ../../msys2/MINGW-packages/mingw-w64-mypackage HEAD...v6 Start patch numbering at 5 $script -i 5 HEAD...v6 Pass output directory as environment variable OUTPUT_DIRECTORY=some/dir $script -i 30 HEAD...v6 dot import a function and call it directly AS_LIB=yes . $script patch_from_commit -o some/dir 1a34e 1 Disable renames in diffs by passing \`git diff\` option GITDIFFOPTS='--no-renames' $script HEAD...v6 Trim revision range selection to 3 by passing \`git log\` option GITLOGOPTS="-3" sh $script 'HEAD' See Also: - https://www.msys2.org/wiki/Creating-Packages/#patch-software - https://git-scm.com/docs/git-format-patch - https://git-scm.com/docs/git-diff - https://www.gnu.org/software/diffutils/manual/html_node/patch-Options.html EOF } patch_from_commit() { stdout=yes while getopts "i:o:" opt; do case $opt in o) output_directory="$OPTARG";; i) patch_number="$OPTARG";; \?) echo "Invalid option: -$OPTARG" >&2 return 1 ;; :) echo "Option -$OPTARG requires an argument." >&2 return 2 ;; esac done shift $((OPTIND -1)) commit_id=$1 test -z "$patch_number" && patch_number=1 ! test -z "$output_directory" && stdout=no name="$( git log --pretty=format:%s $commit_id -1 \ | sed -e 's|[^A-Za-z0-9_]|-|g' \ | sed -E 's|-*-|-|g' \ | sed -E 's|-$||' )" padded="0000$patch_number" ppatchnumber="$( echo $padded | sed "s|$(echo "$padded" | sed -e 's|....$||')||" )" path="$output_directory"/"$ppatchnumber-$name".patch cmd="git diff $GITDIFFOPTS $commit_id^..$commit_id" if ! test "$stdout" '=' 'no'; then $cmd --color | tee /dev/null else echo "$(basename "$0"): $path" $cmd --no-color | tee "$path" fi } format_patch() { local OPTIND local OPTARG while getopts "o:i:" opt; do case $opt in o) optargs_patch_from_commit="$optargs_patch_from_commit -o $OPTARG";; i) patch_number=$OPTARG;; \?) echo "Invalid option: -$OPTARG" >&2 return 4 ;; :) echo "Option -$OPTARG requires an argument." >&2 return 5 ;; esac done shift $((OPTIND -1)) unset OPTIND unset OPTARG test -z "$patch_number" && patch_number=1 for commit_id in $(git log $GITLOGOPTS --oneline $@ | cut -d ' ' -f1 | tac); do git log $commit_id -1 >&2 | tee /dev/null patch_from_commit $optargs_patch_from_commit -i $patch_number $commit_id \ | sed "s|^|$commit_id: |" patch_number=$(expr $patch_number '+' 1) done } while getopts "i:o:h" opt; do case $opt in o) OUTPUT_DIRECTORY="$OPTARG";; i) INITIAL_PATCH_NUMBER=$OPTARG;; h) usage; exit 0;; :) echo "Option -$OPTARG requires an argument." >&2 usage exit 6 ;; esac done shift $((OPTIND -1)) unset OPTIND unset OPTARG ! test -z "$1" && GITREVRANGE="$1" test -z "$GITREVRANGE" && { echo "error: missing first argument: Git revision range" >&2 exit 3 } ! test -z "$OUTPUT_DIRECTORY" && optargs="$optargs -o "$OUTPUT_DIRECTORY"" ! test -z "$INITIAL_PATCH_NUMBER" && optargs="$optargs -i $INITIAL_PATCH_NUMBER" ! test "$AS_LIB" '=' 'yes' && format_patch $optargs "$GITREVRANGE"