This only updates to Go 1.21 baseline, which is what the module still
specifies as the minimum version. I'll need to run this again when
moving to Go 1.25 or 1.26 as minimum.
Previously, this returned an io.ErrUnexpectedEOF. This is not wrong, in
that we did unexpectedly hit the end of the input file, but it is vague
and implies a possible library bug rather than a problem with the patch
or the input. This condition is really a conflict, as the changes
described by the patch are not compatible with the state of the input.
In issue #57, it was reported that the library could not apply some
patches generated by BSD `diff` because of the way that variant reports
changes in files without trailing newlines. Since the library behavior
matches Git, I don't consider this a bug, but update the README to
mention that the support for standard unified diffs is only for the GNU
variant of the `diff` tool.
Include file names in the header (now that we can actually parse them)
and fix a bad find-and-replace that changed "differ" to "fmer". Add a
new test to verify that binary files without data format correctly.
Some patches may include one or more file paths as part of the binary
header when there is no binary data. Git accounts for this by only
checking the prefix and suffix of the line, but I missed that logic when
implementing this originally.
This enables clients to move back and forth between parsed objects and
text patches. The generated patches are semantically equal to the parsed
object and should re-parse to the same object, but may not be
byte-for-byte identical to the original input.
In my testing, formatted text patches are usually identical to the
input, but there may be cases where this is not true. Binary patches
always differ. This is because Go's 'compress/flate' package ends
streams with an empty block instead of adding the end-of-stream flag to
the last non-empty block, like Git's C implementation. Since the streams
will always be different for this reason, I chose to also enable default
compression (the test patches I generated with Git used no compression.)
The main tests for this feature involve parsing, formatting, and then
re-parsing a patch to make sure we get equal objects.
Formatting is handled by a new internal formatter type, which allows
writing all data to the same stream. This isn't exposed publicly right
now, but will be useful if there's a need for more flexible formatting
functions in the future, like formatting to a user-provided io.Writer.
The minimum Go version for the package is now Go 1.21. This is because a
future change will use the 'slices' package in test code. Note that non-test
code in the package should still be compatible with older versions of Go.
As part of this, also update the golangci-lint version to one that works
with Go 1.21, which required replacing some deprecated linters.
While empty patches with only a header were parsable, the parser
discarded the preamble content. This meant callers had to handle this
case specially. Now, if we reach the end of the input without finding a
file, Parse() returns the full content of the patch as the preamble.
When GitHub creates patches for Dependabot PRs, it generates a "From:"
line that is not valid according to RFC 5322: the address spec contains
unquoted special characters (the "[bot]" in "dependabot[bot]"). While
the 'net/mail' parser makes some exceptions to the spec, this is not one
of them, so parsing these patch headers fails.
Git's 'mailinfo' command avoids this by only implementing the unquoting
part of RFC 5322 and then applying a heuristic to separate the string in
to name and email values that seem reasonable.
This commit does two things:
1. Reimplements ParsePatchIdentity to follow Git's logic, so that it can
accept a wider range of inputs, including quoted strings. Strings
accepted by the previous implementation parse in the same way with
one exception: inputs that contain whitespace inside the angle
brackets for an email address now use the email address as the name
and drop any separate name component.
2. When parsing mail-formatted patches, use ParsePatchIdentity to parse
the "From:" line instead of the 'net/mail' function.
Git is actually more lenient here than I thought. As long as the
identity contains the "<>" delimiters, Git will allow an empty email, so
we should accept the same thing. I also discovered that an identity with
only an email set will use the email as the name, so I've implemented
that behavior as well.