The Diff Model
The diff inside a patch — or any standalone git diff — parses into a slice of
FileChange, each with its hunks. Parse does this for you and fills
Patch.Files; ParseDiff exposes it directly for diffs with no email around
them.
files, err := mailpatch.ParseDiff(diffText)
The shape
type FileChange struct {
OldPath string
NewPath string
Type ChangeType // Modified | Added | Deleted | Renamed | Copied
IsBinary bool
OldMode string // e.g. "100644"
NewMode string
Additions int
Deletions int
Hunks []Hunk
}
Path()returns the current path —NewPath, orOldPathfor a deletion.Typeis inferred fromnew file mode/deleted file mode/rename/copyheaders and from/dev/nullon either side.IsBinaryis set forBinary files differandGIT binary patch.
Hunks and lines
type Hunk struct {
OldStart, OldLines int
NewStart, NewLines int
Section string // text after the closing @@
Lines []Line
}
type Line struct {
Kind LineKind // Context | Add | Delete
Text string // leading +/-/space removed
}
Walk them to render or analyze:
for _, f := range files {
fmt.Printf("%s %s (+%d -%d)\n", f.Type, f.Path(), f.Additions, f.Deletions)
for _, h := range f.Hunks {
fmt.Printf(" @@ -%d,%d +%d,%d @@ %s\n",
h.OldStart, h.OldLines, h.NewStart, h.NewLines, h.Section)
for _, ln := range h.Lines {
switch ln.Kind {
case mailpatch.Add:
fmt.Println(" +", ln.Text)
case mailpatch.Delete:
fmt.Println(" -", ln.Text)
case mailpatch.Context:
fmt.Println(" ", ln.Text)
}
}
}
}
Diffstat
Patch.Stat (and the counts on each FileChange) are computed from the parsed
hunks, not from the textual diffstat git prints — so they reflect what is
actually in the diff:
type DiffStat struct {
FilesChanged int
Additions int
Deletions int
}
Bare diffs
ParseDiff does not need a git header. A plain --- / +++ / @@ diff parses
too, which makes it handy for diffs pasted into issues or produced by tools
other than git. Unrecognized lines are ignored, so a diff embedded in
surrounding prose still parses.