feat(8-git-uformat-patch): init

This commit is contained in:
Tiara Rodney 2025-06-04 16:17:39 +02:00
parent 2a4851b6a4
commit 5c60edb932
No known key found for this signature in database
GPG key ID: 5F43FAB4FBE5B5EB
2 changed files with 255 additions and 0 deletions

View file

@ -0,0 +1,200 @@
#!/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"