feat(8-git-uformat-patch): init
This commit is contained in:
parent
2a4851b6a4
commit
5c60edb932
2 changed files with 255 additions and 0 deletions
55
8-git-uformat-patch/README.md
Normal file
55
8-git-uformat-patch/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Managing patches for package sources with Git
|
||||
|
||||
You're working on a package release for a software distribution, but you must
|
||||
apply some patches to the distribution package sources for the packaging to
|
||||
succeed.
|
||||
|
||||
You have checked out two repositories:
|
||||
|
||||
* a repository containing the source code of software you're packaging,
|
||||
* a repository containing the distribution package sources (package spec,
|
||||
etc.), referencing the software source code
|
||||
|
||||
You start patching files inside the build directory of the package.
|
||||
|
||||
Once the patch works, you commit it to the software sources.
|
||||
|
||||
Then use `git-uformat-patch` to generate patch files of the commits you've
|
||||
made to the software sources and store them in the distribution package's
|
||||
sources.
|
||||
|
||||
Clean the build directory and rerun the build after applying the patches through
|
||||
the patch files you've just created. Rebasing, etc. works through the same
|
||||
mechanism.
|
||||
|
||||
The patches are a shadow of the source code commits.
|
||||
|
||||
This way, you can
|
||||
|
||||
* manage patches atomically through Git,
|
||||
* and prepare patches for upstream merge requests to the sources early, while
|
||||
maintaining focus on packaging
|
||||
|
||||
Whether learning along the way and looking for tutoring by the software author,
|
||||
or quickly making patches redundant through approved merge requests, atomicity
|
||||
in patch management through Git allows to give concise historic context.
|
||||
|
||||
If you're packaging version 6 of some software, you would create patch files for
|
||||
all commits between now and the release tag in the software sources of the
|
||||
version you are packaging:
|
||||
|
||||
```
|
||||
/source-repo $> sh git-uformat-patch.sh -o /distro-repo HEAD...v6
|
||||
```
|
||||
|
||||
```
|
||||
/distro-repo $> patch -i ./*.patch
|
||||
```
|
||||
|
||||
But you can also create patch files for only the top 3 commits:
|
||||
|
||||
```
|
||||
/source-repo $> GITLOGOPTS='-3' sh git-uformat-patch.sh -o /distro-repo HEAD...v6
|
||||
```
|
||||
|
||||
Run `sh git-uformat-patch.sh -h`, for more information.
|
||||
200
8-git-uformat-patch/git-uformat-patch.sh
Normal file
200
8-git-uformat-patch/git-uformat-patch.sh
Normal 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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue