https://github.com/floatpane/go-mailpatch
Parse git format-patch emails in Go. Commit metadata, [PATCH n/m] series, unified diffs, and diffstat — zero dependencies.
https://github.com/floatpane/go-mailpatch
Last synced: about 2 months ago
JSON representation
Parse git format-patch emails in Go. Commit metadata, [PATCH n/m] series, unified diffs, and diffstat — zero dependencies.
- Host: GitHub
- URL: https://github.com/floatpane/go-mailpatch
- Owner: floatpane
- License: mit
- Created: 2026-06-02T16:27:40.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2026-06-22T02:56:57.000Z (about 2 months ago)
- Last Synced: 2026-06-22T04:24:44.713Z (about 2 months ago)
- Language: Go
- Homepage: https://mailpatch.floatpane.com
- Size: 57.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# go-mailpatch
**Parse `git format-patch` emails in Go. Commit metadata, `[PATCH n/m]` series, unified diffs, and diffstat — zero dependencies.**
[](https://golang.org)
[](https://pkg.go.dev/github.com/floatpane/go-mailpatch)
[](https://github.com/floatpane/go-mailpatch/releases)
[](https://github.com/floatpane/go-mailpatch/actions/workflows/ci.yml)
[](LICENSE)
`git format-patch` turns commits into email: the subject becomes `[PATCH 2/3] fix the thing`, the author and date become headers, the commit message becomes the body, and the diff follows a `---` separator and a diffstat. `git send-email` mails them; reviewers reply; maintainers run `git am`. `go-mailpatch` reads those messages back into structured Go — author, date, series position, commit message, and a fully parsed diff — **without shelling out to git**.
It grew out of [matcha](https://github.com/floatpane/matcha)'s developer-mail features (patch review and `git-mail`), pulled into a standalone, git-free, dependency-free library.
## Features
- **Parse one message or a whole mbox.** `Parse` for a single patch, `ParseMbox` for every message in an mbox, `ParseSeries` to group a thread into its cover letter + ordered patches.
- **Subject intelligence.** `[PATCH]`, `[PATCH 2/3]`, `[RFC PATCH v3 1/4]` — the prefix is parsed into index, total, version, and cover-letter flag, and stripped from the clean subject. Non-patch subjects (`[bug] …`) are left untouched.
- **Real diff parsing.** Per-file `FileChange` with change type (added/deleted/renamed/copied/modified), old/new paths, file modes, binary detection, and per-line hunks. Add/delete counts and a `DiffStat` come for free.
- **Standalone diff parser.** `ParseDiff` works on a bare `git diff` with no email around it.
- **MIME-aware.** Decodes quoted-printable / base64 bodies and RFC 2047 encoded-word headers; digs the `text/plain` part out of multipart mail.
- **Zero dependencies, never runs git.** Standard library only. It parses; it does not apply. What you do with the result is up to you.
## Install
```bash
go get github.com/floatpane/go-mailpatch
```
Requires Go 1.26+.
## Usage
### Parse a single patch email
```go
package main
import (
"fmt"
"log"
"os"
"github.com/floatpane/go-mailpatch"
)
func main() {
p, err := mailpatch.Parse(os.Stdin)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s <%s>\n", p.AuthorName, p.AuthorEmail)
fmt.Printf("Subject: %s\n", p.Subject)
if p.Series.Total > 0 {
fmt.Printf("Patch %d of %d (v%d)\n",
p.Series.Index, p.Series.Total, p.Series.Version)
}
for _, f := range p.Files {
fmt.Printf(" %-8s %s +%d -%d\n",
f.Type, f.Path(), f.Additions, f.Deletions)
}
fmt.Printf("%d files changed, +%d -%d\n",
p.Stat.FilesChanged, p.Stat.Additions, p.Stat.Deletions)
}
```
```console
$ git format-patch -1 --stdout | go run .
Ada Lovelace
Subject: parser: handle empty input
Patch 2 of 3 (v1)
modified parser.go +3 -0
1 files changed, +3 -0
```
### Walk a patch series from an mbox
```go
f, _ := os.Open("series.mbox")
defer f.Close()
s, err := mailpatch.ParseSeries(f)
if err != nil {
log.Fatal(err)
}
if s.Cover != nil {
fmt.Println("cover:", s.Cover.Subject)
}
fmt.Printf("v%d, %d/%d patches present (complete=%v)\n",
s.Version, s.Len(), s.Total, s.Complete())
for _, p := range s.Patches { // already sorted by index
fmt.Printf(" [%d/%d] %s\n", p.Series.Index, p.Series.Total, p.Subject)
}
```
### Parse a bare diff (no email)
```go
files, _ := mailpatch.ParseDiff(diffText)
for _, f := range files {
for _, h := range f.Hunks {
for _, ln := range h.Lines {
switch ln.Kind {
case mailpatch.Add:
fmt.Println("+", ln.Text)
case mailpatch.Delete:
fmt.Println("-", ln.Text)
}
}
}
}
```
## What you get
| Type | Holds |
|------|-------|
| `Patch` | From / author name+email, Date, clean Subject, Message-ID / In-Reply-To / References, `SeriesInfo`, commit-message Body, raw Diff, parsed Files, DiffStat |
| `SeriesInfo` | Index, Total, Version, Prefix, IsCover |
| `FileChange` | OldPath / NewPath, Type, IsBinary, Old/NewMode, Additions / Deletions, Hunks |
| `Hunk` | OldStart/OldLines, NewStart/NewLines, Section, Lines |
| `Line` | Kind (Context / Add / Delete), Text |
| `DiffStat` | FilesChanged, Additions, Deletions |
| `Series` | Cover, ordered Patches, Version, Total |
## What this is not
- **Not a patch applier.** It never runs `git am` and never touches your working tree. Parse here, apply (and validate) yourself.
- **Not a diff renderer.** It gives you the structure; coloring/printing is the caller's job.
- **Not a full RFC 5322 MTA.** It handles the headers and encodings that format-patch mail uses in practice, not every corner of email.
## Documentation
Full API reference: [pkg.go.dev/github.com/floatpane/go-mailpatch](https://pkg.go.dev/github.com/floatpane/go-mailpatch)
Guides and diagrams: see [`docs/`](docs/).
## Sister projects
| Project | Role |
|---------|------|
| [floatpane/matcha](https://github.com/floatpane/matcha) | Reference consumer — uses this library for patch review and `git-mail`. |
| [floatpane/go-secretbox](https://github.com/floatpane/go-secretbox) | Sibling extraction — password-based encryption for data at rest. |
## Contributing
PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
## Security
It parses untrusted list mail. Report vulnerabilities privately via [SECURITY.md](SECURITY.md).
## License
MIT. See [LICENSE](LICENSE).