200 lines
5.5 KiB
Bash
200 lines
5.5 KiB
Bash
#!/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"
|