Files
nixpkgs/pkgs/build-support/fetchpatch/default.nix
Morgan Jones 77e773dbc6 fetchpatch: support hunks option
We can pick individual hunks or ranges of hunks with filterdiff, so
expose that in fetchpatch.

This was originally useful for openssh, since the first hunks always
look like this (that is, only differing in the date):

```
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.616 2025/08/29 03:50:38 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.617 2025/09/04 00:29:09 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
```

The usage looks something like this to skip the first hunk in the patch.
Numbers work too, if you'd like to only pick out specific hunks.

```
fetchpatch {
  name = "my-patch";
  url = "...";
  hunks = [ "2-" ];
  hash = "...";
}
```
2025-10-12 23:00:17 -07:00

127 lines
3.5 KiB
Nix

# This function downloads and normalizes a patch/diff file.
# This is primarily useful for dynamically generated patches,
# such as GitHub's or cgit's, where the non-significant content parts
# often change with updating of git or cgit.
# stripLen acts as the -p parameter when applying a patch.
{
lib,
fetchurl,
patchutils,
}:
{
relative ? null,
stripLen ? 0,
decode ? "cat", # custom command to decode patch e.g. base64 -d
extraPrefix ? null,
excludes ? [ ],
includes ? [ ],
hunks ? [ ],
revert ? false,
postFetch ? "",
nativeBuildInputs ? [ ],
...
}@args:
let
args' =
if relative != null then
{
stripLen = 1 + lib.length (lib.splitString "/" relative) + stripLen;
extraPrefix = lib.optionalString (extraPrefix != null) extraPrefix;
}
else
{
inherit stripLen extraPrefix;
};
in
let
inherit (args') stripLen extraPrefix;
in
lib.throwIfNot (excludes == [ ] || includes == [ ])
"fetchpatch: cannot use excludes and includes simultaneously"
fetchurl
(
{
nativeBuildInputs = [ patchutils ] ++ nativeBuildInputs;
postFetch = ''
tmpfile="$TMPDIR/patch"
if [ ! -s "$out" ]; then
echo "error: Fetched patch file '$out' is empty!" 1>&2
exit 1
fi
set +e
${decode} < "$out" > "$tmpfile"
if [ $? -ne 0 ] || [ ! -s "$tmpfile" ]; then
echo 'Failed to decode patch with command "'${lib.escapeShellArg decode}'"' >&2
echo 'Fetched file was (limited to 128 bytes):' >&2
od -A x -t x1z -v -N 128 "$out" >&2
exit 1
fi
set -e
mv "$tmpfile" "$out"
lsdiff \
${lib.optionalString (relative != null) "-p1 -i ${lib.escapeShellArg relative}/'*'"} \
"$out" \
| sort -u | sed -e 's/[*?]/\\&/g' \
| xargs -I{} --delimiter='\n' \
filterdiff \
--include={} \
--strip=${toString stripLen} \
${
lib.optionalString (extraPrefix != null) ''
--addoldprefix=a/${lib.escapeShellArg extraPrefix} \
--addnewprefix=b/${lib.escapeShellArg extraPrefix} \
''
} \
--clean "$out" > "$tmpfile"
if [ ! -s "$tmpfile" ]; then
echo "error: Normalized patch '$tmpfile' is empty (while the fetched file was not)!" 1>&2
echo "Did you maybe fetch a HTML representation of a patch instead of a raw patch?" 1>&2
echo "Fetched file was:" 1>&2
cat "$out" 1>&2
exit 1
fi
filterdiff \
-p1 \
${toString (map (x: "-x ${lib.escapeShellArg x}") excludes)} \
${toString (map (x: "-i ${lib.escapeShellArg x}") includes)} \
${
lib.optionalString (hunks != [ ])
"-# ${lib.escapeShellArg (lib.concatMapStringsSep "," toString hunks)}"
} \
"$tmpfile" > "$out"
if [ ! -s "$out" ]; then
echo "error: Filtered patch '$out' is empty (while the original patch file was not)!" 1>&2
echo "Check your includes and excludes." 1>&2
echo "Normalized patch file was:" 1>&2
cat "$tmpfile" 1>&2
exit 1
fi
''
+ lib.optionalString revert ''
interdiff "$out" /dev/null > "$tmpfile"
mv "$tmpfile" "$out"
''
+ postFetch;
}
// removeAttrs args [
"relative"
"stripLen"
"decode"
"extraPrefix"
"excludes"
"includes"
"hunks"
"revert"
"postFetch"
"nativeBuildInputs"
]
)