If a file was an exact multiple of 1024 bytes (the size of an internal
buffer) and was missing a final newline, the LineReaderAt implementation
would drop the last line, leading to an unexpected EOF error on apply.
In addition to fixing the bug, slightly change the behavior of
ReadLineAt to reflect how it is actually used:
1. Clarify that the return value n includes all lines instead of only
lines with a final newline. This was already true except in the
case of the bug fixed by this commit.
2. Only return io.EOF if fewer lines are read than requested. The
previous implementation also returned io.EOF if the last line was
missing a final newline, but this was confusing and didn't really
serve a purpose.
This is technically a breaking change for external implementations but
an implementation that exactly followed the "spec" was already broken in
certain edge cases.
go-gitdiff
A Go library for parsing and applying patches generated by git diff, git show, and git format-patch. It can also parse and apply unified diffs
generated by the standard diff tool.
It supports standard line-oriented text patches and Git binary patches, and
aims to parse anything accepted by the git apply command.
patch, err := os.Open("changes.patch")
if err != nil {
log.Fatal(err)
}
// files is a slice of *gitdiff.File describing the files changed in the patch
// preamble is a string of the content of the patch before the first file
files, preamble, err := gitdiff.Parse(patch)
if err != nil {
log.Fatal(err)
}
code, err := os.Open("code.go")
if err != nil {
log.Fatal(err)
}
// apply the changes in the patch to a source file
var output bytes.Buffer
if err := gitdiff.NewApplier(code).ApplyFile(&output, files[0]); err != nil {
log.Fatal(err)
}
Development Status
Mostly complete. API changes are possible, particularly for patch application, but I expect the parsing interface and types to remain stable.
Patch parsing and strict application are well-covered by unit tests and the library is used in a production application that parses and applies thousands of patches every day, but the space of all possible patches is large, so there are likely undiscovered bugs.
Why another git/unified diff parser?
Several packages with similar functionality exist, so why did I write another?
-
No other packages I found support binary diffs, as generated with the
--binaryflag. This is the main reason for writing a new package, as the format is pretty different from line-oriented diffs and is unique to Git. -
Most other packages only parse patches, so you need additional code to apply them (and if applies are supported, it is only for text files.)
-
This package aims to accept anything that
git applyaccepts, and closely follows the logic inapply.c. -
It seemed like a fun project and a way to learn more about Git.
Differences From Git
-
Certain types of invalid input that are accepted by
git applygenerate errors. These include:- Numbers immediately followed by non-numeric characters
- Trailing characters on a line after valid or expected content
- Malformed file header lines (lines that start with
diff --git)
-
Errors for invalid input are generally more verbose and specific than those from
git apply. -
The translation from C to Go may have introduced inconsistencies in the way Unicode file names are handled; these are bugs, so please report any issues of this type.
-
When reading headers, there is no validation that OIDs present on an
indexline are shorter than or equal to the maximum hash length, as this requires knowing if the repository used SHA1 or SHA256 hashes. -
When reading "traditional" patches (those not produced by
git), prefixes are not stripped from file names;git applyattempts to remove prefixes that match the current repository directory/prefix. -
Patches can only be applied in "strict" mode, where the line numbers and context of each fragment must exactly match the source file;
git applyimplements a search algorithm that tries different lines and amounts of context, with further options to normalize or ignore whitespace changes.