Commit e4ed034

mo khan <mo@mokhan.ca>
2026-01-31 00:18:31
initial commit
1 parent 83be9dd
Changed files (147)
.github
docs
img
pkg
git
gitdiff
testdata
apply
string
links
templates
.github/scripts/build.mjs
@@ -1,33 +0,0 @@
-$.verbose = true
-
-const goos = [
-  'linux',
-  'darwin',
-  'windows',
-]
-const goarch = [
-  'amd64',
-  'arm64',
-]
-
-const name = (GOOS, GOARCH) => `gitmal_${GOOS}_${GOARCH}` + (GOOS === 'windows' ? '.exe' : '')
-
-const resp = await fetch('https://api.github.com/repos/antonmedv/gitmal/releases/latest')
-const {tag_name: latest} = await resp.json()
-
-await $`go mod download`
-
-await Promise.all(
-  goos.flatMap(GOOS =>
-    goarch.map(GOARCH =>
-      $`GOOS=${GOOS} GOARCH=${GOARCH} go build -o ${name(GOOS, GOARCH)}`)))
-
-await Promise.all(
-  goos.flatMap(GOOS =>
-    goarch.map(GOARCH =>
-      $`gh release upload ${latest} ${name(GOOS, GOARCH)}`)))
-
-await Promise.all(
-  goos.flatMap(GOOS =>
-    goarch.map(GOARCH =>
-      $`rm ${name(GOOS, GOARCH)}`)))
.github/workflows/release.yaml
@@ -1,43 +0,0 @@
-name: release
-
-on:
-  release:
-    types: [ created ]
-  workflow_dispatch:
-
-jobs:
-  docker:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-
-      - name: Login to Docker Hub
-        uses: docker/login-action@v3
-        with:
-          username: antonmedv
-          password: ${{ secrets.DOCKERHUB_TOKEN }}
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
-
-      - name: Build test image
-        uses: docker/build-push-action@v5
-        with:
-          context: .
-          file: ./Dockerfile
-          load: true
-          tags: antonmedv/gitmal:test
-
-      - name: Smoke test - verify binary runs
-        run: |
-          docker run --rm antonmedv/gitmal:test --help
-
-      - name: Build and push
-        uses: docker/build-push-action@v5
-        with:
-          context: .
-          file: ./Dockerfile
-          push: true
-          platforms: linux/amd64,linux/arm64
-          tags: antonmedv/gitmal:latest
.github/workflows/test.yaml
@@ -1,18 +0,0 @@
-name: test
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
-
-jobs:
-  test:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-go@v3
-        with:
-          go-version: 1.21
-      - name: Test
-        run: go test ./...
docs/how-to-self-host-a-git-repository.md
@@ -1,67 +0,0 @@
-# How to Self-Host a Git Repository?
-
-**Goal**: self-host a Git repository on your server, allowing read-only clones and a web view.
-
-Create a _git_ user on the server where you want to host the repository. This step is optional, but I like to use a
-dedicated user for repository management to keep the system secure and organized.
-
-Create a `~/public` directory, which will serve as the web root. Configure a web server to serve files from this
-directory.
-
-## Bare repo
-
-Create a bare repository inside `~/public`.
-A [bare repository](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols)
-is a special type of Git repository that doesn't have a working directory. It's used for hosting and sharing code
-without the need for a local working copy.
-
-```sh
-git init --bare repo.git
-```
-
-Create a [post-update](https://git-scm.com/docs/git-receive-pack#_post_update_hook) hook with the following content:
-
-```sh
-#!/bin/sh
-exec git update-server-info
-```
-
-Make the hook executable:
-
-```sh
-chmod +x hooks/post-update
-```
-
-## Gitmal hook
-
-Create a [post‑receive](https://git-scm.com/docs/git-receive-pack#_post_receive_hook) hook with the following content:
-
-```sh
-#!/bin/sh
-exec gitmal --output /home/git/public/repo/
-```
-
-Make the hook executable:
-
-```sh
-chmod +x hooks/post-receive
-```
-
-## Publish
-
-Push your local repository to the bare repository on the server using ssh protocol.
-
-```sh
-git remote add origin git@example.com:public/repo.git
-git push origin master
-```
-
-After pushing, git will run gitmal and generate files under `~/public/repo/` directory.
-
-- Private read-write clone URL: `git@example.com:public/repo.git`
-- Public **read-only** clone URL: `http://example.com/repo.git`
-- Gitmal static web view: `http://example.com/repo/`
-
-```sh
-git clone https://example.com/repo.git
-```
img/gitmal-color-logo.webp
Binary file
img/gitmal-screenshot-code-highlighting.webp
Binary file
img/gitmal-screenshot-file-tree.webp
Binary file
img/gitmal-screenshot-files.webp
Binary file
img/gitmal-smallest-logo.webp
Binary file
pkg/git/git_test.go
@@ -1,64 +0,0 @@
-package git
-
-import (
-	"testing"
-)
-
-func TestParseRefNames_Empty(t *testing.T) {
-	got := parseRefNames("")
-	if len(got) != 0 {
-		t.Fatalf("expected empty slice, got %v", got)
-	}
-}
-
-func TestParseRefNames_Mixed(t *testing.T) {
-	input := "HEAD -> main, tag: v1.0.0, origin/HEAD -> origin/main, origin/main, master"
-	got := parseRefNames(input)
-	if len(got) != 5 {
-		t.Fatalf("expected 5 entries, got %d (%v)", len(got), got)
-	}
-
-	// 1: HEAD pointer
-	if got[0].Kind != RefKindHEAD || got[0].Name != "HEAD" || got[0].Target != "main" {
-		t.Errorf("unexpected HEAD entry: %+v", got[0])
-	}
-	// 2: tag
-	if got[1].Kind != RefKindTag || got[1].Name != "v1.0.0" || got[1].Target != "" {
-		t.Errorf("unexpected Tag entry: %+v", got[1])
-	}
-	// 3: remote HEAD pointer
-	if got[2].Kind != RefKindRemoteHEAD || got[2].Name != "origin/HEAD" || got[2].Target != "origin/main" {
-		t.Errorf("unexpected RemoteHEAD entry: %+v", got[2])
-	}
-	// 4: remote branch
-	if got[3].Kind != RefKindRemote || got[3].Name != "origin/main" || got[3].Target != "" {
-		t.Errorf("unexpected Remote entry: %+v", got[3])
-	}
-	// 5: local branch
-	if got[4].Kind != RefKindBranch || got[4].Name != "master" || got[4].Target != "" {
-		t.Errorf("unexpected Branch entry: %+v", got[4])
-	}
-}
-
-func TestParseRefNames_Singles(t *testing.T) {
-	cases := []struct {
-		in     string
-		kind   RefKind
-		name   string
-		target string
-	}{
-		{"tag: v2", RefKindTag, "v2", ""},
-		{"main", RefKindBranch, "main", ""},
-		{"origin/dev", RefKindRemote, "origin/dev", ""},
-		{"origin/HEAD -> origin/main", RefKindRemoteHEAD, "origin/HEAD", "origin/main"},
-	}
-	for _, c := range cases {
-		got := parseRefNames(c.in)
-		if len(got) != 1 {
-			t.Fatalf("%q: expected 1 entry, got %d (%v)", c.in, len(got), got)
-		}
-		if got[0].Kind != c.kind || got[0].Name != c.name || got[0].Target != c.target {
-			t.Errorf("%q: unexpected entry: %+v", c.in, got[0])
-		}
-	}
-}
pkg/git/utils_test.go
@@ -1,80 +0,0 @@
-package git_test
-
-import (
-	"strings"
-	"testing"
-
-	"github.com/antonmedv/gitmal/pkg/git"
-)
-
-func TestParseFileMode(t *testing.T) {
-	tests := []struct {
-		mode     string
-		expected string
-	}{
-		{"100644", "rw-r--r--"},
-		{"100755", "rwxr-xr-x"},
-		{"100600", "rw-------"},
-		{"100400", "r--------"},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.mode, func(t *testing.T) {
-			result, err := git.ParseFileMode(tt.mode)
-			if err != nil {
-				t.Errorf("unexpected error: %v", err)
-			}
-			if result != tt.expected {
-				t.Errorf("got %q, want %q", result, tt.expected)
-			}
-		})
-	}
-}
-
-func TestParseFileModeInvalid(t *testing.T) {
-	tests := []string{
-		"",
-		"12",
-		"abc",
-		"10070x",
-	}
-
-	for _, mode := range tests {
-		t.Run(mode, func(t *testing.T) {
-			result, err := git.ParseFileMode(mode)
-			if err == nil {
-				t.Error("expected error, got nil")
-			}
-			if result != "" {
-				t.Errorf("expected empty result, got %q", result)
-			}
-			if !strings.Contains(err.Error(), "invalid mode") && !strings.Contains(err.Error(), "strconv.Atoi") {
-				t.Errorf("unexpected error message: %v", err)
-			}
-		})
-	}
-}
-
-func TestRefToFileName(t *testing.T) {
-	tests := []struct {
-		in   string
-		want string
-	}{
-		{"main", "main"},
-		{"master", "master"},
-		{"release/v1.0", "release-v1.0"},
-		{"feature/add-login", "feature-add-login"},
-		{"bugfix\\windows\\path", "bugfix-windows-path"},
-		{"1.0.0", "1.0.0"},
-		{"1.x", "1.x"},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.in, func(t *testing.T) {
-			got := git.RefToFileName(tt.in)
-			if got != tt.want {
-				t.Fatalf("refToFileName(%q) = %q, want %q", tt.in, got, tt.want)
-			}
-		})
-	}
-}
pkg/gitdiff/testdata/apply/bin.go
@@ -1,124 +0,0 @@
-//go:build ignore
-
-// bin.go is a helper CLI to manipulate binary diff data for testing purposes.
-// It can decode patches generated by git using the standard parsing functions
-// or it can encode binary data back into the format expected by Git. It
-// operates on stdin writes results (possibly binary) to stdout.
-
-package main
-
-import (
-	"bytes"
-	"compress/zlib"
-	"encoding/binary"
-	"flag"
-	"io/ioutil"
-	"log"
-	"os"
-	"strings"
-
-	"github.com/bluekeyes/go-gitdiff/gitdiff"
-)
-
-var (
-	b85Powers = []uint32{52200625, 614125, 7225, 85, 1}
-	b85Alpha  = []byte(
-		"0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "!#$%&()*+-;<=>?@^_`{|}~",
-	)
-)
-
-var mode string
-
-func base85Encode(data []byte) []byte {
-	chunks, remaining := len(data)/4, len(data)%4
-	if remaining > 0 {
-		data = append(data, make([]byte, 4-remaining)...)
-		chunks++
-	}
-
-	var n int
-	out := make([]byte, 5*chunks)
-
-	for i := 0; i < len(data); i += 4 {
-		v := binary.BigEndian.Uint32(data[i : i+4])
-		for j := 0; j < 5; j++ {
-			p := v / b85Powers[j]
-			out[n+j] = b85Alpha[p]
-			v -= b85Powers[j] * p
-		}
-		n += 5
-	}
-
-	return out
-}
-
-func compress(data []byte) ([]byte, error) {
-	var b bytes.Buffer
-	w := zlib.NewWriter(&b)
-
-	if _, err := w.Write(data); err != nil {
-		return nil, err
-	}
-	if err := w.Close(); err != nil {
-		return nil, err
-	}
-
-	return b.Bytes(), nil
-}
-
-func wrap(data []byte) string {
-	var s strings.Builder
-	for i := 0; i < len(data); i += 52 {
-		c := 52
-		if c > len(data)-i {
-			c = len(data) - i
-		}
-		b := (c / 5) * 4
-
-		if b <= 26 {
-			s.WriteByte(byte('A' + b - 1))
-		} else {
-			s.WriteByte(byte('a' + b - 27))
-		}
-		s.Write(data[i : i+c])
-		s.WriteByte('\n')
-	}
-	return s.String()
-}
-
-func init() {
-	flag.StringVar(&mode, "mode", "parse", "operation mode, one of 'parse' or 'encode'")
-}
-
-func main() {
-	flag.Parse()
-
-	switch mode {
-	case "parse":
-		files, _, err := gitdiff.Parse(os.Stdin)
-		if err != nil {
-			log.Fatalf("failed to parse file: %v", err)
-		}
-		if len(files) != 1 {
-			log.Fatalf("patch contains more than one file: %d", len(files))
-		}
-		if files[0].BinaryFragment == nil {
-			log.Fatalf("patch file does not contain a binary fragment")
-		}
-		os.Stdout.Write(files[0].BinaryFragment.Data)
-
-	case "encode":
-		data, err := ioutil.ReadAll(os.Stdin)
-		if err != nil {
-			log.Fatalf("failed to read input: %v", err)
-		}
-		data, err = compress(data)
-		if err != nil {
-			log.Fatalf("failed to compress data: %v", err)
-		}
-		os.Stdout.WriteString(wrap(base85Encode(data)))
-
-	default:
-		log.Fatalf("unknown mode: %s", mode)
-	}
-}
pkg/gitdiff/testdata/apply/bin_fragment_delta_error.src
Binary file
pkg/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size.patch
@@ -1,5 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src
-GIT binary patch
-delta 18
-fc${itY+{<=z`_4AtEhVK$zKyatN;N30RR6$D+j^=
-
pkg/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_add.patch
@@ -1,5 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src
-GIT binary patch
-delta 11
-Xc${itY+{_?z`_4As|XMP0RR6K8UwQc
-
pkg/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_copy.patch
@@ -1,5 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src
-GIT binary patch
-delta 17
-fc${itY+{_?z`_4AtEhVK$zKya00961|Nl5!2ZsOv
-
pkg/gitdiff/testdata/apply/bin_fragment_delta_error_src_size.patch
@@ -1,5 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src
-GIT binary patch
-delta 18
-fc${itYGRz=z`_4AtEhVK$zKyatN;N30RR6$EeFB?
-
pkg/gitdiff/testdata/apply/bin_fragment_delta_modify.out
Binary file
pkg/gitdiff/testdata/apply/bin_fragment_delta_modify.patch
@@ -1,13 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_delta_modify.src b/gitdiff/testdata/apply/bin_fragment_delta_modify.src
-GIT binary patch
-delta 172
-zcmV;d08{^f2)qc8AP{I3VQ>J`s>wb0HU+h#6w8q?tUO~cHmDjZi2<8yZ9XmKhhMdo
-zWu(4bg|8QwzZ|1e*rL4P#)`Fen<n~ik=E?$qG6?hzJ6$u{l5W#?uwHb0q6w)00000
-zlLZ3%0RfW%1N%UMJ{~Z~0@X${&1Kk#98tb3==a{J7A;`O`v&<T@514_mvMTz72b#n
-atf$#NLoPbNe?RPFJVt1aCFGoQbiKD!OHgJ2
-
-delta 112
-zcmV-$0FVE?2!IHXAP~DY<7&llQfwqYA%tL<sR@xVtUMD;+4ZG>XTQ5=J2y;^BfB}4
-zWkisH791|vOVl5e-@^VLX0s~Ky_UyN!3;CgPr>Edj0j+0gOSwSsFsr$0q6zUJph<q
-SlLZ3%0XmZb1N#I__7UCuR5Dxu
-
pkg/gitdiff/testdata/apply/bin_fragment_delta_modify.src
Binary file
pkg/gitdiff/testdata/apply/bin_fragment_delta_modify_large.out
Binary file
pkg/gitdiff/testdata/apply/bin_fragment_delta_modify_large.patch
@@ -1,166 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_delta_modify_large.src b/gitdiff/testdata/apply/bin_fragment_delta_modify_large.src
-GIT binary patch
-delta 4145
-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
-
-delta 4145
-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
-
pkg/gitdiff/testdata/apply/bin_fragment_delta_modify_large.src
Binary file
pkg/gitdiff/testdata/apply/bin_fragment_literal_create.out
Binary file
pkg/gitdiff/testdata/apply/bin_fragment_literal_create.patch
@@ -1,8 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_literal_create.src b/gitdiff/testdata/apply/bin_fragment_literal_create.src
-GIT binary patch
-literal 32
-ocmZQzU`lR_IpTEv<rOIzfy0`|3>L*`JUiBtu5<US@-j_*0M$he#sB~S
-
-literal 0
-HcmV?d00001
-
pkg/gitdiff/testdata/apply/bin_fragment_literal_create.src
pkg/gitdiff/testdata/apply/bin_fragment_literal_modify.out
Binary file
pkg/gitdiff/testdata/apply/bin_fragment_literal_modify.patch
@@ -1,8 +0,0 @@
-diff --git a/gitdiff/testdata/apply/bin_fragment_literal_modify.src b/gitdiff/testdata/apply/bin_fragment_literal_modify.src
-GIT binary patch
-literal 32
-ocmZQzU`lR_IpTEv<rOIzfy0_p?@r&O@$6Vny3XCR%F8tM0pJ`CjQ{`u
-
-literal 32
-ocmZQzU`lR_IpTEv<rOIzfy0`|3>L*`JUiBtu5<US@-j_*0M$he#sB~S
-
pkg/gitdiff/testdata/apply/bin_fragment_literal_modify.src
Binary file
pkg/gitdiff/testdata/apply/file_bin_modify.out
Binary file
pkg/gitdiff/testdata/apply/file_bin_modify.patch
@@ -1,13 +0,0 @@
-diff --git a/gitdiff/testdata/apply/file_bin_modify.src b/gitdiff/testdata/apply/file_bin_modify.src
-GIT binary patch
-delta 172
-zcmV;d08{^f2)qc8AP{I3VQ>J`s>wb0HU+h#6w8q?tUO~cHmDjZi2<8yZ9XmKhhMdo
-zWu(4bg|8QwzZ|1e*rL4P#)`Fen<n~ik=E?$qG6?hzJ6$u{l5W#?uwHb0q6w)00000
-zlLZ3%0RfW%1N%UMJ{~Z~0@X${&1Kk#98tb3==a{J7A;`O`v&<T@514_mvMTz72b#n
-atf$#NLoPbNe?RPFJVt1aCFGoQbiKD!OHgJ2
-
-delta 112
-zcmV-$0FVE?2!IHXAP~DY<7&llQfwqYA%tL<sR@xVtUMD;+4ZG>XTQ5=J2y;^BfB}4
-zWkisH791|vOVl5e-@^VLX0s~Ky_UyN!3;CgPr>Edj0j+0gOSwSsFsr$0q6zUJph<q
-SlLZ3%0XmZb1N#I__7UCuR5Dxu
-
pkg/gitdiff/testdata/apply/file_bin_modify.src
Binary file
pkg/gitdiff/testdata/apply/file_mode_change.out
@@ -1,2 +0,0 @@
-#!/bin/bash
-echo "this file is executable"
pkg/gitdiff/testdata/apply/file_mode_change.patch
@@ -1,3 +0,0 @@
-diff --git a/gitdiff/testdata/apply/file_mode_change.src b/gitdiff/testdata/apply/file_mode_change.src
-old mode 100644
-new mode 100755
pkg/gitdiff/testdata/apply/file_mode_change.src
@@ -1,2 +0,0 @@
-#!/bin/bash
-echo "this file is executable"
pkg/gitdiff/testdata/apply/file_text.src
@@ -1,200 +0,0 @@
-this is line 1
-this is line 2
-this is line 3
-this is line 4
-this is line 5
-this is line 6
-this is line 7
-this is line 8
-this is line 9
-this is line 10
-this is line 11
-this is line 12
-this is line 13
-this is line 14
-this is line 15
-this is line 16
-this is line 17
-this is line 18
-this is line 19
-this is line 20
-this is line 21
-this is line 22
-this is line 23
-this is line 24
-this is line 25
-this is line 26
-this is line 27
-this is line 28
-this is line 29
-this is line 30
-this is line 31
-this is line 32
-this is line 33
-this is line 34
-this is line 35
-this is line 36
-this is line 37
-this is line 38
-this is line 39
-this is line 40
-this is line 41
-this is line 42
-this is line 43
-this is line 44
-this is line 45
-this is line 46
-this is line 47
-this is line 48
-this is line 49
-this is line 50
-this is line 51
-this is line 52
-this is line 53
-this is line 54
-this is line 55
-this is line 56
-this is line 57
-this is line 58
-this is line 59
-this is line 60
-this is line 61
-this is line 62
-this is line 63
-this is line 64
-this is line 65
-this is line 66
-this is line 67
-this is line 68
-this is line 69
-this is line 70
-this is line 71
-this is line 72
-this is line 73
-this is line 74
-this is line 75
-this is line 76
-this is line 77
-this is line 78
-this is line 79
-this is line 80
-this is line 81
-this is line 82
-this is line 83
-this is line 84
-this is line 85
-this is line 86
-this is line 87
-this is line 88
-this is line 89
-this is line 90
-this is line 91
-this is line 92
-this is line 93
-this is line 94
-this is line 95
-this is line 96
-this is line 97
-this is line 98
-this is line 99
-this is line 100
-this is line 101
-this is line 102
-this is line 103
-this is line 104
-this is line 105
-this is line 106
-this is line 107
-this is line 108
-this is line 109
-this is line 110
-this is line 111
-this is line 112
-this is line 113
-this is line 114
-this is line 115
-this is line 116
-this is line 117
-this is line 118
-this is line 119
-this is line 120
-this is line 121
-this is line 122
-this is line 123
-this is line 124
-this is line 125
-this is line 126
-this is line 127
-this is line 128
-this is line 129
-this is line 130
-this is line 131
-this is line 132
-this is line 133
-this is line 134
-this is line 135
-this is line 136
-this is line 137
-this is line 138
-this is line 139
-this is line 140
-this is line 141
-this is line 142
-this is line 143
-this is line 144
-this is line 145
-this is line 146
-this is line 147
-this is line 148
-this is line 149
-this is line 150
-this is line 151
-this is line 152
-this is line 153
-this is line 154
-this is line 155
-this is line 156
-this is line 157
-this is line 158
-this is line 159
-this is line 160
-this is line 161
-this is line 162
-this is line 163
-this is line 164
-this is line 165
-this is line 166
-this is line 167
-this is line 168
-this is line 169
-this is line 170
-this is line 171
-this is line 172
-this is line 173
-this is line 174
-this is line 175
-this is line 176
-this is line 177
-this is line 178
-this is line 179
-this is line 180
-this is line 181
-this is line 182
-this is line 183
-this is line 184
-this is line 185
-this is line 186
-this is line 187
-this is line 188
-this is line 189
-this is line 190
-this is line 191
-this is line 192
-this is line 193
-this is line 194
-this is line 195
-this is line 196
-this is line 197
-this is line 198
-this is line 199
-this is line 200
pkg/gitdiff/testdata/apply/file_text_delete.out
pkg/gitdiff/testdata/apply/file_text_delete.patch
@@ -1,206 +0,0 @@
-diff --git a/gitdiff/testdata/apply/file_text.src.src b/gitdiff/testdata/apply/file_text.src
-deleted file mode 100644
-index 3805ad4..0000000
---- a/gitdiff/testdata/apply/file_text.src.src
-+++ /dev/null
-@@ -1,200 +0,0 @@
--this is line 1
--this is line 2
--this is line 3
--this is line 4
--this is line 5
--this is line 6
--this is line 7
--this is line 8
--this is line 9
--this is line 10
--this is line 11
--this is line 12
--this is line 13
--this is line 14
--this is line 15
--this is line 16
--this is line 17
--this is line 18
--this is line 19
--this is line 20
--this is line 21
--this is line 22
--this is line 23
--this is line 24
--this is line 25
--this is line 26
--this is line 27
--this is line 28
--this is line 29
--this is line 30
--this is line 31
--this is line 32
--this is line 33
--this is line 34
--this is line 35
--this is line 36
--this is line 37
--this is line 38
--this is line 39
--this is line 40
--this is line 41
--this is line 42
--this is line 43
--this is line 44
--this is line 45
--this is line 46
--this is line 47
--this is line 48
--this is line 49
--this is line 50
--this is line 51
--this is line 52
--this is line 53
--this is line 54
--this is line 55
--this is line 56
--this is line 57
--this is line 58
--this is line 59
--this is line 60
--this is line 61
--this is line 62
--this is line 63
--this is line 64
--this is line 65
--this is line 66
--this is line 67
--this is line 68
--this is line 69
--this is line 70
--this is line 71
--this is line 72
--this is line 73
--this is line 74
--this is line 75
--this is line 76
--this is line 77
--this is line 78
--this is line 79
--this is line 80
--this is line 81
--this is line 82
--this is line 83
--this is line 84
--this is line 85
--this is line 86
--this is line 87
--this is line 88
--this is line 89
--this is line 90
--this is line 91
--this is line 92
--this is line 93
--this is line 94
--this is line 95
--this is line 96
--this is line 97
--this is line 98
--this is line 99
--this is line 100
--this is line 101
--this is line 102
--this is line 103
--this is line 104
--this is line 105
--this is line 106
--this is line 107
--this is line 108
--this is line 109
--this is line 110
--this is line 111
--this is line 112
--this is line 113
--this is line 114
--this is line 115
--this is line 116
--this is line 117
--this is line 118
--this is line 119
--this is line 120
--this is line 121
--this is line 122
--this is line 123
--this is line 124
--this is line 125
--this is line 126
--this is line 127
--this is line 128
--this is line 129
--this is line 130
--this is line 131
--this is line 132
--this is line 133
--this is line 134
--this is line 135
--this is line 136
--this is line 137
--this is line 138
--this is line 139
--this is line 140
--this is line 141
--this is line 142
--this is line 143
--this is line 144
--this is line 145
--this is line 146
--this is line 147
--this is line 148
--this is line 149
--this is line 150
--this is line 151
--this is line 152
--this is line 153
--this is line 154
--this is line 155
--this is line 156
--this is line 157
--this is line 158
--this is line 159
--this is line 160
--this is line 161
--this is line 162
--this is line 163
--this is line 164
--this is line 165
--this is line 166
--this is line 167
--this is line 168
--this is line 169
--this is line 170
--this is line 171
--this is line 172
--this is line 173
--this is line 174
--this is line 175
--this is line 176
--this is line 177
--this is line 178
--this is line 179
--this is line 180
--this is line 181
--this is line 182
--this is line 183
--this is line 184
--this is line 185
--this is line 186
--this is line 187
--this is line 188
--this is line 189
--this is line 190
--this is line 191
--this is line 192
--this is line 193
--this is line 194
--this is line 195
--this is line 196
--this is line 197
--this is line 198
--this is line 199
--this is line 200
pkg/gitdiff/testdata/apply/file_text_error_partial_delete.patch
@@ -1,106 +0,0 @@
-diff --git a/gitdiff/testdata/apply/file_text.src.src b/gitdiff/testdata/apply/file_text.src
-deleted file mode 100644
-index 3805ad4..0000000
---- a/gitdiff/testdata/apply/file_text.src.src
-+++ /dev/null
-@@ -1,100 +0,0 @@
--this is line 1
--this is line 2
--this is line 3
--this is line 4
--this is line 5
--this is line 6
--this is line 7
--this is line 8
--this is line 9
--this is line 10
--this is line 11
--this is line 12
--this is line 13
--this is line 14
--this is line 15
--this is line 16
--this is line 17
--this is line 18
--this is line 19
--this is line 20
--this is line 21
--this is line 22
--this is line 23
--this is line 24
--this is line 25
--this is line 26
--this is line 27
--this is line 28
--this is line 29
--this is line 30
--this is line 31
--this is line 32
--this is line 33
--this is line 34
--this is line 35
--this is line 36
--this is line 37
--this is line 38
--this is line 39
--this is line 40
--this is line 41
--this is line 42
--this is line 43
--this is line 44
--this is line 45
--this is line 46
--this is line 47
--this is line 48
--this is line 49
--this is line 50
--this is line 51
--this is line 52
--this is line 53
--this is line 54
--this is line 55
--this is line 56
--this is line 57
--this is line 58
--this is line 59
--this is line 60
--this is line 61
--this is line 62
--this is line 63
--this is line 64
--this is line 65
--this is line 66
--this is line 67
--this is line 68
--this is line 69
--this is line 70
--this is line 71
--this is line 72
--this is line 73
--this is line 74
--this is line 75
--this is line 76
--this is line 77
--this is line 78
--this is line 79
--this is line 80
--this is line 81
--this is line 82
--this is line 83
--this is line 84
--this is line 85
--this is line 86
--this is line 87
--this is line 88
--this is line 89
--this is line 90
--this is line 91
--this is line 92
--this is line 93
--this is line 94
--this is line 95
--this is line 96
--this is line 97
--this is line 98
--this is line 99
--this is line 100
pkg/gitdiff/testdata/apply/file_text_modify.out
@@ -1,195 +0,0 @@
-the first line is different
-this is line 2
-this is line 3
-this is line 4
-this is line 5
-this is line 6
-this is line 7
-this is line 8
-this is line 9
-this is line 10
-this is line 11
-this is line 12
-this is line 13
-this is line 14
-this is line 15
-this is line 16
-this is line 17
-this is line 18
-this is line 19
-this line offsets all the line numbers!
-this is line 20
-this is line 21
-until here, now we're back on track!
-this is line 24
-this is line 25
-this is line 26
-this is line 27
-this is line 28
-this is line 29
-this is line 30
-this is line 31
-this is line 32
-this is line 33
-this is line 34
-this is line 35
-this is line 36
-this is line 37
-this is line 38
-this is line 39
-this is line 40
-this is line 41
-this is line 42
-this is line 43
-this is line 44
-this is line 45
-this is line 46
-this is line 47
-this is line 48
-this is line 49
-this is line 50
-this is line 51
-this is line 52
-this is line 53
-this is line 54
-this is line 55
-once upon a time, a line
-  in a text
-    file
-  changed
-this is line 60
-this is line 61
-this is line 62
-this is line 63
-this is line 64
-this is line 65
-this is line 66
-this is line 67
-this is line 68
-this is line 69
-this is line 70
-this is line 71
-this is line 72
-this is line 73
-this is line 74
-this is line 75
-this is line 76
-this is line 77
-this is line 78
-this is line 79
-this is line 80
-this is line 81
-this is line 82
-this is line 83
-this is line 84
-this is line 85
-this is line 86
-this is line 87
-this is line 88
-this is line 89
-this is line 90
-this is line 91
-this is line 92
-this is line 93
-this is line 94
-this is line 95
-this is line 96
-this is line 97
-this is line 98
-this is line 99
-this is line 100
-this is line 101
-this is line 102
-this is line 103
-this is line 104
-this is line 105
-this is line 106
-this is line 107
-this is line 108
-this is line 109
-this is line 110
-this is line 111
-this is line 112
-this is line 113
-this is line 114
-this is line 115
-this is line 116
-this is line 117
-this is line 118
-this is line 119
-this is line 120
-this is line 121
-this is line 122
-this is line 123
-this is line 124
-this is line 125
-this is line 126
-this is line 127
-this is line 128
-this is line 129
-this is line 130
-this is line 131
-this is line 132
-this line was bad and has been removed
-this line was REDACTED and has been REDACTED
-this is line 135
-this is line 136
-this is line 137
-this is line 138
-this is line 139
-this is line 140
-this is line 141
-this is line 142
-this is line 143
-this is line 144
-this is line 145
-this is line 146
-this is line 147
-this is line 148
-this is line 149
-this is line 150
-this is line 151
-this is line 152
-this is line 153
-this is line 154
-this is line 155
-this is line 156
-this is line 157
-this is line 158
-this is line 159
-this is line 160
-this is line 161
-this is line 162
-this is line 163
-the number on the remaining lines is 5 ahead of their actual position in the file
-this is line 170
-this is line 171
-this is line 172
-this is line 173
-this is line 174
-this is line 175
-this is line 176
-this is line 177
-this is line 178
-this is line 179
-this is line 180
-this is line 181
-this is line 182
-this is line 183
-this is line 184
-this is line 185
-this is line 186
-this is line 187
-this is line 188
-this is line 189
-this is line 190
-this is line 191
-this is line 192
-this is line 193
-this is line 194
-this is line 195
-this is line 196
-this is line 197
-this is line 198
-this is line 199
-this is line 200
pkg/gitdiff/testdata/apply/file_text_modify.patch
@@ -1,62 +0,0 @@
-diff --git a/gitdiff/testdata/apply/file_text.src b/gitdiff/testdata/apply/file_text.src
---- a/gitdiff/testdata/apply/file_text.src
-+++ b/gitdiff/testdata/apply/file_text.src
-@@ -1,4 +1,4 @@
--this is line 1
-+the first line is different
- this is line 2
- this is line 3
- this is line 4
-@@ -17,10 +17,10 @@ this is line 16
- this is line 17
- this is line 18
- this is line 19
-+this line offsets all the line numbers!
- this is line 20
- this is line 21
--this is line 22
--this is line 23
-+until here, now we're back on track!
- this is line 24
- this is line 25
- this is line 26
-@@ -53,10 +53,10 @@ this is line 52
- this is line 53
- this is line 54
- this is line 55
--this is line 56
--this is line 57
--this is line 58
--this is line 59
-+once upon a time, a line
-+  in a text
-+    file
-+  changed
- this is line 60
- this is line 61
- this is line 62
-@@ -130,8 +130,8 @@ this is line 129
- this is line 130
- this is line 131
- this is line 132
--this is line 133
--this is line 134
-+this line was bad and has been removed
-+this line was REDACTED and has been REDACTED
- this is line 135
- this is line 136
- this is line 137
-@@ -161,12 +161,7 @@ this is line 160
- this is line 161
- this is line 162
- this is line 163
--this is line 164
--this is line 165
--this is line 166
--this is line 167
--this is line 168
--this is line 169
-+the number on the remaining lines is 5 ahead of their actual position in the file
- this is line 170
- this is line 171
- this is line 172
pkg/gitdiff/testdata/apply/text_fragment_add_end.out
@@ -1,5 +0,0 @@
-line 1
-line 2
-line 3
-new line a
-new line b
pkg/gitdiff/testdata/apply/text_fragment_add_end.patch
@@ -1,9 +0,0 @@
-diff --git a/gitdiff/testdata/apply/fragment_add_end.src b/gitdiff/testdata/apply/fragment_add_end.src
---- a/gitdiff/testdata/apply/fragment_add_end.src
-+++ b/gitdiff/testdata/apply/fragment_add_end.src
-@@ -1,3 +1,5 @@
- line 1
- line 2
- line 3
-+new line a
-+new line b
pkg/gitdiff/testdata/apply/text_fragment_add_end.src
@@ -1,3 +0,0 @@
-line 1
-line 2
-line 3
pkg/gitdiff/testdata/apply/text_fragment_add_end_noeol.out
@@ -1,5 +0,0 @@
-line 1
-line 2
-line 3
-line 4
-line 5
pkg/gitdiff/testdata/apply/text_fragment_add_end_noeol.patch
@@ -1,11 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_add_end_noeol.src b/gitdiff/testdata/apply/text_fragment_add_end_noeol.src
---- a/gitdiff/testdata/apply/text_fragment_add_end_noeol.src
-+++ b/gitdiff/testdata/apply/text_fragment_add_end_noeol.src
-@@ -1,3 +1,5 @@
- line 1
- line 2
--line 3
-\ No newline at end of file
-+line 3
-+line 4
-+line 5
pkg/gitdiff/testdata/apply/text_fragment_add_end_noeol.src
@@ -1,3 +0,0 @@
-line 1
-line 2
-line 3
\ No newline at end of file
pkg/gitdiff/testdata/apply/text_fragment_add_middle.out
@@ -1,5 +0,0 @@
-line 1
-line 2
-new line a
-new line b
-line 3
pkg/gitdiff/testdata/apply/text_fragment_add_middle.patch
@@ -1,9 +0,0 @@
-diff --git a/gitdiff/testdata/apply/fragment_add_middle.src b/gitdiff/testdata/apply/fragment_add_middle.src
---- a/gitdiff/testdata/apply/fragment_add_middle.src
-+++ b/gitdiff/testdata/apply/fragment_add_middle.src
-@@ -1,3 +1,5 @@
- line 1
- line 2
-+new line a
-+new line b
- line 3
pkg/gitdiff/testdata/apply/text_fragment_add_middle.src
@@ -1,3 +0,0 @@
-line 1
-line 2
-line 3
pkg/gitdiff/testdata/apply/text_fragment_add_start.out
@@ -1,4 +0,0 @@
-new line a
-line 1
-line 2
-line 3
pkg/gitdiff/testdata/apply/text_fragment_add_start.patch
@@ -1,8 +0,0 @@
-diff --git a/gitdiff/testdata/apply/fragment_add_start.src b/gitdiff/testdata/apply/fragment_add_start.src
---- a/gitdiff/testdata/apply/fragment_add_start.src
-+++ b/gitdiff/testdata/apply/fragment_add_start.src
-@@ -1,3 +1,4 @@
-+new line a
- line 1
- line 2
- line 3
pkg/gitdiff/testdata/apply/text_fragment_add_start.src
@@ -1,3 +0,0 @@
-line 1
-line 2
-line 3
pkg/gitdiff/testdata/apply/text_fragment_change_end.out
@@ -1,10 +0,0 @@
-line 1
-line 2
-line 3
-line 4
-line 5
-line 6
-line 7
-line 8
-line 9
-new line a
pkg/gitdiff/testdata/apply/text_fragment_change_end.patch
@@ -1,9 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_change_end.src b/gitdiff/testdata/apply/text_fragment_change_end.src
---- a/gitdiff/testdata/apply/text_fragment_change_end.src
-+++ b/gitdiff/testdata/apply/text_fragment_change_end.src
-@@ -7,4 +7,4 @@ line 6
- line 7
- line 8
- line 9
--line 10
-+new line a
pkg/gitdiff/testdata/apply/text_fragment_change_end.src
@@ -1,10 +0,0 @@
-line 1
-line 2
-line 3
-line 4
-line 5
-line 6
-line 7
-line 8
-line 9
-line 10
pkg/gitdiff/testdata/apply/text_fragment_change_end_eol.out
@@ -1,3 +0,0 @@
-line 1
-line 2
-line 3
\ No newline at end of file
pkg/gitdiff/testdata/apply/text_fragment_change_end_eol.patch
@@ -1,10 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_remove_last_eol.src b/gitdiff/testdata/apply/text_fragment_remove_last_eol.src
-index a92d664..8cf2f17 100644
---- a/gitdiff/testdata/apply/text_fragment_remove_last_eol.src
-+++ b/gitdiff/testdata/apply/text_fragment_remove_last_eol.src
-@@ -1,3 +1,3 @@
- line 1
- line 2
--line 3
-+line 3
-\ No newline at end of file
pkg/gitdiff/testdata/apply/text_fragment_change_end_eol.src
@@ -1,3 +0,0 @@
-line 1
-line 2
-line 3
pkg/gitdiff/testdata/apply/text_fragment_change_exact.out
@@ -1,19 +0,0 @@
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-new line a
-line
-line
-line
pkg/gitdiff/testdata/apply/text_fragment_change_exact.patch
@@ -1,12 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_change_exact.src b/gitdiff/testdata/apply/text_fragment_change_exact.src
---- a/gitdiff/testdata/apply/text_fragment_change_exact.src
-+++ b/gitdiff/testdata/apply/text_fragment_change_exact.src
-@@ -13,7 +13,7 @@ line
- line
- line
- line
--line
-+new line a
- line
- line
- line
pkg/gitdiff/testdata/apply/text_fragment_change_exact.src
@@ -1,30 +0,0 @@
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
-line
pkg/gitdiff/testdata/apply/text_fragment_change_middle.out
@@ -1,9 +0,0 @@
-line 1
-line 2
-line 3
-line 4
-line 5
-new line a
-line 7
-line 8
-line 9
pkg/gitdiff/testdata/apply/text_fragment_change_middle.patch
@@ -1,12 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_change_middle.src b/gitdiff/testdata/apply/text_fragment_change_middle.src
---- a/gitdiff/testdata/apply/text_fragment_change_middle.src
-+++ b/gitdiff/testdata/apply/text_fragment_change_middle.src
-@@ -3,7 +3,7 @@ line 2
- line 3
- line 4
- line 5
--line 6
-+new line a
- line 7
- line 8
- line 9
pkg/gitdiff/testdata/apply/text_fragment_change_middle.src
@@ -1,10 +0,0 @@
-line 1
-line 2
-line 3
-line 4
-line 5
-line 6
-line 7
-line 8
-line 9
-line 10
pkg/gitdiff/testdata/apply/text_fragment_change_single_noeol.out
@@ -1,1 +0,0 @@
-new line a
\ No newline at end of file
pkg/gitdiff/testdata/apply/text_fragment_change_single_noeol.patch
@@ -1,8 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_change_single_noeol.src b/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
---- a/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
-+++ b/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
-@@ -1 +1 @@
--line 1
-\ No newline at end of file
-+new line a
-\ No newline at end of file
pkg/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
@@ -1,1 +0,0 @@
-line 1
\ No newline at end of file
pkg/gitdiff/testdata/apply/text_fragment_change_start.out
@@ -1,4 +0,0 @@
-new line a
-line 2
-line 3
-line 4
pkg/gitdiff/testdata/apply/text_fragment_change_start.patch
@@ -1,9 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_change_start.src b/gitdiff/testdata/apply/text_fragment_change_start.src
---- a/gitdiff/testdata/apply/text_fragment_change_start.src
-+++ b/gitdiff/testdata/apply/text_fragment_change_start.src
-@@ -1,4 +1,4 @@
--line 1
-+new line a
- line 2
- line 3
- line 4
pkg/gitdiff/testdata/apply/text_fragment_change_start.src
@@ -1,10 +0,0 @@
-line 1
-line 2
-line 3
-line 4
-line 5
-line 6
-line 7
-line 8
-line 9
-line 10
pkg/gitdiff/testdata/apply/text_fragment_delete_all.out
pkg/gitdiff/testdata/apply/text_fragment_delete_all.patch
@@ -1,8 +0,0 @@
-diff --git a/gitdiff/testdata/apply/fragment_delete_all.src b/gitdiff/testdata/apply/fragment_delete_all.src
---- a/gitdiff/testdata/apply/fragment_delete_all.src
-+++ b/gitdiff/testdata/apply/fragment_delete_all.src
-@@ -1,4 +0,0 @@
--line a
--line b
--line c
--line d
pkg/gitdiff/testdata/apply/text_fragment_delete_all.src
@@ -1,4 +0,0 @@
-line a
-line b
-line c
-line d
pkg/gitdiff/testdata/apply/text_fragment_error.src
@@ -1,13 +0,0 @@
-line 1
-line 2
-line 3
-line 4
-line 5
-line 6
-line 7
-line 8
-line 9
-line 10
-line 11
-line 12
-line 13
pkg/gitdiff/testdata/apply/text_fragment_error_context_conflict.patch
@@ -1,12 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_error.src b/gitdiff/testdata/apply/text_fragment_error.src
---- a/gitdiff/testdata/apply/text_fragment_error.src
-+++ b/gitdiff/testdata/apply/text_fragment_error.src
-@@ -4,7 +4,7 @@ line 3
- line 4
- line 5
- line conflict
--line 7
-+new line a
- line 8
- line 9
- line 10
pkg/gitdiff/testdata/apply/text_fragment_error_delete_conflict.patch
@@ -1,12 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_error.src b/gitdiff/testdata/apply/text_fragment_error.src
---- a/gitdiff/testdata/apply/text_fragment_error.src
-+++ b/gitdiff/testdata/apply/text_fragment_error.src
-@@ -4,7 +4,7 @@ line 3
- line 4
- line 5
- line 6
--line conflict
-+new line a
- line 8
- line 9
- line 10
pkg/gitdiff/testdata/apply/text_fragment_error_new_file.patch
@@ -1,7 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_error.src b/gitdiff/testdata/apply/text_fragment_error.src
---- a/gitdiff/testdata/apply/text_fragment_error.src
-+++ b/gitdiff/testdata/apply/text_fragment_error.src
-@@ -0,0 +1,3 @@
-+line 1
-+line 2
-+line 3
pkg/gitdiff/testdata/apply/text_fragment_error_short_src.patch
@@ -1,12 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_error.src b/gitdiff/testdata/apply/text_fragment_error.src
---- a/gitdiff/testdata/apply/text_fragment_error.src
-+++ b/gitdiff/testdata/apply/text_fragment_error.src
-@@ -9,7 +9,7 @@ line 8
- line 9
- line 10
- line 11
--line 12
-+new line a
- line 13
- line 14
- line 15
pkg/gitdiff/testdata/apply/text_fragment_error_short_src_before.patch
@@ -1,12 +0,0 @@
-diff --git a/gitdiff/testdata/apply/text_fragment_error.src b/gitdiff/testdata/apply/text_fragment_error.src
---- a/gitdiff/testdata/apply/text_fragment_error.src
-+++ b/gitdiff/testdata/apply/text_fragment_error.src
-@@ -15,7 +15,7 @@ line 14
- line 15
- line 16
- line 17
--line 18
-+new line a
- line 19
- line 20
- line 21
pkg/gitdiff/testdata/apply/text_fragment_new.out
@@ -1,3 +0,0 @@
-line 1
-line 2
-line 3
pkg/gitdiff/testdata/apply/text_fragment_new.patch
@@ -1,7 +0,0 @@
-diff --git a/gitdiff/testdata/apply/fragment_new.src b/gitdiff/testdata/apply/fragment_new.src
---- a/gitdiff/testdata/apply/fragment_new.src
-+++ b/gitdiff/testdata/apply/fragment_new.src
-@@ -0,0 +1,3 @@
-+line 1
-+line 2
-+line 3
pkg/gitdiff/testdata/apply/text_fragment_new.src
pkg/gitdiff/testdata/string/binary_modify.patch
@@ -1,9 +0,0 @@
-diff --git a/file.bin b/file.bin
-index a7f4d5d6975ec021016c02b6d58345ebf434f38c..bdc9a70f055892146612dcdb413f0e339faaa0df 100644
-GIT binary patch
-delta 66
-QcmeZhVVvM$!$1K50C&Ox;s5{u
-
-delta 5
-McmZo+^qAlQ00i9urT_o{
-
pkg/gitdiff/testdata/string/binary_modify_nodata.patch
@@ -1,3 +0,0 @@
-diff --git a/file.bin b/file.bin
-index a7f4d5d..bdc9a70 100644
-Binary files a/file.bin and b/file.bin differ
pkg/gitdiff/testdata/string/binary_new.patch
@@ -1,11 +0,0 @@
-diff --git a/file.bin b/file.bin
-new file mode 100644
-index 0000000000000000000000000000000000000000..a7f4d5d6975ec021016c02b6d58345ebf434f38c
-GIT binary patch
-literal 72
-zcmV-O0Jr~td-`u6JcK&{KDK=<a#;v1^LR5&K)zQ0=Goz82(?nJ6_nD`f#8O9p}}{P
-eiXim+rDI+BDadMQmMsO5Sw@;DbrCA+PamP;Ng_@F
-
-literal 0
-HcmV?d00001
-
pkg/gitdiff/testdata/string/copy.patch
@@ -1,4 +0,0 @@
-diff --git a/file.txt b/numbers.txt
-similarity index 100%
-copy from file.txt
-copy to numbers.txt
pkg/gitdiff/testdata/string/copy_modify.patch
@@ -1,21 +0,0 @@
-diff --git a/file.txt b/numbers.txt
-similarity index 57%
-copy from file.txt
-copy to numbers.txt
-index c9e9e05..6c4a3e0 100644
---- a/file.txt
-+++ b/numbers.txt
-@@ -1,6 +1,6 @@
- one
- two
--three
-+three three three
- four
- five
- six
-@@ -8,3 +8,5 @@ seven
- eight
- nine
- ten
-+eleven
-+twelve
pkg/gitdiff/testdata/string/delete.patch
@@ -1,16 +0,0 @@
-diff --git a/file.txt b/file.txt
-deleted file mode 100644
-index c9e9e05..0000000
---- a/file.txt
-+++ /dev/null
-@@ -1,10 +0,0 @@
--one
--two
--three
--four
--five
--six
--seven
--eight
--nine
--ten
pkg/gitdiff/testdata/string/mode.patch
@@ -1,3 +0,0 @@
-diff --git a/file.txt b/file.txt
-old mode 100644
-new mode 100755
pkg/gitdiff/testdata/string/mode_modify.patch
@@ -1,10 +0,0 @@
-diff --git a/script.sh b/script.sh
-old mode 100644
-new mode 100755
-index 7a870bd..68d501e
---- a/script.sh
-+++ b/script.sh
-@@ -1,2 +1,2 @@
- #!/bin/bash
--echo "Hello World"
-+echo "Hello, World!"
pkg/gitdiff/testdata/string/modify.patch
@@ -1,16 +0,0 @@
-diff --git a/file.txt b/file.txt
-index c9e9e05..7d5fdc6 100644
---- a/file.txt
-+++ b/file.txt
-@@ -3,8 +3,10 @@ two
- three
- four
- five
--six
-+six six six six six six
- seven
- eight
- nine
- ten
-+eleven
-+twelve
pkg/gitdiff/testdata/string/new.patch
@@ -1,16 +0,0 @@
-diff --git a/file.txt b/file.txt
-new file mode 100644
-index 0000000..c9e9e05
---- /dev/null
-+++ b/file.txt
-@@ -0,0 +1,10 @@
-+one
-+two
-+three
-+four
-+five
-+six
-+seven
-+eight
-+nine
-+ten
pkg/gitdiff/testdata/string/new_empty.patch
@@ -1,3 +0,0 @@
-diff --git a/file.txt b/file.txt
-new file mode 100644
-index 0000000..e69de29
pkg/gitdiff/testdata/string/new_mode.patch
@@ -1,16 +0,0 @@
-diff --git a/file.sh b/file.sh
-new file mode 100755
-index 0000000..c9e9e05
---- /dev/null
-+++ b/file.sh
-@@ -0,0 +1,10 @@
-+one
-+two
-+three
-+four
-+five
-+six
-+seven
-+eight
-+nine
-+ten
pkg/gitdiff/testdata/string/rename.patch
@@ -1,4 +0,0 @@
-diff --git a/file.txt b/numbers.txt
-similarity index 100%
-rename from file.txt
-rename to numbers.txt
pkg/gitdiff/testdata/string/rename_modify.patch
@@ -1,18 +0,0 @@
-diff --git a/file.txt b/numbers.txt
-similarity index 77%
-rename from file.txt
-rename to numbers.txt
-index c9e9e05..a6b31d6 100644
---- a/file.txt
-+++ b/numbers.txt
-@@ -3,8 +3,9 @@ two
- three
- four
- five
--six
-+  six
- seven
- eight
- nine
- ten
-+eleven
pkg/gitdiff/testdata/new_binary_file.patch
@@ -1,16 +0,0 @@
-commit 5d9790fec7d95aa223f3d20936340bf55ff3dcbe
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Tue Apr 2 22:55:40 2019 -0700
-
-    A binary file with the first 10 fibonacci numbers.
-
-diff --git a/dir/ten.bin b/dir/ten.bin
-new file mode 100644
-index 0000000000000000000000000000000000000000..77b068ba48c356156944ea714740d0d5ca07bfec
-GIT binary patch
-literal 40
-gcmZQzU|?i`U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
-
-literal 0
-HcmV?d00001
-
pkg/gitdiff/testdata/no_files.patch
@@ -1,8 +0,0 @@
-commit 5d9790fec7d95aa223f3d20936340bf55ff3dcbe
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Tue Apr 2 22:55:40 2019 -0700
-
-    A file with multiple fragments.
-
-    The content is arbitrary.
-
pkg/gitdiff/testdata/one_file.patch
@@ -1,28 +0,0 @@
-commit 5d9790fec7d95aa223f3d20936340bf55ff3dcbe
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Tue Apr 2 22:55:40 2019 -0700
-
-    A file with multiple fragments.
-
-    The content is arbitrary.
-
-diff --git a/dir/file1.txt b/dir/file1.txt
-index ebe9fa54..fe103e1d 100644
---- a/dir/file1.txt
-+++ b/dir/file1.txt
-@@ -3,6 +3,8 @@ fragment 1
- context line
--old line 1
--old line 2
- context line
-+new line 1
-+new line 2
-+new line 3
- context line
--old line 3
-+new line 4
-+new line 5
-@@ -31,2 +33,2 @@ fragment 2
- context line
--old line 4
-+new line 6
pkg/gitdiff/testdata/two_files.patch
@@ -1,48 +0,0 @@
-commit 5d9790fec7d95aa223f3d20936340bf55ff3dcbe
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Tue Apr 2 22:55:40 2019 -0700
-
-    A file with multiple fragments.
-
-    The content is arbitrary.
-
-diff --git a/dir/file1.txt b/dir/file1.txt
-index ebe9fa54..fe103e1d 100644
---- a/dir/file1.txt
-+++ b/dir/file1.txt
-@@ -3,6 +3,8 @@ fragment 1
- context line
--old line 1
--old line 2
- context line
-+new line 1
-+new line 2
-+new line 3
- context line
--old line 3
-+new line 4
-+new line 5
-@@ -31,2 +33,2 @@ fragment 2
- context line
--old line 4
-+new line 6
-diff --git a/dir/file2.txt b/dir/file2.txt
-index 417ebc70..67514b7f 100644
---- a/dir/file2.txt
-+++ b/dir/file2.txt
-@@ -3,6 +3,8 @@ fragment 1
- context line
--old line 1
--old line 2
- context line
-+new line 1
-+new line 2
-+new line 3
- context line
--old line 3
-+new line 4
-+new line 5
-@@ -31,2 +33,2 @@ fragment 2
- context line
--old line 4
-+new line 6
pkg/gitdiff/apply_test.go
@@ -1,235 +0,0 @@
-package gitdiff
-
-import (
-	"bytes"
-	"errors"
-	"io"
-	"io/ioutil"
-	"path/filepath"
-	"testing"
-)
-
-func TestApplyTextFragment(t *testing.T) {
-	tests := map[string]applyTest{
-		"createFile": {Files: getApplyFiles("text_fragment_new")},
-		"deleteFile": {Files: getApplyFiles("text_fragment_delete_all")},
-
-		"addStart":    {Files: getApplyFiles("text_fragment_add_start")},
-		"addMiddle":   {Files: getApplyFiles("text_fragment_add_middle")},
-		"addEnd":      {Files: getApplyFiles("text_fragment_add_end")},
-		"addEndNoEOL": {Files: getApplyFiles("text_fragment_add_end_noeol")},
-
-		"changeStart":       {Files: getApplyFiles("text_fragment_change_start")},
-		"changeMiddle":      {Files: getApplyFiles("text_fragment_change_middle")},
-		"changeEnd":         {Files: getApplyFiles("text_fragment_change_end")},
-		"changeEndEOL":      {Files: getApplyFiles("text_fragment_change_end_eol")},
-		"changeExact":       {Files: getApplyFiles("text_fragment_change_exact")},
-		"changeSingleNoEOL": {Files: getApplyFiles("text_fragment_change_single_noeol")},
-
-		"errorShortSrcBefore": {
-			Files: applyFiles{
-				Src:   "text_fragment_error.src",
-				Patch: "text_fragment_error_short_src_before.patch",
-			},
-			Err: &Conflict{},
-		},
-		"errorShortSrc": {
-			Files: applyFiles{
-				Src:   "text_fragment_error.src",
-				Patch: "text_fragment_error_short_src.patch",
-			},
-			Err: &Conflict{},
-		},
-		"errorContextConflict": {
-			Files: applyFiles{
-				Src:   "text_fragment_error.src",
-				Patch: "text_fragment_error_context_conflict.patch",
-			},
-			Err: &Conflict{},
-		},
-		"errorDeleteConflict": {
-			Files: applyFiles{
-				Src:   "text_fragment_error.src",
-				Patch: "text_fragment_error_delete_conflict.patch",
-			},
-			Err: &Conflict{},
-		},
-		"errorNewFile": {
-			Files: applyFiles{
-				Src:   "text_fragment_error.src",
-				Patch: "text_fragment_error_new_file.patch",
-			},
-			Err: &Conflict{},
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
-				if len(file.TextFragments) != 1 {
-					t.Fatalf("patch should contain exactly one fragment, but it has %d", len(file.TextFragments))
-				}
-				applier := NewTextApplier(dst, src)
-				return applier.ApplyFragment(file.TextFragments[0])
-			})
-		})
-	}
-}
-
-func TestApplyBinaryFragment(t *testing.T) {
-	tests := map[string]applyTest{
-		"literalCreate":    {Files: getApplyFiles("bin_fragment_literal_create")},
-		"literalModify":    {Files: getApplyFiles("bin_fragment_literal_modify")},
-		"deltaModify":      {Files: getApplyFiles("bin_fragment_delta_modify")},
-		"deltaModifyLarge": {Files: getApplyFiles("bin_fragment_delta_modify_large")},
-
-		"errorIncompleteAdd": {
-			Files: applyFiles{
-				Src:   "bin_fragment_delta_error.src",
-				Patch: "bin_fragment_delta_error_incomplete_add.patch",
-			},
-			Err: "incomplete add",
-		},
-		"errorIncompleteCopy": {
-			Files: applyFiles{
-				Src:   "bin_fragment_delta_error.src",
-				Patch: "bin_fragment_delta_error_incomplete_copy.patch",
-			},
-			Err: "incomplete copy",
-		},
-		"errorSrcSize": {
-			Files: applyFiles{
-				Src:   "bin_fragment_delta_error.src",
-				Patch: "bin_fragment_delta_error_src_size.patch",
-			},
-			Err: &Conflict{},
-		},
-		"errorDstSize": {
-			Files: applyFiles{
-				Src:   "bin_fragment_delta_error.src",
-				Patch: "bin_fragment_delta_error_dst_size.patch",
-			},
-			Err: "insufficient or extra data",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
-				applier := NewBinaryApplier(dst, src)
-				return applier.ApplyFragment(file.BinaryFragment)
-			})
-		})
-	}
-}
-
-func TestApplyFile(t *testing.T) {
-	tests := map[string]applyTest{
-		"textModify": {
-			Files: applyFiles{
-				Src:   "file_text.src",
-				Patch: "file_text_modify.patch",
-				Out:   "file_text_modify.out",
-			},
-		},
-		"textDelete": {
-			Files: applyFiles{
-				Src:   "file_text.src",
-				Patch: "file_text_delete.patch",
-				Out:   "file_text_delete.out",
-			},
-		},
-		"textErrorPartialDelete": {
-			Files: applyFiles{
-				Src:   "file_text.src",
-				Patch: "file_text_error_partial_delete.patch",
-			},
-			Err: &Conflict{},
-		},
-		"binaryModify": {
-			Files: getApplyFiles("file_bin_modify"),
-		},
-		"modeChange": {
-			Files: getApplyFiles("file_mode_change"),
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
-				return Apply(dst, src, file)
-			})
-		})
-	}
-}
-
-type applyTest struct {
-	Files applyFiles
-	Err   interface{}
-}
-
-func (at applyTest) run(t *testing.T, apply func(io.Writer, io.ReaderAt, *File) error) {
-	src, patch, out := at.Files.Load(t)
-
-	files, _, err := Parse(bytes.NewReader(patch))
-	if err != nil {
-		t.Fatalf("failed to parse patch file: %v", err)
-	}
-	if len(files) != 1 {
-		t.Fatalf("patch should contain exactly one file, but it has %d", len(files))
-	}
-
-	var dst bytes.Buffer
-	err = apply(&dst, bytes.NewReader(src), files[0])
-	if at.Err != nil {
-		assertError(t, at.Err, err, "applying fragment")
-		return
-	}
-	if err != nil {
-		var aerr *ApplyError
-		if errors.As(err, &aerr) {
-			t.Fatalf("unexpected error applying: at %d: fragment %d at %d: %v", aerr.Line, aerr.Fragment, aerr.FragmentLine, err)
-		} else {
-			t.Fatalf("unexpected error applying: %v", err)
-		}
-	}
-
-	if !bytes.Equal(out, dst.Bytes()) {
-		t.Errorf("incorrect result after apply\nexpected:\n%q\nactual:\n%q", out, dst.Bytes())
-	}
-}
-
-type applyFiles struct {
-	Src   string
-	Patch string
-	Out   string
-}
-
-func getApplyFiles(name string) applyFiles {
-	return applyFiles{
-		Src:   name + ".src",
-		Patch: name + ".patch",
-		Out:   name + ".out",
-	}
-}
-
-func (f applyFiles) Load(t *testing.T) (src []byte, patch []byte, out []byte) {
-	load := func(name, kind string) []byte {
-		d, err := ioutil.ReadFile(filepath.Join("testdata", "apply", name))
-		if err != nil {
-			t.Fatalf("failed to read %s file: %v", kind, err)
-		}
-		return d
-	}
-
-	if f.Src != "" {
-		src = load(f.Src, "source")
-	}
-	if f.Patch != "" {
-		patch = load(f.Patch, "patch")
-	}
-	if f.Out != "" {
-		out = load(f.Out, "output")
-	}
-	return
-}
pkg/gitdiff/assert_test.go
@@ -1,30 +0,0 @@
-package gitdiff
-
-import (
-	"errors"
-	"strings"
-	"testing"
-)
-
-func assertError(t *testing.T, expected interface{}, actual error, action string) {
-	if actual == nil {
-		t.Fatalf("expected error %s, but got nil", action)
-	}
-
-	switch exp := expected.(type) {
-	case bool:
-		if !exp {
-			t.Fatalf("unexpected error %s: %v", action, actual)
-		}
-	case string:
-		if !strings.Contains(actual.Error(), exp) {
-			t.Fatalf("incorrect error %s: %q does not contain %q", action, actual.Error(), exp)
-		}
-	case error:
-		if !errors.Is(actual, exp) {
-			t.Fatalf("incorrect error %s: expected %T (%v), actual: %T (%v)", action, exp, exp, actual, actual)
-		}
-	default:
-		t.Fatalf("unsupported expected error type: %T", exp)
-	}
-}
pkg/gitdiff/base85_test.go
@@ -1,118 +0,0 @@
-package gitdiff
-
-import (
-	"bytes"
-	"testing"
-)
-
-func TestBase85Decode(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output []byte
-		Err    bool
-	}{
-		"twoBytes": {
-			Input:  "%KiWV",
-			Output: []byte{0xCA, 0xFE},
-		},
-		"fourBytes": {
-			Input:  "007GV",
-			Output: []byte{0x0, 0x0, 0xCA, 0xFE},
-		},
-		"sixBytes": {
-			Input:  "007GV%KiWV",
-			Output: []byte{0x0, 0x0, 0xCA, 0xFE, 0xCA, 0xFE},
-		},
-		"invalidCharacter": {
-			Input: "00'GV",
-			Err:   true,
-		},
-		"underpaddedSequence": {
-			Input: "007G",
-			Err:   true,
-		},
-		"dataUnderrun": {
-			Input:  "007GV",
-			Output: make([]byte, 8),
-			Err:    true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			dst := make([]byte, len(test.Output))
-			err := base85Decode(dst, []byte(test.Input))
-			if test.Err {
-				if err == nil {
-					t.Fatalf("expected error decoding base85 data, but got nil")
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error decoding base85 data: %v", err)
-			}
-			for i, b := range test.Output {
-				if dst[i] != b {
-					t.Errorf("incorrect byte at index %d: expected 0x%X, actual 0x%X", i, b, dst[i])
-				}
-			}
-		})
-	}
-}
-
-func TestBase85Encode(t *testing.T) {
-	tests := map[string]struct {
-		Input  []byte
-		Output string
-	}{
-		"zeroBytes": {
-			Input:  []byte{},
-			Output: "",
-		},
-		"twoBytes": {
-			Input:  []byte{0xCA, 0xFE},
-			Output: "%KiWV",
-		},
-		"fourBytes": {
-			Input:  []byte{0x0, 0x0, 0xCA, 0xFE},
-			Output: "007GV",
-		},
-		"sixBytes": {
-			Input:  []byte{0x0, 0x0, 0xCA, 0xFE, 0xCA, 0xFE},
-			Output: "007GV%KiWV",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			dst := make([]byte, len(test.Output))
-			base85Encode(dst, test.Input)
-			for i, b := range test.Output {
-				if dst[i] != byte(b) {
-					t.Errorf("incorrect character at index %d: expected '%c', actual '%c'", i, b, dst[i])
-				}
-			}
-		})
-	}
-}
-
-func FuzzBase85Roundtrip(f *testing.F) {
-	f.Add([]byte{0x2b, 0x0d})
-	f.Add([]byte{0xbc, 0xb4, 0x3f})
-	f.Add([]byte{0xfa, 0x62, 0x05, 0x83, 0x24, 0x39, 0xd5, 0x25})
-	f.Add([]byte{0x31, 0x59, 0x02, 0xa0, 0x61, 0x12, 0xd9, 0x43, 0xb8, 0x23, 0x1a, 0xb4, 0x02, 0xae, 0xfa, 0xcc, 0x22, 0xad, 0x41, 0xb9, 0xb8})
-
-	f.Fuzz(func(t *testing.T, in []byte) {
-		n := len(in)
-		dst := make([]byte, base85Len(n))
-		out := make([]byte, n)
-
-		base85Encode(dst, in)
-		if err := base85Decode(out, dst); err != nil {
-			t.Fatalf("unexpected error decoding base85 data: %v", err)
-		}
-		if !bytes.Equal(in, out) {
-			t.Errorf("decoded data differed from input data:\n   input: %x\n  output: %x\nencoding: %s\n", in, out, string(dst))
-		}
-	})
-}
pkg/gitdiff/binary_test.go
@@ -1,324 +0,0 @@
-package gitdiff
-
-import (
-	"encoding/binary"
-	"io"
-	"reflect"
-	"strings"
-	"testing"
-)
-
-func TestParseBinaryMarker(t *testing.T) {
-	tests := map[string]struct {
-		Input    string
-		IsBinary bool
-		HasData  bool
-		Err      bool
-	}{
-		"binaryPatch": {
-			Input:    "GIT binary patch\n",
-			IsBinary: true,
-			HasData:  true,
-		},
-		"binaryFileNoPatch": {
-			Input:    "Binary files differ\n",
-			IsBinary: true,
-			HasData:  false,
-		},
-		"binaryFileNoPatchPaths": {
-			Input:    "Binary files a/foo.bin and b/foo.bin differ\n",
-			IsBinary: true,
-			HasData:  false,
-		},
-		"fileNoPatch": {
-			Input:    "Files differ\n",
-			IsBinary: true,
-			HasData:  false,
-		},
-		"textFile": {
-			Input:    "@@ -10,14 +22,31 @@\n",
-			IsBinary: false,
-			HasData:  false,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			isBinary, hasData, err := p.ParseBinaryMarker()
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing binary marker, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing binary marker: %v", err)
-			}
-			if test.IsBinary != isBinary {
-				t.Errorf("incorrect isBinary value: expected %t, actual %t", test.IsBinary, isBinary)
-			}
-			if test.HasData != hasData {
-				t.Errorf("incorrect hasData value: expected %t, actual %t", test.HasData, hasData)
-			}
-		})
-	}
-}
-
-func TestParseBinaryFragmentHeader(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output *BinaryFragment
-		Err    bool
-	}{
-		"delta": {
-			Input: "delta 1234\n",
-			Output: &BinaryFragment{
-				Method: BinaryPatchDelta,
-				Size:   1234,
-			},
-		},
-		"literal": {
-			Input: "literal 1234\n",
-			Output: &BinaryFragment{
-				Method: BinaryPatchLiteral,
-				Size:   1234,
-			},
-		},
-		"unknownMethod": {
-			Input:  "compressed 1234\n",
-			Output: nil,
-		},
-		"notAHeader": {
-			Input:  "Binary files differ\n",
-			Output: nil,
-		},
-		"invalidSize": {
-			Input: "delta 123abc\n",
-			Err:   true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			frag, err := p.ParseBinaryFragmentHeader()
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing binary header, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing binary header: %v", err)
-			}
-			if !reflect.DeepEqual(test.Output, frag) {
-				t.Errorf("incorrect binary fragment\nexpected: %+v\n  actual: %+v", test.Output, frag)
-			}
-		})
-	}
-}
-
-func TestParseBinaryChunk(t *testing.T) {
-	tests := map[string]struct {
-		Input    string
-		Fragment BinaryFragment
-		Output   []byte
-		Err      string
-	}{
-		"singleline": {
-			Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n\n",
-			Fragment: BinaryFragment{
-				Size: 20,
-			},
-			Output: fib(5, binary.BigEndian),
-		},
-		"multiline": {
-			Input: "zcmZQzU|?i`U?w2V48*KJ%mKu_Kr9NxN<eH5#F0Qe0f=7$l~*z_FeL$%-)3N7vt?l5\n" +
-				"zl3-vE2xVZ9%4J~CI>f->s?WfX|B-=Vs{#X~svra7Ekg#T|4s}nH;WnAZ)|1Y*`&cB\n" +
-				"s(sh?X(Uz6L^!Ou&aF*u`J!eibJifSrv0z>$Q%Hd(^HIJ<Y?5`S0gT5UE&u=k\n\n",
-			Fragment: BinaryFragment{
-				Size: 160,
-			},
-			Output: fib(40, binary.BigEndian),
-		},
-		"shortLine": {
-			Input: "A00\n\n",
-			Err:   "corrupt data line",
-		},
-		"underpaddedLine": {
-			Input: "H00000000\n\n",
-			Err:   "corrupt data line",
-		},
-		"invalidLengthByte": {
-			Input: "!00000\n\n",
-			Err:   "invalid length byte",
-		},
-		"miscountedLine": {
-			Input: "H00000\n\n",
-			Err:   "incorrect byte count",
-		},
-		"invalidEncoding": {
-			Input: "TcmZQzU|?i'U?w2V48*Je09XJG\n",
-			Err:   "invalid base85 byte",
-		},
-		"noTrailingEmptyLine": {
-			Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n",
-			Err:   "unexpected EOF",
-		},
-		"invalidCompression": {
-			Input: "F007GV%KiWV\n\n",
-			Err:   "zlib",
-		},
-		"incorrectSize": {
-			Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n\n",
-			Fragment: BinaryFragment{
-				Size: 16,
-			},
-			Err: "16 byte fragment inflated to 20",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			frag := test.Fragment
-			err := p.ParseBinaryChunk(&frag)
-			if test.Err != "" {
-				if err == nil || !strings.Contains(err.Error(), test.Err) {
-					t.Fatalf("expected error containing %q parsing binary chunk, but got %v", test.Err, err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing binary chunk: %v", err)
-			}
-			if !reflect.DeepEqual(test.Output, frag.Data) {
-				t.Errorf("incorrect binary chunk\nexpected: %+v\n  actual: %+v", test.Output, frag.Data)
-			}
-		})
-	}
-}
-
-func TestParseBinaryFragments(t *testing.T) {
-	tests := map[string]struct {
-		Input string
-		File  File
-
-		Binary          bool
-		Fragment        *BinaryFragment
-		ReverseFragment *BinaryFragment
-		Err             bool
-	}{
-		"dataWithReverse": {
-			Input: `GIT binary patch
-literal 40
-gcmZQzU|?i` + "`" + `U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
-
-literal 0
-HcmV?d00001
-
-`,
-			Binary: true,
-			Fragment: &BinaryFragment{
-				Method: BinaryPatchLiteral,
-				Size:   40,
-				Data:   fib(10, binary.BigEndian),
-			},
-			ReverseFragment: &BinaryFragment{
-				Method: BinaryPatchLiteral,
-				Size:   0,
-				Data:   []byte{},
-			},
-		},
-		"dataWithoutReverse": {
-			Input: `GIT binary patch
-literal 40
-gcmZQzU|?i` + "`" + `U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
-
-`,
-			Binary: true,
-			Fragment: &BinaryFragment{
-				Method: BinaryPatchLiteral,
-				Size:   40,
-				Data:   fib(10, binary.BigEndian),
-			},
-		},
-		"noData": {
-			Input:  "Binary files differ\n",
-			Binary: true,
-		},
-		"text": {
-			Input: `@@ -1 +1 @@
--old line
-+new line
-`,
-			Binary: false,
-		},
-		"missingData": {
-			Input: "GIT binary patch\n",
-			Err:   true,
-		},
-		"invalidData": {
-			Input: `GIT binary patch
-literal 20
-TcmZQzU|?i'U?w2V48*Je09XJG
-
-`,
-			Err: true,
-		},
-		"invalidReverseData": {
-			Input: `GIT binary patch
-literal 20
-TcmZQzU|?i` + "`" + `U?w2V48*Je09XJG
-
-literal 0
-zcmV?d00001
-
-`,
-			Err: true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			file := test.File
-			_, err := p.ParseBinaryFragments(&file)
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing binary fragments, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing binary fragments: %v", err)
-			}
-			if test.Binary != file.IsBinary {
-				t.Errorf("incorrect binary state: expected %t, actual %t", test.Binary, file.IsBinary)
-			}
-			if !reflect.DeepEqual(test.Fragment, file.BinaryFragment) {
-				t.Errorf("incorrect binary fragment\nexpected: %+v\n  actual: %+v", test.Fragment, file.BinaryFragment)
-			}
-			if !reflect.DeepEqual(test.ReverseFragment, file.ReverseBinaryFragment) {
-				t.Errorf("incorrect reverse binary fragment\nexpected: %+v\n  actual: %+v", test.ReverseFragment, file.ReverseBinaryFragment)
-			}
-		})
-	}
-}
-
-func fib(n int, ord binary.ByteOrder) []byte {
-	buf := make([]byte, 4*n)
-	for i := 0; i < len(buf); i += 4 {
-		if i < 8 {
-			ord.PutUint32(buf[i:], 1)
-		} else {
-			ord.PutUint32(buf[i:], ord.Uint32(buf[i-4:])+ord.Uint32(buf[i-8:]))
-		}
-	}
-	return buf
-}
pkg/gitdiff/file_header_test.go
@@ -1,766 +0,0 @@
-package gitdiff
-
-import (
-	"io"
-	"os"
-	"reflect"
-	"testing"
-)
-
-func TestParseGitFileHeader(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output *File
-		Err    bool
-	}{
-		"fileContentChange": {
-			Input: `diff --git a/dir/file.txt b/dir/file.txt
-index 1c23fcc..40a1b33 100644
---- a/dir/file.txt
-+++ b/dir/file.txt
-@@ -2,3 +4,5 @@
-`,
-			Output: &File{
-				OldName:      "dir/file.txt",
-				NewName:      "dir/file.txt",
-				OldMode:      os.FileMode(0100644),
-				OldOIDPrefix: "1c23fcc",
-				NewOIDPrefix: "40a1b33",
-			},
-		},
-		"newFile": {
-			Input: `diff --git a/dir/file.txt b/dir/file.txt
-new file mode 100644
-index 0000000..f5711e4
---- /dev/null
-+++ b/dir/file.txt
-`,
-			Output: &File{
-				NewName:      "dir/file.txt",
-				NewMode:      os.FileMode(0100644),
-				OldOIDPrefix: "0000000",
-				NewOIDPrefix: "f5711e4",
-				IsNew:        true,
-			},
-		},
-		"newEmptyFile": {
-			Input: `diff --git a/empty.txt b/empty.txt
-new file mode 100644
-index 0000000..e69de29
-`,
-			Output: &File{
-				NewName:      "empty.txt",
-				NewMode:      os.FileMode(0100644),
-				OldOIDPrefix: "0000000",
-				NewOIDPrefix: "e69de29",
-				IsNew:        true,
-			},
-		},
-		"deleteFile": {
-			Input: `diff --git a/dir/file.txt b/dir/file.txt
-deleted file mode 100644
-index 44cc321..0000000
---- a/dir/file.txt
-+++ /dev/null
-`,
-			Output: &File{
-				OldName:      "dir/file.txt",
-				OldMode:      os.FileMode(0100644),
-				OldOIDPrefix: "44cc321",
-				NewOIDPrefix: "0000000",
-				IsDelete:     true,
-			},
-		},
-		"changeMode": {
-			Input: `diff --git a/file.sh b/file.sh
-old mode 100644
-new mode 100755
-`,
-			Output: &File{
-				OldName: "file.sh",
-				NewName: "file.sh",
-				OldMode: os.FileMode(0100644),
-				NewMode: os.FileMode(0100755),
-			},
-		},
-		"rename": {
-			Input: `diff --git a/foo.txt b/bar.txt
-similarity index 100%
-rename from foo.txt
-rename to bar.txt
-`,
-			Output: &File{
-				OldName:  "foo.txt",
-				NewName:  "bar.txt",
-				Score:    100,
-				IsRename: true,
-			},
-		},
-		"copy": {
-			Input: `diff --git a/file.txt b/copy.txt
-similarity index 100%
-copy from file.txt
-copy to copy.txt
-`,
-			Output: &File{
-				OldName: "file.txt",
-				NewName: "copy.txt",
-				Score:   100,
-				IsCopy:  true,
-			},
-		},
-		"missingDefaultFilename": {
-			Input: `diff --git a/foo.sh b/bar.sh
-old mode 100644
-new mode 100755
-`,
-			Err: true,
-		},
-		"missingNewFilename": {
-			Input: `diff --git a/file.txt b/file.txt
-index 1c23fcc..40a1b33 100644
---- a/file.txt
-`,
-			Err: true,
-		},
-		"missingOldFilename": {
-			Input: `diff --git a/file.txt b/file.txt
-index 1c23fcc..40a1b33 100644
-+++ b/file.txt
-`,
-			Err: true,
-		},
-		"invalidHeaderLine": {
-			Input: `diff --git a/file.txt b/file.txt
-index deadbeef
---- a/file.txt
-+++ b/file.txt
-`,
-			Err: true,
-		},
-		"notGitHeader": {
-			Input: `--- file.txt
-+++ file.txt
-@@ -0,0 +1 @@
-`,
-			Output: nil,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			f, err := p.ParseGitFileHeader()
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing git file header, got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing git file header: %v", err)
-			}
-
-			if !reflect.DeepEqual(test.Output, f) {
-				t.Errorf("incorrect file\nexpected: %+v\n  actual: %+v", test.Output, f)
-			}
-		})
-	}
-}
-
-func TestParseTraditionalFileHeader(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output *File
-		Err    bool
-	}{
-		"fileContentChange": {
-			Input: `--- dir/file_old.txt	2019-03-21 23:00:00.0 -0700
-+++ dir/file_new.txt	2019-03-21 23:30:00.0 -0700
-@@ -0,0 +1 @@
-`,
-			Output: &File{
-				OldName: "dir/file_new.txt",
-				NewName: "dir/file_new.txt",
-			},
-		},
-		"newFile": {
-			Input: `--- /dev/null	1969-12-31 17:00:00.0 -0700
-+++ dir/file.txt	2019-03-21 23:30:00.0 -0700
-@@ -0,0 +1 @@
-`,
-			Output: &File{
-				NewName: "dir/file.txt",
-				IsNew:   true,
-			},
-		},
-		"newFileTimestamp": {
-			Input: `--- dir/file.txt	1969-12-31 17:00:00.0 -0700
-+++ dir/file.txt	2019-03-21 23:30:00.0 -0700
-@@ -0,0 +1 @@
-`,
-			Output: &File{
-				NewName: "dir/file.txt",
-				IsNew:   true,
-			},
-		},
-		"deleteFile": {
-			Input: `--- dir/file.txt	2019-03-21 23:30:00.0 -0700
-+++ /dev/null	1969-12-31 17:00:00.0 -0700
-@@ -0,0 +1 @@
-`,
-			Output: &File{
-				OldName:  "dir/file.txt",
-				IsDelete: true,
-			},
-		},
-		"deleteFileTimestamp": {
-			Input: `--- dir/file.txt	2019-03-21 23:30:00.0 -0700
-+++ dir/file.txt	1969-12-31 17:00:00.0 -0700
-@@ -0,0 +1 @@
-`,
-			Output: &File{
-				OldName:  "dir/file.txt",
-				IsDelete: true,
-			},
-		},
-		"useShortestPrefixName": {
-			Input: `--- dir/file.txt	2019-03-21 23:00:00.0 -0700
-+++ dir/file.txt~	2019-03-21 23:30:00.0 -0700
-@@ -0,0 +1 @@
-`,
-			Output: &File{
-				OldName: "dir/file.txt",
-				NewName: "dir/file.txt",
-			},
-		},
-		"notTraditionalHeader": {
-			Input: `diff --git a/dir/file.txt b/dir/file.txt
---- a/dir/file.txt
-+++ b/dir/file.txt
-`,
-			Output: nil,
-		},
-		"noUnifiedFragment": {
-			Input: `--- dir/file_old.txt	2019-03-21 23:00:00.0 -0700
-+++ dir/file_new.txt	2019-03-21 23:30:00.0 -0700
-context line
-+added line
-`,
-			Output: nil,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			f, err := p.ParseTraditionalFileHeader()
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing traditional file header, got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing traditional file header: %v", err)
-			}
-
-			if !reflect.DeepEqual(test.Output, f) {
-				t.Errorf("incorrect file\nexpected: %+v\n  actual: %+v", test.Output, f)
-			}
-		})
-	}
-}
-
-func TestCleanName(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Drop   int
-		Output string
-	}{
-		"alreadyClean": {
-			Input: "a/b/c.txt", Output: "a/b/c.txt",
-		},
-		"doubleSlashes": {
-			Input: "a//b/c.txt", Output: "a/b/c.txt",
-		},
-		"tripleSlashes": {
-			Input: "a///b/c.txt", Output: "a/b/c.txt",
-		},
-		"dropPrefix": {
-			Input: "a/b/c.txt", Drop: 2, Output: "c.txt",
-		},
-		"removeDoublesBeforeDrop": {
-			Input: "a//b/c.txt", Drop: 1, Output: "b/c.txt",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			output := cleanName(test.Input, test.Drop)
-			if output != test.Output {
-				t.Fatalf("incorrect output: expected %q, actual %q", test.Output, output)
-			}
-		})
-	}
-}
-
-func TestParseName(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Term   byte
-		Drop   int
-		Output string
-		N      int
-		Err    bool
-	}{
-		"singleUnquoted": {
-			Input: "dir/file.txt", Output: "dir/file.txt", N: 12,
-		},
-		"singleQuoted": {
-			Input: `"dir/file.txt"`, Output: "dir/file.txt", N: 14,
-		},
-		"quotedWithEscape": {
-			Input: `"dir/\"quotes\".txt"`, Output: `dir/"quotes".txt`, N: 20,
-		},
-		"quotedWithSpaces": {
-			Input: `"dir/space file.txt"`, Output: "dir/space file.txt", N: 20,
-		},
-		"tabTerminator": {
-			Input: "dir/space file.txt\tfile2.txt", Term: '\t', Output: "dir/space file.txt", N: 18,
-		},
-		"dropPrefix": {
-			Input: "a/dir/file.txt", Drop: 1, Output: "dir/file.txt", N: 14,
-		},
-		"unquotedWithSpaces": {
-			Input: "dir/with spaces.txt", Output: "dir/with spaces.txt", N: 19,
-		},
-		"unquotedWithTrailingSpaces": {
-			Input: "dir/with spaces.space  ", Output: "dir/with spaces.space  ", N: 23,
-		},
-		"devNull": {
-			Input: "/dev/null", Term: '\t', Drop: 1, Output: "/dev/null", N: 9,
-		},
-		"newlineSeparates": {
-			Input: "dir/file.txt\n", Output: "dir/file.txt", N: 12,
-		},
-		"emptyString": {
-			Input: "", Err: true,
-		},
-		"emptyQuotedString": {
-			Input: `""`, Err: true,
-		},
-		"unterminatedQuotes": {
-			Input: `"dir/file.txt`, Err: true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			output, n, err := parseName(test.Input, test.Term, test.Drop)
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing name, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing name: %v", err)
-			}
-
-			if output != test.Output {
-				t.Errorf("incorrect output: expected %q, actual: %q", test.Output, output)
-			}
-			if n != test.N {
-				t.Errorf("incorrect next position: expected %d, actual %d", test.N, n)
-			}
-		})
-	}
-}
-
-func TestParseGitHeaderData(t *testing.T) {
-	tests := map[string]struct {
-		InputFile   *File
-		Line        string
-		DefaultName string
-
-		OutputFile *File
-		End        bool
-		Err        bool
-	}{
-		"fragementEndsParsing": {
-			Line: "@@ -12,3 +12,2 @@\n",
-			End:  true,
-		},
-		"unknownEndsParsing": {
-			Line: "GIT binary file\n",
-			End:  true,
-		},
-		"oldFileName": {
-			Line: "--- a/dir/file.txt\n",
-			OutputFile: &File{
-				OldName: "dir/file.txt",
-			},
-		},
-		"oldFileNameDevNull": {
-			InputFile: &File{
-				IsNew: true,
-			},
-			Line: "--- /dev/null\n",
-			OutputFile: &File{
-				IsNew: true,
-			},
-		},
-		"oldFileNameInconsistent": {
-			InputFile: &File{
-				OldName: "dir/foo.txt",
-			},
-			Line: "--- a/dir/bar.txt\n",
-			Err:  true,
-		},
-		"oldFileNameExistingCreateMismatch": {
-			InputFile: &File{
-				OldName: "dir/foo.txt",
-				IsNew:   true,
-			},
-			Line: "--- /dev/null\n",
-			Err:  true,
-		},
-		"oldFileNameParsedCreateMismatch": {
-			InputFile: &File{
-				IsNew: true,
-			},
-			Line: "--- a/dir/file.txt\n",
-			Err:  true,
-		},
-		"oldFileNameMissing": {
-			Line: "--- \n",
-			Err:  true,
-		},
-		"newFileName": {
-			Line: "+++ b/dir/file.txt\n",
-			OutputFile: &File{
-				NewName: "dir/file.txt",
-			},
-		},
-		"newFileNameDevNull": {
-			InputFile: &File{
-				IsDelete: true,
-			},
-			Line: "+++ /dev/null\n",
-			OutputFile: &File{
-				IsDelete: true,
-			},
-		},
-		"newFileNameInconsistent": {
-			InputFile: &File{
-				NewName: "dir/foo.txt",
-			},
-			Line: "+++ b/dir/bar.txt\n",
-			Err:  true,
-		},
-		"newFileNameExistingDeleteMismatch": {
-			InputFile: &File{
-				NewName:  "dir/foo.txt",
-				IsDelete: true,
-			},
-			Line: "+++ /dev/null\n",
-			Err:  true,
-		},
-		"newFileNameParsedDeleteMismatch": {
-			InputFile: &File{
-				IsDelete: true,
-			},
-			Line: "+++ b/dir/file.txt\n",
-			Err:  true,
-		},
-		"newFileNameMissing": {
-			Line: "+++ \n",
-			Err:  true,
-		},
-		"oldMode": {
-			Line: "old mode 100644\n",
-			OutputFile: &File{
-				OldMode: os.FileMode(0100644),
-			},
-		},
-		"oldModeWithTrailingSpace": {
-			Line: "old mode 100644\r\n",
-			OutputFile: &File{
-				OldMode: os.FileMode(0100644),
-			},
-		},
-		"invalidOldMode": {
-			Line: "old mode rw\n",
-			Err:  true,
-		},
-		"newMode": {
-			Line: "new mode 100755\n",
-			OutputFile: &File{
-				NewMode: os.FileMode(0100755),
-			},
-		},
-		"newModeWithTrailingSpace": {
-			Line: "new mode 100755\r\n",
-			OutputFile: &File{
-				NewMode: os.FileMode(0100755),
-			},
-		},
-		"invalidNewMode": {
-			Line: "new mode rwx\n",
-			Err:  true,
-		},
-		"deletedFileMode": {
-			Line:        "deleted file mode 100644\n",
-			DefaultName: "dir/file.txt",
-			OutputFile: &File{
-				OldName:  "dir/file.txt",
-				OldMode:  os.FileMode(0100644),
-				IsDelete: true,
-			},
-		},
-		"newFileMode": {
-			Line:        "new file mode 100755\n",
-			DefaultName: "dir/file.txt",
-			OutputFile: &File{
-				NewName: "dir/file.txt",
-				NewMode: os.FileMode(0100755),
-				IsNew:   true,
-			},
-		},
-		"newFileModeWithTrailingSpace": {
-			Line:        "new file mode 100755\r\n",
-			DefaultName: "dir/file.txt",
-			OutputFile: &File{
-				NewName: "dir/file.txt",
-				NewMode: os.FileMode(0100755),
-				IsNew:   true,
-			},
-		},
-		"copyFrom": {
-			Line: "copy from dir/file.txt\n",
-			OutputFile: &File{
-				OldName: "dir/file.txt",
-				IsCopy:  true,
-			},
-		},
-		"copyTo": {
-			Line: "copy to dir/file.txt\n",
-			OutputFile: &File{
-				NewName: "dir/file.txt",
-				IsCopy:  true,
-			},
-		},
-		"renameFrom": {
-			Line: "rename from dir/file.txt\n",
-			OutputFile: &File{
-				OldName:  "dir/file.txt",
-				IsRename: true,
-			},
-		},
-		"renameTo": {
-			Line: "rename to dir/file.txt\n",
-			OutputFile: &File{
-				NewName:  "dir/file.txt",
-				IsRename: true,
-			},
-		},
-		"similarityIndex": {
-			Line: "similarity index 88%\n",
-			OutputFile: &File{
-				Score: 88,
-			},
-		},
-		"similarityIndexTooBig": {
-			Line: "similarity index 9001%\n",
-			OutputFile: &File{
-				Score: 0,
-			},
-		},
-		"similarityIndexInvalid": {
-			Line: "similarity index 12ab%\n",
-			Err:  true,
-		},
-		"indexFullSHA1AndMode": {
-			Line: "index 79c6d7f7b7e76c75b3d238f12fb1323f2333ba14..04fab916d8f938173cbb8b93469855f0e838f098 100644\n",
-			OutputFile: &File{
-				OldOIDPrefix: "79c6d7f7b7e76c75b3d238f12fb1323f2333ba14",
-				NewOIDPrefix: "04fab916d8f938173cbb8b93469855f0e838f098",
-				OldMode:      os.FileMode(0100644),
-			},
-		},
-		"indexFullSHA1NoMode": {
-			Line: "index 79c6d7f7b7e76c75b3d238f12fb1323f2333ba14..04fab916d8f938173cbb8b93469855f0e838f098\n",
-			OutputFile: &File{
-				OldOIDPrefix: "79c6d7f7b7e76c75b3d238f12fb1323f2333ba14",
-				NewOIDPrefix: "04fab916d8f938173cbb8b93469855f0e838f098",
-			},
-		},
-		"indexAbbrevSHA1AndMode": {
-			Line: "index 79c6d7..04fab9 100644\n",
-			OutputFile: &File{
-				OldOIDPrefix: "79c6d7",
-				NewOIDPrefix: "04fab9",
-				OldMode:      os.FileMode(0100644),
-			},
-		},
-		"indexInvalid": {
-			Line: "index 79c6d7f7b7e76c75b3d238f12fb1323f2333ba14\n",
-			Err:  true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			var f File
-			if test.InputFile != nil {
-				f = *test.InputFile
-			}
-
-			end, err := parseGitHeaderData(&f, test.Line, test.DefaultName)
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing header data, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing header data: %v", err)
-			}
-
-			if test.OutputFile != nil && !reflect.DeepEqual(test.OutputFile, &f) {
-				t.Errorf("incorrect output:\nexpected: %+v\nactual: %+v", test.OutputFile, &f)
-			}
-			if end != test.End {
-				t.Errorf("incorrect end state, expected %t, actual %t", test.End, end)
-			}
-		})
-	}
-}
-
-func TestParseGitHeaderName(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output string
-		Err    bool
-	}{
-		"twoMatchingNames": {
-			Input:  "a/dir/file.txt b/dir/file.txt",
-			Output: "dir/file.txt",
-		},
-		"twoDifferentNames": {
-			Input:  "a/dir/foo.txt b/dir/bar.txt",
-			Output: "",
-		},
-		"matchingNamesWithSpaces": {
-			Input:  "a/dir/file with spaces.txt b/dir/file with spaces.txt",
-			Output: "dir/file with spaces.txt",
-		},
-		"matchingNamesWithTrailingSpaces": {
-			Input:  "a/dir/spaces   b/dir/spaces  ",
-			Output: "dir/spaces  ",
-		},
-		"matchingNamesQuoted": {
-			Input:  `"a/dir/\"quotes\".txt" "b/dir/\"quotes\".txt"`,
-			Output: `dir/"quotes".txt`,
-		},
-		"matchingNamesFirstQuoted": {
-			Input:  `"a/dir/file.txt" b/dir/file.txt`,
-			Output: "dir/file.txt",
-		},
-		"matchingNamesSecondQuoted": {
-			Input:  `a/dir/file.txt "b/dir/file.txt"`,
-			Output: "dir/file.txt",
-		},
-		"noSecondName": {
-			Input:  "a/dir/foo.txt",
-			Output: "",
-		},
-		"noSecondNameQuoted": {
-			Input:  `"a/dir/foo.txt"`,
-			Output: "",
-		},
-		"invalidName": {
-			Input: `"a/dir/file.txt b/dir/file.txt`,
-			Err:   true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			output, err := parseGitHeaderName(test.Input)
-			if test.Err {
-				if err == nil {
-					t.Fatalf("expected error parsing header name, but got nil")
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing header name: %v", err)
-			}
-
-			if output != test.Output {
-				t.Errorf("incorrect output: expected %q, actual %q", test.Output, output)
-			}
-		})
-	}
-}
-
-func TestHasEpochTimestamp(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output bool
-	}{
-		"utcTimestamp": {
-			Input:  "+++ file.txt\t1970-01-01 00:00:00 +0000\n",
-			Output: true,
-		},
-		"utcZoneWithColon": {
-			Input:  "+++ file.txt\t1970-01-01 00:00:00 +00:00\n",
-			Output: true,
-		},
-		"utcZoneWithMilliseconds": {
-			Input:  "+++ file.txt\t1970-01-01 00:00:00.000000 +00:00\n",
-			Output: true,
-		},
-		"westTimestamp": {
-			Input:  "+++ file.txt\t1969-12-31 16:00:00 -0800\n",
-			Output: true,
-		},
-		"eastTimestamp": {
-			Input:  "+++ file.txt\t1970-01-01 04:00:00 +0400\n",
-			Output: true,
-		},
-		"noTab": {
-			Input:  "+++ file.txt 1970-01-01 00:00:00 +0000\n",
-			Output: false,
-		},
-		"invalidFormat": {
-			Input:  "+++ file.txt\t1970-01-01T00:00:00Z\n",
-			Output: false,
-		},
-		"notEpoch": {
-			Input:  "+++ file.txt\t2019-03-21 12:34:56.789 -0700\n",
-			Output: false,
-		},
-		"notTimestamp": {
-			Input:  "+++ file.txt\trandom text\n",
-			Output: false,
-		},
-		"notTimestampShort": {
-			Input:  "+++ file.txt\t0\n",
-			Output: false,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			output := hasEpochTimestamp(test.Input)
-			if output != test.Output {
-				t.Errorf("incorrect output: expected %t, actual %t", test.Output, output)
-			}
-		})
-	}
-}
pkg/gitdiff/format_roundtrip_test.go
@@ -1,157 +0,0 @@
-package gitdiff
-
-import (
-	"bytes"
-	"fmt"
-	"os"
-	"path/filepath"
-	"slices"
-	"testing"
-)
-
-func TestFormatRoundtrip(t *testing.T) {
-	patches := []struct {
-		File            string
-		SkipTextCompare bool
-	}{
-		{File: "copy.patch"},
-		{File: "copy_modify.patch"},
-		{File: "delete.patch"},
-		{File: "mode.patch"},
-		{File: "mode_modify.patch"},
-		{File: "modify.patch"},
-		{File: "new.patch"},
-		{File: "new_empty.patch"},
-		{File: "new_mode.patch"},
-		{File: "rename.patch"},
-		{File: "rename_modify.patch"},
-
-		// Due to differences between Go's 'encoding/zlib' package and the zlib
-		// C library, binary patches cannot be compared directly as the patch
-		// data is slightly different when re-encoded by Go.
-		{File: "binary_modify.patch", SkipTextCompare: true},
-		{File: "binary_new.patch", SkipTextCompare: true},
-		{File: "binary_modify_nodata.patch"},
-	}
-
-	for _, patch := range patches {
-		t.Run(patch.File, func(t *testing.T) {
-			b, err := os.ReadFile(filepath.Join("testdata", "string", patch.File))
-			if err != nil {
-				t.Fatalf("failed to read patch: %v", err)
-			}
-
-			original := assertParseSingleFile(t, b, "patch")
-			str := original.String()
-
-			if !patch.SkipTextCompare {
-				if string(b) != str {
-					t.Errorf("incorrect patch text\nexpected: %q\n  actual: %q\n", string(b), str)
-				}
-			}
-
-			reparsed := assertParseSingleFile(t, []byte(str), "formatted patch")
-			assertFilesEqual(t, original, reparsed)
-		})
-	}
-}
-
-func assertParseSingleFile(t *testing.T, b []byte, kind string) *File {
-	files, _, err := Parse(bytes.NewReader(b))
-	if err != nil {
-		t.Fatalf("failed to parse %s: %v", kind, err)
-	}
-	if len(files) != 1 {
-		t.Fatalf("expected %s to contain a single files, but found %d", kind, len(files))
-	}
-	return files[0]
-}
-
-func assertFilesEqual(t *testing.T, expected, actual *File) {
-	assertEqual(t, expected.OldName, actual.OldName, "OldName")
-	assertEqual(t, expected.NewName, actual.NewName, "NewName")
-
-	assertEqual(t, expected.IsNew, actual.IsNew, "IsNew")
-	assertEqual(t, expected.IsDelete, actual.IsDelete, "IsDelete")
-	assertEqual(t, expected.IsCopy, actual.IsCopy, "IsCopy")
-	assertEqual(t, expected.IsRename, actual.IsRename, "IsRename")
-
-	assertEqual(t, expected.OldMode, actual.OldMode, "OldMode")
-	assertEqual(t, expected.NewMode, actual.NewMode, "NewMode")
-
-	assertEqual(t, expected.OldOIDPrefix, actual.OldOIDPrefix, "OldOIDPrefix")
-	assertEqual(t, expected.NewOIDPrefix, actual.NewOIDPrefix, "NewOIDPrefix")
-	assertEqual(t, expected.Score, actual.Score, "Score")
-
-	if len(expected.TextFragments) == len(actual.TextFragments) {
-		for i := range expected.TextFragments {
-			prefix := fmt.Sprintf("TextFragments[%d].", i)
-			ef := expected.TextFragments[i]
-			af := actual.TextFragments[i]
-
-			assertEqual(t, ef.Comment, af.Comment, prefix+"Comment")
-
-			assertEqual(t, ef.OldPosition, af.OldPosition, prefix+"OldPosition")
-			assertEqual(t, ef.OldLines, af.OldLines, prefix+"OldLines")
-
-			assertEqual(t, ef.NewPosition, af.NewPosition, prefix+"NewPosition")
-			assertEqual(t, ef.NewLines, af.NewLines, prefix+"NewLines")
-
-			assertEqual(t, ef.LinesAdded, af.LinesAdded, prefix+"LinesAdded")
-			assertEqual(t, ef.LinesDeleted, af.LinesDeleted, prefix+"LinesDeleted")
-
-			assertEqual(t, ef.LeadingContext, af.LeadingContext, prefix+"LeadingContext")
-			assertEqual(t, ef.TrailingContext, af.TrailingContext, prefix+"TrailingContext")
-
-			if !slices.Equal(ef.Lines, af.Lines) {
-				t.Errorf("%sLines: expected %#v, actual %#v", prefix, ef.Lines, af.Lines)
-			}
-		}
-	} else {
-		t.Errorf("TextFragments: expected length %d, actual length %d", len(expected.TextFragments), len(actual.TextFragments))
-	}
-
-	assertEqual(t, expected.IsBinary, actual.IsBinary, "IsBinary")
-
-	if expected.BinaryFragment != nil {
-		if actual.BinaryFragment == nil {
-			t.Errorf("BinaryFragment: expected non-nil, actual is nil")
-		} else {
-			ef := expected.BinaryFragment
-			af := expected.BinaryFragment
-
-			assertEqual(t, ef.Method, af.Method, "BinaryFragment.Method")
-			assertEqual(t, ef.Size, af.Size, "BinaryFragment.Size")
-
-			if !slices.Equal(ef.Data, af.Data) {
-				t.Errorf("BinaryFragment.Data: expected %#v, actual %#v", ef.Data, af.Data)
-			}
-		}
-	} else if actual.BinaryFragment != nil {
-		t.Errorf("BinaryFragment: expected nil, actual is non-nil")
-	}
-
-	if expected.ReverseBinaryFragment != nil {
-		if actual.ReverseBinaryFragment == nil {
-			t.Errorf("ReverseBinaryFragment: expected non-nil, actual is nil")
-		} else {
-			ef := expected.ReverseBinaryFragment
-			af := expected.ReverseBinaryFragment
-
-			assertEqual(t, ef.Method, af.Method, "ReverseBinaryFragment.Method")
-			assertEqual(t, ef.Size, af.Size, "ReverseBinaryFragment.Size")
-
-			if !slices.Equal(ef.Data, af.Data) {
-				t.Errorf("ReverseBinaryFragment.Data: expected %#v, actual %#v", ef.Data, af.Data)
-			}
-		}
-	} else if actual.ReverseBinaryFragment != nil {
-		t.Errorf("ReverseBinaryFragment: expected nil, actual is non-nil")
-	}
-}
-
-func assertEqual[T comparable](t *testing.T, expected, actual T, name string) {
-	if expected != actual {
-		t.Errorf("%s: expected %#v, actual %#v", name, expected, actual)
-	}
-}
pkg/gitdiff/format_test.go
@@ -1,28 +0,0 @@
-package gitdiff
-
-import (
-	"strings"
-	"testing"
-)
-
-func TestFormatter_WriteQuotedName(t *testing.T) {
-	tests := []struct {
-		Input    string
-		Expected string
-	}{
-		{"noquotes.txt", `noquotes.txt`},
-		{"no quotes.txt", `no quotes.txt`},
-		{"new\nline", `"new\nline"`},
-		{"escape\x1B null\x00", `"escape\033 null\000"`},
-		{"snowman \u2603 snowman", `"snowman \342\230\203 snowman"`},
-		{"\"already quoted\"", `"\"already quoted\""`},
-	}
-
-	for _, test := range tests {
-		var b strings.Builder
-		newFormatter(&b).WriteQuotedName(test.Input)
-		if b.String() != test.Expected {
-			t.Errorf("expected %q, got %q", test.Expected, b.String())
-		}
-	}
-}
pkg/gitdiff/gitdiff_test.go
@@ -1,161 +0,0 @@
-package gitdiff
-
-import (
-	"strings"
-	"testing"
-)
-
-func TestTextFragmentValidate(t *testing.T) {
-	tests := map[string]struct {
-		Fragment TextFragment
-		Err      string
-	}{
-		"oldLines": {
-			Fragment: TextFragment{
-				OldPosition:     1,
-				OldLines:        3,
-				NewPosition:     1,
-				NewLines:        2,
-				LeadingContext:  1,
-				TrailingContext: 0,
-				LinesAdded:      1,
-				LinesDeleted:    1,
-				Lines: []Line{
-					{Op: OpContext, Line: "line 1\n"},
-					{Op: OpDelete, Line: "old line 2\n"},
-					{Op: OpAdd, Line: "new line 2\n"},
-				},
-			},
-			Err: "2 old lines",
-		},
-		"newLines": {
-			Fragment: TextFragment{
-				OldPosition:     1,
-				OldLines:        2,
-				NewPosition:     1,
-				NewLines:        3,
-				LeadingContext:  1,
-				TrailingContext: 0,
-				LinesAdded:      1,
-				LinesDeleted:    1,
-				Lines: []Line{
-					{Op: OpContext, Line: "line 1\n"},
-					{Op: OpDelete, Line: "old line 2\n"},
-					{Op: OpAdd, Line: "new line 2\n"},
-				},
-			},
-			Err: "2 new lines",
-		},
-		"leadingContext": {
-			Fragment: TextFragment{
-				OldPosition:     1,
-				OldLines:        2,
-				NewPosition:     1,
-				NewLines:        2,
-				LeadingContext:  0,
-				TrailingContext: 0,
-				LinesAdded:      1,
-				LinesDeleted:    1,
-				Lines: []Line{
-					{Op: OpContext, Line: "line 1\n"},
-					{Op: OpDelete, Line: "old line 2\n"},
-					{Op: OpAdd, Line: "new line 2\n"},
-				},
-			},
-			Err: "1 leading context lines",
-		},
-		"trailingContext": {
-			Fragment: TextFragment{
-				OldPosition:     1,
-				OldLines:        4,
-				NewPosition:     1,
-				NewLines:        3,
-				LeadingContext:  1,
-				TrailingContext: 1,
-				LinesAdded:      1,
-				LinesDeleted:    2,
-				Lines: []Line{
-					{Op: OpContext, Line: "line 1\n"},
-					{Op: OpDelete, Line: "old line 2\n"},
-					{Op: OpAdd, Line: "new line 2\n"},
-					{Op: OpContext, Line: "line 3\n"},
-					{Op: OpDelete, Line: "old line 4\n"},
-				},
-			},
-			Err: "0 trailing context lines",
-		},
-		"linesAdded": {
-			Fragment: TextFragment{
-				OldPosition:     1,
-				OldLines:        4,
-				NewPosition:     1,
-				NewLines:        3,
-				LeadingContext:  1,
-				TrailingContext: 0,
-				LinesAdded:      2,
-				LinesDeleted:    2,
-				Lines: []Line{
-					{Op: OpContext, Line: "line 1\n"},
-					{Op: OpDelete, Line: "old line 2\n"},
-					{Op: OpAdd, Line: "new line 2\n"},
-					{Op: OpContext, Line: "line 3\n"},
-					{Op: OpDelete, Line: "old line 4\n"},
-				},
-			},
-			Err: "1 added lines",
-		},
-		"linesDeleted": {
-			Fragment: TextFragment{
-				OldPosition:     1,
-				OldLines:        4,
-				NewPosition:     1,
-				NewLines:        3,
-				LeadingContext:  1,
-				TrailingContext: 0,
-				LinesAdded:      1,
-				LinesDeleted:    1,
-				Lines: []Line{
-					{Op: OpContext, Line: "line 1\n"},
-					{Op: OpDelete, Line: "old line 2\n"},
-					{Op: OpAdd, Line: "new line 2\n"},
-					{Op: OpContext, Line: "line 3\n"},
-					{Op: OpDelete, Line: "old line 4\n"},
-				},
-			},
-			Err: "2 deleted lines",
-		},
-		"fileCreation": {
-			Fragment: TextFragment{
-				OldPosition:     0,
-				OldLines:        2,
-				NewPosition:     1,
-				NewLines:        1,
-				LeadingContext:  0,
-				TrailingContext: 0,
-				LinesAdded:      1,
-				LinesDeleted:    2,
-				Lines: []Line{
-					{Op: OpDelete, Line: "old line 1\n"},
-					{Op: OpDelete, Line: "old line 2\n"},
-					{Op: OpAdd, Line: "new line\n"},
-				},
-			},
-			Err: "creation fragment",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			err := test.Fragment.Validate()
-			if test.Err == "" && err != nil {
-				t.Fatalf("unexpected validation error: %v", err)
-			}
-			if test.Err != "" && err == nil {
-				t.Fatal("expected validation error, but got nil")
-			}
-			if !strings.Contains(err.Error(), test.Err) {
-				t.Fatalf("incorrect validation error: %q is not in %q", test.Err, err.Error())
-			}
-		})
-	}
-}
pkg/gitdiff/io_test.go
@@ -1,254 +0,0 @@
-package gitdiff
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"math/rand"
-	"testing"
-)
-
-func TestLineReaderAt(t *testing.T) {
-	const lineTemplate = "generated test line %d\n"
-
-	tests := map[string]struct {
-		InputLines int
-		Offset     int64
-		Count      int
-		Err        bool
-		EOF        bool
-		EOFCount   int
-	}{
-		"readLines": {
-			InputLines: 32,
-			Offset:     0,
-			Count:      4,
-		},
-		"readLinesOffset": {
-			InputLines: 32,
-			Offset:     8,
-			Count:      4,
-		},
-		"readLinesLargeOffset": {
-			InputLines: 8192,
-			Offset:     4096,
-			Count:      64,
-		},
-		"readSingleLine": {
-			InputLines: 4,
-			Offset:     2,
-			Count:      1,
-		},
-		"readZeroLines": {
-			InputLines: 4,
-			Offset:     2,
-			Count:      0,
-		},
-		"readAllLines": {
-			InputLines: 64,
-			Offset:     0,
-			Count:      64,
-		},
-		"readThroughEOF": {
-			InputLines: 16,
-			Offset:     12,
-			Count:      8,
-			EOF:        true,
-			EOFCount:   4,
-		},
-		"emptyInput": {
-			InputLines: 0,
-			Offset:     0,
-			Count:      2,
-			EOF:        true,
-			EOFCount:   0,
-		},
-		"offsetAfterEOF": {
-			InputLines: 8,
-			Offset:     10,
-			Count:      2,
-			EOF:        true,
-			EOFCount:   0,
-		},
-		"offsetNegative": {
-			InputLines: 8,
-			Offset:     -1,
-			Count:      2,
-			Err:        true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			var input bytes.Buffer
-			for i := 0; i < test.InputLines; i++ {
-				fmt.Fprintf(&input, lineTemplate, i)
-			}
-
-			output := make([][]byte, test.Count)
-			for i := 0; i < test.Count; i++ {
-				output[i] = []byte(fmt.Sprintf(lineTemplate, test.Offset+int64(i)))
-			}
-
-			r := &lineReaderAt{r: bytes.NewReader(input.Bytes())}
-			lines := make([][]byte, test.Count)
-
-			n, err := r.ReadLinesAt(lines, test.Offset)
-			if test.Err {
-				if err == nil {
-					t.Fatal("expected error reading lines, but got nil")
-				}
-				return
-			}
-			if err != nil && (!test.EOF || err != io.EOF) {
-				t.Fatalf("unexpected error reading lines: %v", err)
-			}
-
-			count := test.Count
-			if test.EOF {
-				count = test.EOFCount
-			}
-
-			if n != count {
-				t.Fatalf("incorrect number of lines read: expected %d, actual %d", count, n)
-			}
-			for i := 0; i < n; i++ {
-				if !bytes.Equal(output[i], lines[i]) {
-					t.Errorf("incorrect content in line %d:\nexpected: %q\nactual: %q", i, output[i], lines[i])
-				}
-			}
-		})
-	}
-
-	newlineTests := map[string]struct {
-		InputSize int
-	}{
-		"readLinesNoFinalNewline": {
-			InputSize: indexBufferSize + indexBufferSize/2,
-		},
-		"readLinesNoFinalNewlineBufferMultiple": {
-			InputSize: 4 * indexBufferSize,
-		},
-	}
-
-	for name, test := range newlineTests {
-		t.Run(name, func(t *testing.T) {
-			input := bytes.Repeat([]byte("0"), test.InputSize)
-
-			var output [][]byte
-			for i := 0; i < len(input); i++ {
-				last := i
-				i += rand.Intn(80)
-				if i < len(input)-1 { // last character of input must not be a newline
-					input[i] = '\n'
-					output = append(output, input[last:i+1])
-				} else {
-					output = append(output, input[last:])
-				}
-			}
-
-			r := &lineReaderAt{r: bytes.NewReader(input)}
-			lines := make([][]byte, len(output))
-
-			n, err := r.ReadLinesAt(lines, 0)
-			if err != nil {
-				t.Fatalf("unexpected error reading reading lines: %v", err)
-			}
-
-			if n != len(output) {
-				t.Fatalf("incorrect number of lines read: expected %d, actual %d", len(output), n)
-			}
-
-			for i, line := range lines {
-				if !bytes.Equal(output[i], line) {
-					t.Errorf("incorrect content in line %d:\nexpected: %q\nactual: %q", i, output[i], line)
-				}
-			}
-		})
-	}
-}
-
-func TestCopyFrom(t *testing.T) {
-	tests := map[string]struct {
-		Bytes  int64
-		Offset int64
-	}{
-		"copyAll": {
-			Bytes: byteBufferSize / 2,
-		},
-		"copyPartial": {
-			Bytes:  byteBufferSize / 2,
-			Offset: byteBufferSize / 4,
-		},
-		"copyLarge": {
-			Bytes: 8 * byteBufferSize,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			data := make([]byte, test.Bytes)
-			rand.Read(data)
-
-			var dst bytes.Buffer
-			n, err := copyFrom(&dst, bytes.NewReader(data), test.Offset)
-			if err != nil {
-				t.Fatalf("unexpected error copying data: %v", err)
-			}
-			if n != test.Bytes-test.Offset {
-				t.Fatalf("incorrect number of bytes copied: expected %d, actual %d", test.Bytes-test.Offset, n)
-			}
-
-			expected := data[test.Offset:]
-			if !bytes.Equal(expected, dst.Bytes()) {
-				t.Fatalf("incorrect data copied:\nexpected: %v\nactual: %v", expected, dst.Bytes())
-			}
-		})
-	}
-}
-
-func TestCopyLinesFrom(t *testing.T) {
-	tests := map[string]struct {
-		Lines  int64
-		Offset int64
-	}{
-		"copyAll": {
-			Lines: lineBufferSize / 2,
-		},
-		"copyPartial": {
-			Lines:  lineBufferSize / 2,
-			Offset: lineBufferSize / 4,
-		},
-		"copyLarge": {
-			Lines: 8 * lineBufferSize,
-		},
-	}
-
-	const lineLength = 128
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			data := make([]byte, test.Lines*lineLength)
-			for i := range data {
-				data[i] = byte(32 + rand.Intn(95)) // ascii letters, numbers, symbols
-				if i%lineLength == lineLength-1 {
-					data[i] = '\n'
-				}
-			}
-
-			var dst bytes.Buffer
-			n, err := copyLinesFrom(&dst, &lineReaderAt{r: bytes.NewReader(data)}, test.Offset)
-			if err != nil {
-				t.Fatalf("unexpected error copying data: %v", err)
-			}
-			if n != test.Lines-test.Offset {
-				t.Fatalf("incorrect number of lines copied: expected %d, actual %d", test.Lines-test.Offset, n)
-			}
-
-			expected := data[test.Offset*lineLength:]
-			if !bytes.Equal(expected, dst.Bytes()) {
-				t.Fatalf("incorrect data copied:\nexpected: %v\nactual: %v", expected, dst.Bytes())
-			}
-		})
-	}
-}
pkg/gitdiff/parser_test.go
@@ -1,511 +0,0 @@
-package gitdiff
-
-import (
-	"bytes"
-	"encoding/binary"
-	"encoding/json"
-	"io"
-	"os"
-	"reflect"
-	"testing"
-)
-
-func TestLineOperations(t *testing.T) {
-	const content = "the first line\nthe second line\nthe third line\n"
-
-	t.Run("read", func(t *testing.T) {
-		p := newTestParser(content, false)
-
-		for i, expected := range []string{
-			"the first line\n",
-			"the second line\n",
-			"the third line\n",
-		} {
-			if err := p.Next(); err != nil {
-				t.Fatalf("error advancing parser after line %d: %v", i, err)
-			}
-			if p.lineno != int64(i+1) {
-				t.Fatalf("incorrect line number: expected %d, actual: %d", i+1, p.lineno)
-			}
-
-			line := p.Line(0)
-			if line != expected {
-				t.Fatalf("incorrect line %d: expected %q, was %q", i+1, expected, line)
-			}
-		}
-
-		// reading after the last line should return EOF
-		if err := p.Next(); err != io.EOF {
-			t.Fatalf("expected EOF after end, but got: %v", err)
-		}
-		if p.lineno != 4 {
-			t.Fatalf("incorrect line number: expected %d, actual: %d", 4, p.lineno)
-		}
-
-		// reading again returns EOF again and does not advance the line
-		if err := p.Next(); err != io.EOF {
-			t.Fatalf("expected EOF after end, but got: %v", err)
-		}
-		if p.lineno != 4 {
-			t.Fatalf("incorrect line number: expected %d, actual: %d", 4, p.lineno)
-		}
-	})
-
-	t.Run("peek", func(t *testing.T) {
-		p := newTestParser(content, false)
-		if err := p.Next(); err != nil {
-			t.Fatalf("error advancing parser: %v", err)
-		}
-
-		line := p.Line(1)
-		if line != "the second line\n" {
-			t.Fatalf("incorrect peek line: %s", line)
-		}
-
-		if err := p.Next(); err != nil {
-			t.Fatalf("error advancing parser after peek: %v", err)
-		}
-
-		line = p.Line(0)
-		if line != "the second line\n" {
-			t.Fatalf("incorrect read line: %s", line)
-		}
-	})
-
-	t.Run("emptyInput", func(t *testing.T) {
-		p := newTestParser("", false)
-		if err := p.Next(); err != io.EOF {
-			t.Fatalf("expected EOF on first Next(), but got: %v", err)
-		}
-	})
-}
-
-func TestParserInvariant_Advancement(t *testing.T) {
-	tests := map[string]struct {
-		Input   string
-		Parse   func(p *parser) error
-		EndLine string
-	}{
-		"ParseGitFileHeader": {
-			Input: `diff --git a/dir/file.txt b/dir/file.txt
-index 9540595..30e6333 100644
---- a/dir/file.txt
-+++ b/dir/file.txt
-@@ -1,2 +1,3 @@
-context line
-`,
-			Parse: func(p *parser) error {
-				_, err := p.ParseGitFileHeader()
-				return err
-			},
-			EndLine: "@@ -1,2 +1,3 @@\n",
-		},
-		"ParseTraditionalFileHeader": {
-			Input: `--- dir/file.txt
-+++ dir/file.txt
-@@ -1,2 +1,3 @@
-context line
-`,
-			Parse: func(p *parser) error {
-				_, err := p.ParseTraditionalFileHeader()
-				return err
-			},
-			EndLine: "@@ -1,2 +1,3 @@\n",
-		},
-		"ParseTextFragmentHeader": {
-			Input: `@@ -1,2 +1,3 @@
-context line
-`,
-			Parse: func(p *parser) error {
-				_, err := p.ParseTextFragmentHeader()
-				return err
-			},
-			EndLine: "context line\n",
-		},
-		"ParseTextChunk": {
-			Input: ` context line
--old line
-+new line
- context line
-@@ -1 +1 @@
-`,
-			Parse: func(p *parser) error {
-				return p.ParseTextChunk(&TextFragment{OldLines: 3, NewLines: 3})
-			},
-			EndLine: "@@ -1 +1 @@\n",
-		},
-		"ParseTextFragments": {
-			Input: `@@ -1,2 +1,2 @@
- context line
--old line
-+new line
-@@ -1,2 +1,2 @@
--old line
-+new line
- context line
-diff --git a/file.txt b/file.txt
-`,
-			Parse: func(p *parser) error {
-				_, err := p.ParseTextFragments(&File{})
-				return err
-			},
-			EndLine: "diff --git a/file.txt b/file.txt\n",
-		},
-		"ParseNextFileHeader": {
-			Input: `not a header
-diff --git a/file.txt b/file.txt
---- a/file.txt
-+++ b/file.txt
-@@ -1,2 +1,2 @@
-`,
-			Parse: func(p *parser) error {
-				_, _, err := p.ParseNextFileHeader()
-				return err
-			},
-			EndLine: "@@ -1,2 +1,2 @@\n",
-		},
-		"ParseBinaryMarker": {
-			Input: `Binary files differ
-diff --git a/file.txt b/file.txt
-`,
-			Parse: func(p *parser) error {
-				_, _, err := p.ParseBinaryMarker()
-				return err
-			},
-			EndLine: "diff --git a/file.txt b/file.txt\n",
-		},
-		"ParseBinaryFragmentHeader": {
-			Input: `literal 0
-HcmV?d00001
-`,
-			Parse: func(p *parser) error {
-				_, err := p.ParseBinaryFragmentHeader()
-				return err
-			},
-			EndLine: "HcmV?d00001\n",
-		},
-		"ParseBinaryChunk": {
-			Input: "TcmZQzU|?i`" + `U?w2V48*Je09XJG
-
-literal 0
-`,
-			Parse: func(p *parser) error {
-				return p.ParseBinaryChunk(&BinaryFragment{Size: 20})
-			},
-			EndLine: "literal 0\n",
-		},
-		"ParseBinaryFragments": {
-			Input: `GIT binary patch
-literal 40
-gcmZQzU|?i` + "`" + `U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
-
-literal 0
-HcmV?d00001
-
-diff --git a/file.txt b/file.txt
-`,
-			Parse: func(p *parser) error {
-				_, err := p.ParseBinaryFragments(&File{})
-				return err
-			},
-			EndLine: "diff --git a/file.txt b/file.txt\n",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			if err := test.Parse(p); err != nil {
-				t.Fatalf("unexpected error while parsing: %v", err)
-			}
-
-			if test.EndLine != p.Line(0) {
-				t.Errorf("incorrect position after parsing\nexpected: %q\n  actual: %q", test.EndLine, p.Line(0))
-			}
-		})
-	}
-}
-
-func TestParseNextFileHeader(t *testing.T) {
-	tests := map[string]struct {
-		Input    string
-		Output   *File
-		Preamble string
-		Err      bool
-	}{
-		"gitHeader": {
-			Input: `commit 1acbae563cd6ef5750a82ee64e116c6eb065cb94
-Author:	Morton Haypenny <mhaypenny@example.com>
-Date:	Tue Apr 2 22:30:00 2019 -0700
-
-    This is a sample commit message.
-
-diff --git a/file.txt b/file.txt
-index cc34da1..1acbae5 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1,3 +1,4 @@
-`,
-			Output: &File{
-				OldName:      "file.txt",
-				NewName:      "file.txt",
-				OldMode:      os.FileMode(0100644),
-				OldOIDPrefix: "cc34da1",
-				NewOIDPrefix: "1acbae5",
-			},
-			Preamble: `commit 1acbae563cd6ef5750a82ee64e116c6eb065cb94
-Author:	Morton Haypenny <mhaypenny@example.com>
-Date:	Tue Apr 2 22:30:00 2019 -0700
-
-    This is a sample commit message.
-
-`,
-		},
-		"traditionalHeader": {
-			Input: `
---- file.txt	2019-04-01 22:58:14.833597918 -0700
-+++ file.txt	2019-04-01 22:58:14.833597918 -0700
-@@ -1,3 +1,4 @@
-`,
-			Output: &File{
-				OldName: "file.txt",
-				NewName: "file.txt",
-			},
-			Preamble: "\n",
-		},
-		"noHeaders": {
-			Input: `
-this is a line
-this is another line
---- could this be a header?
-nope, it's just some dashes
-`,
-			Output: nil,
-			Preamble: `
-this is a line
-this is another line
---- could this be a header?
-nope, it's just some dashes
-`,
-		},
-		"detatchedFragmentLike": {
-			Input: `
-a wild fragment appears?
-@@ -1,3 +1,4 ~1,5 @@
-`,
-			Output: nil,
-			Preamble: `
-a wild fragment appears?
-@@ -1,3 +1,4 ~1,5 @@
-`,
-		},
-		"detatchedFragment": {
-			Input: `
-a wild fragment appears?
-@@ -1,3 +1,4 @@
-`,
-			Err: true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			f, pre, err := p.ParseNextFileHeader()
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing next file header, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing next file header: %v", err)
-			}
-
-			if test.Preamble != pre {
-				t.Errorf("incorrect preamble\nexpected: %q\n  actual: %q", test.Preamble, pre)
-			}
-			if !reflect.DeepEqual(test.Output, f) {
-				t.Errorf("incorrect file\nexpected: %+v\n  actual: %+v", test.Output, f)
-			}
-		})
-	}
-}
-
-func TestParse(t *testing.T) {
-	textFragments := []*TextFragment{
-		{
-			OldPosition: 3,
-			OldLines:    6,
-			NewPosition: 3,
-			NewLines:    8,
-			Comment:     "fragment 1",
-			Lines: []Line{
-				{OpContext, "context line\n"},
-				{OpDelete, "old line 1\n"},
-				{OpDelete, "old line 2\n"},
-				{OpContext, "context line\n"},
-				{OpAdd, "new line 1\n"},
-				{OpAdd, "new line 2\n"},
-				{OpAdd, "new line 3\n"},
-				{OpContext, "context line\n"},
-				{OpDelete, "old line 3\n"},
-				{OpAdd, "new line 4\n"},
-				{OpAdd, "new line 5\n"},
-			},
-			LinesAdded:     5,
-			LinesDeleted:   3,
-			LeadingContext: 1,
-		},
-		{
-			OldPosition: 31,
-			OldLines:    2,
-			NewPosition: 33,
-			NewLines:    2,
-			Comment:     "fragment 2",
-			Lines: []Line{
-				{OpContext, "context line\n"},
-				{OpDelete, "old line 4\n"},
-				{OpAdd, "new line 6\n"},
-			},
-			LinesAdded:     1,
-			LinesDeleted:   1,
-			LeadingContext: 1,
-		},
-	}
-
-	textPreamble := `commit 5d9790fec7d95aa223f3d20936340bf55ff3dcbe
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Tue Apr 2 22:55:40 2019 -0700
-
-    A file with multiple fragments.
-
-    The content is arbitrary.
-
-`
-
-	binaryPreamble := `commit 5d9790fec7d95aa223f3d20936340bf55ff3dcbe
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Tue Apr 2 22:55:40 2019 -0700
-
-    A binary file with the first 10 fibonacci numbers.
-
-`
-	tests := map[string]struct {
-		InputFile string
-		Output    []*File
-		Preamble  string
-		Err       bool
-	}{
-		"oneFile": {
-			InputFile: "testdata/one_file.patch",
-			Output: []*File{
-				{
-					OldName:       "dir/file1.txt",
-					NewName:       "dir/file1.txt",
-					OldMode:       os.FileMode(0100644),
-					OldOIDPrefix:  "ebe9fa54",
-					NewOIDPrefix:  "fe103e1d",
-					TextFragments: textFragments,
-				},
-			},
-			Preamble: textPreamble,
-		},
-		"twoFiles": {
-			InputFile: "testdata/two_files.patch",
-			Output: []*File{
-				{
-					OldName:       "dir/file1.txt",
-					NewName:       "dir/file1.txt",
-					OldMode:       os.FileMode(0100644),
-					OldOIDPrefix:  "ebe9fa54",
-					NewOIDPrefix:  "fe103e1d",
-					TextFragments: textFragments,
-				},
-				{
-					OldName:       "dir/file2.txt",
-					NewName:       "dir/file2.txt",
-					OldMode:       os.FileMode(0100644),
-					OldOIDPrefix:  "417ebc70",
-					NewOIDPrefix:  "67514b7f",
-					TextFragments: textFragments,
-				},
-			},
-			Preamble: textPreamble,
-		},
-		"noFiles": {
-			InputFile: "testdata/no_files.patch",
-			Output:    nil,
-			Preamble:  textPreamble,
-		},
-		"newBinaryFile": {
-			InputFile: "testdata/new_binary_file.patch",
-			Output: []*File{
-				{
-					OldName:      "",
-					NewName:      "dir/ten.bin",
-					NewMode:      os.FileMode(0100644),
-					OldOIDPrefix: "0000000000000000000000000000000000000000",
-					NewOIDPrefix: "77b068ba48c356156944ea714740d0d5ca07bfec",
-					IsNew:        true,
-					IsBinary:     true,
-					BinaryFragment: &BinaryFragment{
-						Method: BinaryPatchLiteral,
-						Size:   40,
-						Data:   fib(10, binary.BigEndian),
-					},
-					ReverseBinaryFragment: &BinaryFragment{
-						Method: BinaryPatchLiteral,
-						Size:   0,
-						Data:   []byte{},
-					},
-				},
-			},
-			Preamble: binaryPreamble,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			f, err := os.Open(test.InputFile)
-			if err != nil {
-				t.Fatalf("unexpected error opening input file: %v", err)
-			}
-
-			files, pre, err := Parse(f)
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing patch, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing patch: %v", err)
-			}
-
-			if len(test.Output) != len(files) {
-				t.Fatalf("incorrect number of parsed files: expected %d, actual %d", len(test.Output), len(files))
-			}
-			if test.Preamble != pre {
-				t.Errorf("incorrect preamble\nexpected: %q\n  actual: %q", test.Preamble, pre)
-			}
-			for i := range test.Output {
-				if !reflect.DeepEqual(test.Output[i], files[i]) {
-					exp, _ := json.MarshalIndent(test.Output[i], "", "  ")
-					act, _ := json.MarshalIndent(files[i], "", "  ")
-					t.Errorf("incorrect file at position %d\nexpected: %s\n  actual: %s", i, exp, act)
-				}
-			}
-		})
-	}
-}
-
-func newTestParser(input string, init bool) *parser {
-	p := newParser(bytes.NewBufferString(input))
-	if init {
-		_ = p.Next()
-	}
-	return p
-}
pkg/gitdiff/patch_header_test.go
@@ -1,590 +0,0 @@
-package gitdiff
-
-import (
-	"testing"
-	"time"
-)
-
-func TestParsePatchDate(t *testing.T) {
-	expected := time.Date(2020, 4, 9, 8, 7, 6, 0, time.UTC)
-
-	tests := map[string]struct {
-		Input  string
-		Output time.Time
-		Err    interface{}
-	}{
-		"default": {
-			Input:  "Thu Apr 9 01:07:06 2020 -0700",
-			Output: expected,
-		},
-		"defaultLocal": {
-			Input:  "Thu Apr 9 01:07:06 2020",
-			Output: time.Date(2020, 4, 9, 1, 7, 6, 0, time.Local),
-		},
-		"iso": {
-			Input:  "2020-04-09 01:07:06 -0700",
-			Output: expected,
-		},
-		"isoStrict": {
-			Input:  "2020-04-09T01:07:06-07:00",
-			Output: expected,
-		},
-		"rfc": {
-			Input:  "Thu, 9 Apr 2020 01:07:06 -0700",
-			Output: expected,
-		},
-		"short": {
-			Input:  "2020-04-09",
-			Output: time.Date(2020, 4, 9, 0, 0, 0, 0, time.Local),
-		},
-		"raw": {
-			Input:  "1586419626 -0700",
-			Output: expected,
-		},
-		"unix": {
-			Input:  "1586419626",
-			Output: expected,
-		},
-		"unknownFormat": {
-			Input: "4/9/2020 01:07:06 PDT",
-			Err:   "unknown date format",
-		},
-		"empty": {
-			Input: "",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			d, err := ParsePatchDate(test.Input)
-			if test.Err != nil {
-				assertError(t, test.Err, err, "parsing date")
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing date: %v", err)
-			}
-			if !test.Output.Equal(d) {
-				t.Errorf("incorrect parsed date: expected %v, actual %v", test.Output, d)
-			}
-		})
-	}
-}
-
-func TestParsePatchHeader(t *testing.T) {
-	expectedSHA := "61f5cd90bed4d204ee3feb3aa41ee91d4734855b"
-	expectedIdentity := &PatchIdentity{
-		Name:  "Morton Haypenny",
-		Email: "mhaypenny@example.com",
-	}
-	expectedDate := time.Date(2020, 04, 11, 15, 21, 23, 0, time.FixedZone("PDT", -7*60*60))
-	expectedTitle := "A sample commit to test header parsing"
-	expectedEmojiOneLineTitle := "🤖 Enabling auto-merging"
-	expectedEmojiMultiLineTitle := "[IA64] Put ia64 config files on the Uwe Kleine-König diet"
-	expectedBody := "The medium format shows the body, which\nmay wrap on to multiple lines.\n\nAnother body line."
-	expectedBodyAppendix := "CC: Joe Smith <joe.smith@company.com>"
-
-	tests := map[string]struct {
-		Input   string
-		Options []PatchHeaderOption
-		Header  PatchHeader
-		Err     interface{}
-	}{
-		"prettyShort": {
-			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author: Morton Haypenny <mhaypenny@example.com>
-
-    A sample commit to test header parsing
-`,
-			Header: PatchHeader{
-				SHA:    expectedSHA,
-				Author: expectedIdentity,
-				Title:  expectedTitle,
-			},
-		},
-		"prettyMedium": {
-			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Sat Apr 11 15:21:23 2020 -0700
-
-    A sample commit to test header parsing
-
-    The medium format shows the body, which
-    may wrap on to multiple lines.
-
-    Another body line.
-`,
-			Header: PatchHeader{
-				SHA:        expectedSHA,
-				Author:     expectedIdentity,
-				AuthorDate: expectedDate,
-				Title:      expectedTitle,
-				Body:       expectedBody,
-			},
-		},
-		"prettyFull": {
-			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author: Morton Haypenny <mhaypenny@example.com>
-Commit: Morton Haypenny <mhaypenny@example.com>
-
-    A sample commit to test header parsing
-
-    The medium format shows the body, which
-    may wrap on to multiple lines.
-
-    Another body line.
-`,
-			Header: PatchHeader{
-				SHA:       expectedSHA,
-				Author:    expectedIdentity,
-				Committer: expectedIdentity,
-				Title:     expectedTitle,
-				Body:      expectedBody,
-			},
-		},
-		"prettyFuller": {
-			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author:     Morton Haypenny <mhaypenny@example.com>
-AuthorDate: Sat Apr 11 15:21:23 2020 -0700
-Commit:     Morton Haypenny <mhaypenny@example.com>
-CommitDate: Sat Apr 11 15:21:23 2020 -0700
-
-    A sample commit to test header parsing
-
-    The medium format shows the body, which
-    may wrap on to multiple lines.
-
-    Another body line.
-`,
-			Header: PatchHeader{
-				SHA:           expectedSHA,
-				Author:        expectedIdentity,
-				AuthorDate:    expectedDate,
-				Committer:     expectedIdentity,
-				CommitterDate: expectedDate,
-				Title:         expectedTitle,
-				Body:          expectedBody,
-			},
-		},
-		"prettyAppendix": {
-			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author:     Morton Haypenny <mhaypenny@example.com>
-AuthorDate: Sat Apr 11 15:21:23 2020 -0700
-Commit:     Morton Haypenny <mhaypenny@example.com>
-CommitDate: Sat Apr 11 15:21:23 2020 -0700
-
-    A sample commit to test header parsing
-
-    The medium format shows the body, which
-    may wrap on to multiple lines.
-
-    Another body line.
-    ---
-    CC: Joe Smith <joe.smith@company.com>
-`,
-			Header: PatchHeader{
-				SHA:           expectedSHA,
-				Author:        expectedIdentity,
-				AuthorDate:    expectedDate,
-				Committer:     expectedIdentity,
-				CommitterDate: expectedDate,
-				Title:         expectedTitle,
-				Body:          expectedBody + "\n---\n" + expectedBodyAppendix,
-			},
-		},
-		"mailbox": {
-			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
-From: Morton Haypenny <mhaypenny@example.com>
-Date: Sat, 11 Apr 2020 15:21:23 -0700
-Subject: [PATCH] A sample commit to test header parsing
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
-`,
-			Header: PatchHeader{
-				SHA:        expectedSHA,
-				Author:     expectedIdentity,
-				AuthorDate: expectedDate,
-				Title:      expectedTitle,
-				Body:       expectedBody,
-			},
-		},
-		"mailboxPatchOnly": {
-			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
-From: Morton Haypenny <mhaypenny@example.com>
-Date: Sat, 11 Apr 2020 15:21:23 -0700
-Subject: [PATCH] [BUG-123] A sample commit to test header parsing
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
-`,
-			Options: []PatchHeaderOption{
-				WithSubjectCleanMode(SubjectCleanPatchOnly),
-			},
-			Header: PatchHeader{
-				SHA:        expectedSHA,
-				Author:     expectedIdentity,
-				AuthorDate: expectedDate,
-				Title:      "[BUG-123] " + expectedTitle,
-				Body:       expectedBody,
-			},
-		},
-		"mailboxEmojiOneLine": {
-			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
-From: Morton Haypenny <mhaypenny@example.com>
-Date: Sat, 11 Apr 2020 15:21:23 -0700
-Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Enabling=20auto-merging?=
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
-`,
-			Header: PatchHeader{
-				SHA:        expectedSHA,
-				Author:     expectedIdentity,
-				AuthorDate: expectedDate,
-				Title:      expectedEmojiOneLineTitle,
-				Body:       expectedBody,
-			},
-		},
-		"mailboxEmojiMultiLine": {
-			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
-From: Morton Haypenny <mhaypenny@example.com>
-Date: Sat, 11 Apr 2020 15:21:23 -0700
-Subject: [PATCH] =?UTF-8?q?[IA64]=20Put=20ia64=20config=20files=20on=20the=20?=
- =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20diet?=
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
-`,
-			Header: PatchHeader{
-				SHA:        expectedSHA,
-				Author:     expectedIdentity,
-				AuthorDate: expectedDate,
-				Title:      expectedEmojiMultiLineTitle,
-				Body:       expectedBody,
-			},
-		},
-		"mailboxRFC5322SpecialCharacters": {
-			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
-From: "dependabot[bot]" <12345+dependabot[bot]@users.noreply.github.com>
-Date: Sat, 11 Apr 2020 15:21:23 -0700
-Subject: [PATCH] A sample commit to test header parsing
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
-`,
-			Header: PatchHeader{
-				SHA: expectedSHA,
-				Author: &PatchIdentity{
-					Name:  "dependabot[bot]",
-					Email: "12345+dependabot[bot]@users.noreply.github.com",
-				},
-				AuthorDate: expectedDate,
-				Title:      expectedTitle,
-				Body:       expectedBody,
-			},
-		},
-		"mailboxAppendix": {
-			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
-From: Morton Haypenny <mhaypenny@example.com>
-Date: Sat, 11 Apr 2020 15:21:23 -0700
-Subject: [PATCH] A sample commit to test header parsing
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
----
-CC: Joe Smith <joe.smith@company.com>
-`,
-			Header: PatchHeader{
-				SHA:          expectedSHA,
-				Author:       expectedIdentity,
-				AuthorDate:   expectedDate,
-				Title:        expectedTitle,
-				Body:         expectedBody,
-				BodyAppendix: expectedBodyAppendix,
-			},
-		},
-		"mailboxMinimalNoName": {
-			Input: `From: <mhaypenny@example.com>
-Subject: [PATCH] A sample commit to test header parsing
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
-`,
-			Header: PatchHeader{
-				Author: &PatchIdentity{expectedIdentity.Email, expectedIdentity.Email},
-				Title:  expectedTitle,
-				Body:   expectedBody,
-			},
-		},
-		"mailboxMinimal": {
-			Input: `From: Morton Haypenny <mhaypenny@example.com>
-Subject: [PATCH] A sample commit to test header parsing
-
-The medium format shows the body, which
-may wrap on to multiple lines.
-
-Another body line.
-`,
-			Header: PatchHeader{
-				Author: expectedIdentity,
-				Title:  expectedTitle,
-				Body:   expectedBody,
-			},
-		},
-		"unwrapTitle": {
-			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Sat Apr 11 15:21:23 2020 -0700
-
-    A sample commit to test header parsing with a long
-	title that is wrapped.
-`,
-			Header: PatchHeader{
-				SHA:        expectedSHA,
-				Author:     expectedIdentity,
-				AuthorDate: expectedDate,
-				Title:      expectedTitle + " with a long title that is wrapped.",
-			},
-		},
-		"normalizeBodySpace": {
-			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author: Morton Haypenny <mhaypenny@example.com>
-Date:   Sat Apr 11 15:21:23 2020 -0700
-
-    A sample commit to test header parsing
-
-
-    The medium format shows the body, which
-    may wrap on to multiple lines.
-
-
-    Another body line.
-
-
-`,
-			Header: PatchHeader{
-				SHA:        expectedSHA,
-				Author:     expectedIdentity,
-				AuthorDate: expectedDate,
-				Title:      expectedTitle,
-				Body:       expectedBody,
-			},
-		},
-		"ignoreLeadingBlankLines": {
-			Input: `
-
-` + "    " + `
-commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
-Author: Morton Haypenny <mhaypenny@example.com>
-
-    A sample commit to test header parsing
-`,
-			Header: PatchHeader{
-				SHA:    expectedSHA,
-				Author: expectedIdentity,
-				Title:  expectedTitle,
-			},
-		},
-		"emptyHeader": {
-			Input:  "",
-			Header: PatchHeader{},
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			h, err := ParsePatchHeader(test.Input, test.Options...)
-			if test.Err != nil {
-				assertError(t, test.Err, err, "parsing patch header")
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing patch header: %v", err)
-			}
-			if h == nil {
-				t.Fatalf("expected non-nil header, but got nil")
-			}
-
-			exp := test.Header
-			act := *h
-
-			if exp.SHA != act.SHA {
-				t.Errorf("incorrect parsed SHA: expected %q, actual %q", exp.SHA, act.SHA)
-			}
-
-			assertPatchIdentity(t, "author", exp.Author, act.Author)
-			if !exp.AuthorDate.Equal(act.AuthorDate) {
-				t.Errorf("incorrect parsed author date: expected %v, but got %v", exp.AuthorDate, act.AuthorDate)
-			}
-
-			assertPatchIdentity(t, "committer", exp.Committer, act.Committer)
-			if !exp.CommitterDate.Equal(act.CommitterDate) {
-				t.Errorf("incorrect parsed committer date: expected %v, but got %v", exp.CommitterDate, act.CommitterDate)
-			}
-
-			if exp.Title != act.Title {
-				t.Errorf("incorrect parsed title:\n  expected: %q\n    actual: %q", exp.Title, act.Title)
-			}
-			if exp.Body != act.Body {
-				t.Errorf("incorrect parsed body:\n  expected: %q\n    actual: %q", exp.Body, act.Body)
-			}
-			if exp.BodyAppendix != act.BodyAppendix {
-				t.Errorf("incorrect parsed body appendix:\n  expected: %q\n    actual: %q",
-					exp.BodyAppendix, act.BodyAppendix)
-			}
-		})
-	}
-}
-
-func assertPatchIdentity(t *testing.T, kind string, exp, act *PatchIdentity) {
-	switch {
-	case exp == nil && act == nil:
-	case exp == nil && act != nil:
-		t.Errorf("incorrect parsed %s: expected nil, but got %+v", kind, act)
-	case exp != nil && act == nil:
-		t.Errorf("incorrect parsed %s: expected %+v, but got nil", kind, exp)
-	case exp.Name != act.Name || exp.Email != act.Email:
-		t.Errorf("incorrect parsed %s, expected %+v, bot got %+v", kind, exp, act)
-	}
-}
-
-func TestCleanSubject(t *testing.T) {
-	expectedSubject := "A sample commit to test header parsing"
-
-	tests := map[string]struct {
-		Input   string
-		Mode    SubjectCleanMode
-		Prefix  string
-		Subject string
-	}{
-		"CleanAll/noPrefix": {
-			Input:   expectedSubject,
-			Mode:    SubjectCleanAll,
-			Subject: expectedSubject,
-		},
-		"CleanAll/patchPrefix": {
-			Input:   "[PATCH] " + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Prefix:  "[PATCH] ",
-			Subject: expectedSubject,
-		},
-		"CleanAll/patchPrefixNoSpace": {
-			Input:   "[PATCH]" + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Prefix:  "[PATCH]",
-			Subject: expectedSubject,
-		},
-		"CleanAll/patchPrefixContent": {
-			Input:   "[PATCH 3/7] " + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Prefix:  "[PATCH 3/7] ",
-			Subject: expectedSubject,
-		},
-		"CleanAll/spacePrefix": {
-			Input:   "   " + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Subject: expectedSubject,
-		},
-		"CleanAll/replyLowerPrefix": {
-			Input:   "re: " + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Prefix:  "re: ",
-			Subject: expectedSubject,
-		},
-		"CleanAll/replyMixedPrefix": {
-			Input:   "Re: " + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Prefix:  "Re: ",
-			Subject: expectedSubject,
-		},
-		"CleanAll/replyCapsPrefix": {
-			Input:   "RE: " + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Prefix:  "RE: ",
-			Subject: expectedSubject,
-		},
-		"CleanAll/replyDoublePrefix": {
-			Input:   "Re: re: " + expectedSubject,
-			Mode:    SubjectCleanAll,
-			Prefix:  "Re: re: ",
-			Subject: expectedSubject,
-		},
-		"CleanAll/noPrefixSubjectHasRe": {
-			Input:   "Reimplement parsing",
-			Mode:    SubjectCleanAll,
-			Subject: "Reimplement parsing",
-		},
-		"CleanAll/patchPrefixSubjectHasRe": {
-			Input:   "[PATCH 1/2] Reimplement parsing",
-			Mode:    SubjectCleanAll,
-			Prefix:  "[PATCH 1/2] ",
-			Subject: "Reimplement parsing",
-		},
-		"CleanAll/unclosedPrefix": {
-			Input:   "[Just to annoy people",
-			Mode:    SubjectCleanAll,
-			Subject: "[Just to annoy people",
-		},
-		"CleanAll/multiplePrefix": {
-			Input:   " Re:Re: [PATCH 1/2][DRAFT] " + expectedSubject + "  ",
-			Mode:    SubjectCleanAll,
-			Prefix:  "Re:Re: [PATCH 1/2][DRAFT] ",
-			Subject: expectedSubject,
-		},
-		"CleanPatchOnly/patchPrefix": {
-			Input:   "[PATCH] " + expectedSubject,
-			Mode:    SubjectCleanPatchOnly,
-			Prefix:  "[PATCH] ",
-			Subject: expectedSubject,
-		},
-		"CleanPatchOnly/mixedPrefix": {
-			Input:   "[PATCH] [TICKET-123] " + expectedSubject,
-			Mode:    SubjectCleanPatchOnly,
-			Prefix:  "[PATCH] ",
-			Subject: "[TICKET-123] " + expectedSubject,
-		},
-		"CleanPatchOnly/multiplePrefix": {
-			Input:   "Re:Re: [PATCH 1/2][DRAFT] " + expectedSubject,
-			Mode:    SubjectCleanPatchOnly,
-			Prefix:  "Re:Re: [PATCH 1/2]",
-			Subject: "[DRAFT] " + expectedSubject,
-		},
-		"CleanWhitespace/leadingSpace": {
-			Input:   "    [PATCH] " + expectedSubject,
-			Mode:    SubjectCleanWhitespace,
-			Subject: "[PATCH] " + expectedSubject,
-		},
-		"CleanWhitespace/trailingSpace": {
-			Input:   "[PATCH] " + expectedSubject + "   ",
-			Mode:    SubjectCleanWhitespace,
-			Subject: "[PATCH] " + expectedSubject,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			prefix, subject := cleanSubject(test.Input, test.Mode)
-			if prefix != test.Prefix {
-				t.Errorf("incorrect prefix: expected %q, actual %q", test.Prefix, prefix)
-			}
-			if subject != test.Subject {
-				t.Errorf("incorrect subject: expected %q, actual %q", test.Subject, subject)
-			}
-		})
-	}
-}
pkg/gitdiff/patch_identity_test.go
@@ -1,127 +0,0 @@
-package gitdiff
-
-import (
-	"testing"
-)
-
-func TestParsePatchIdentity(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output PatchIdentity
-		Err    interface{}
-	}{
-		"simple": {
-			Input: "Morton Haypenny <mhaypenny@example.com>",
-			Output: PatchIdentity{
-				Name:  "Morton Haypenny",
-				Email: "mhaypenny@example.com",
-			},
-		},
-		"extraWhitespace": {
-			Input: "\t  Morton Haypenny  \r\n<mhaypenny@example.com>  ",
-			Output: PatchIdentity{
-				Name:  "Morton Haypenny",
-				Email: "mhaypenny@example.com",
-			},
-		},
-		"trailingCharacters": {
-			Input: "Morton Haypenny <mhaypenny@example.com> II",
-			Output: PatchIdentity{
-				Name:  "Morton Haypenny II",
-				Email: "mhaypenny@example.com",
-			},
-		},
-		"onlyEmail": {
-			Input: "mhaypenny@example.com",
-			Output: PatchIdentity{
-				Name:  "mhaypenny@example.com",
-				Email: "mhaypenny@example.com",
-			},
-		},
-		"onlyEmailInBrackets": {
-			Input: "<mhaypenny@example.com>",
-			Output: PatchIdentity{
-				Name:  "mhaypenny@example.com",
-				Email: "mhaypenny@example.com",
-			},
-		},
-		"rfc5322SpecialCharacters": {
-			Input: `"dependabot[bot]" <12345+dependabot[bot]@users.noreply.github.com>`,
-			Output: PatchIdentity{
-				Name:  "dependabot[bot]",
-				Email: "12345+dependabot[bot]@users.noreply.github.com",
-			},
-		},
-		"rfc5322QuotedPairs": {
-			Input: `"Morton \"Old-Timer\" Haypenny" <"mhaypenny\+[1900]"@example.com> (III \(PhD\))`,
-			Output: PatchIdentity{
-				Name:  `Morton "Old-Timer" Haypenny (III (PhD))`,
-				Email: "mhaypenny+[1900]@example.com",
-			},
-		},
-		"rfc5322QuotedPairsOutOfContext": {
-			Input: `Morton \\Backslash Haypenny <mhaypenny@example.com>`,
-			Output: PatchIdentity{
-				Name:  `Morton \\Backslash Haypenny`,
-				Email: "mhaypenny@example.com",
-			},
-		},
-		"emptyEmail": {
-			Input: "Morton Haypenny <>",
-			Output: PatchIdentity{
-				Name:  "Morton Haypenny",
-				Email: "",
-			},
-		},
-		"unclosedEmail": {
-			Input: "Morton Haypenny <mhaypenny@example.com",
-			Output: PatchIdentity{
-				Name:  "Morton Haypenny",
-				Email: "mhaypenny@example.com",
-			},
-		},
-		"bogusEmail": {
-			Input: "Morton Haypenny <mhaypenny>",
-			Output: PatchIdentity{
-				Name:  "Morton Haypenny",
-				Email: "mhaypenny",
-			},
-		},
-		"bogusEmailWithWhitespace": {
-			Input: "Morton Haypenny <  mhaypenny  >",
-			Output: PatchIdentity{
-				Name:  "Morton Haypenny",
-				Email: "mhaypenny",
-			},
-		},
-		"missingEmail": {
-			Input: "Morton Haypenny",
-			Err:   "invalid identity",
-		},
-		"missingNameAndEmptyEmail": {
-			Input: "<>",
-			Err:   "invalid identity",
-		},
-		"empty": {
-			Input: "",
-			Err:   "invalid identity",
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			id, err := ParsePatchIdentity(test.Input)
-			if test.Err != nil {
-				assertError(t, test.Err, err, "parsing identity")
-				return
-			}
-			if err != nil {
-				t.Fatalf("unexpected error parsing identity: %v", err)
-			}
-
-			if test.Output != id {
-				t.Errorf("incorrect identity: expected %#v, actual %#v", test.Output, id)
-			}
-		})
-	}
-}
pkg/gitdiff/README.md
@@ -1,1 +0,0 @@
-This is a vendored copy of the [bluekeyes/go-gitdiff](https://github.com/bluekeyes/go-gitdiff) package.
pkg/gitdiff/text_test.go
@@ -1,488 +0,0 @@
-package gitdiff
-
-import (
-	"io"
-	"reflect"
-	"testing"
-)
-
-func TestParseTextFragmentHeader(t *testing.T) {
-	tests := map[string]struct {
-		Input  string
-		Output *TextFragment
-		Err    bool
-	}{
-		"shortest": {
-			Input: "@@ -1 +1 @@\n",
-			Output: &TextFragment{
-				OldPosition: 1,
-				OldLines:    1,
-				NewPosition: 1,
-				NewLines:    1,
-			},
-		},
-		"standard": {
-			Input: "@@ -21,5 +28,9 @@\n",
-			Output: &TextFragment{
-				OldPosition: 21,
-				OldLines:    5,
-				NewPosition: 28,
-				NewLines:    9,
-			},
-		},
-		"trailingComment": {
-			Input: "@@ -21,5 +28,9 @@ func test(n int) {\n",
-			Output: &TextFragment{
-				Comment:     "func test(n int) {",
-				OldPosition: 21,
-				OldLines:    5,
-				NewPosition: 28,
-				NewLines:    9,
-			},
-		},
-		"incomplete": {
-			Input: "@@ -12,3 +2\n",
-			Err:   true,
-		},
-		"badNumbers": {
-			Input: "@@ -1a,2b +3c,4d @@\n",
-			Err:   true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			frag, err := p.ParseTextFragmentHeader()
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing header, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("error parsing header: %v", err)
-			}
-
-			if !reflect.DeepEqual(test.Output, frag) {
-				t.Errorf("incorrect fragment\nexpected: %+v\nactual: %+v", test.Output, frag)
-			}
-		})
-	}
-}
-
-func TestParseTextChunk(t *testing.T) {
-	tests := map[string]struct {
-		Input    string
-		Fragment TextFragment
-
-		Output *TextFragment
-		Err    bool
-	}{
-		"addWithContext": {
-			Input: ` context line
-+new line 1
-+new line 2
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 2,
-				NewLines: 4,
-			},
-			Output: &TextFragment{
-				OldLines: 2,
-				NewLines: 4,
-				Lines: []Line{
-					{OpContext, "context line\n"},
-					{OpAdd, "new line 1\n"},
-					{OpAdd, "new line 2\n"},
-					{OpContext, "context line\n"},
-				},
-				LinesAdded:      2,
-				LeadingContext:  1,
-				TrailingContext: 1,
-			},
-		},
-		"deleteWithContext": {
-			Input: ` context line
--old line 1
--old line 2
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 4,
-				NewLines: 2,
-			},
-			Output: &TextFragment{
-				OldLines: 4,
-				NewLines: 2,
-				Lines: []Line{
-					{OpContext, "context line\n"},
-					{OpDelete, "old line 1\n"},
-					{OpDelete, "old line 2\n"},
-					{OpContext, "context line\n"},
-				},
-				LinesDeleted:    2,
-				LeadingContext:  1,
-				TrailingContext: 1,
-			},
-		},
-		"replaceWithContext": {
-			Input: ` context line
--old line 1
-+new line 1
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 3,
-				NewLines: 3,
-			},
-			Output: &TextFragment{
-				OldLines: 3,
-				NewLines: 3,
-				Lines: []Line{
-					{OpContext, "context line\n"},
-					{OpDelete, "old line 1\n"},
-					{OpAdd, "new line 1\n"},
-					{OpContext, "context line\n"},
-				},
-				LinesDeleted:    1,
-				LinesAdded:      1,
-				LeadingContext:  1,
-				TrailingContext: 1,
-			},
-		},
-		"middleContext": {
-			Input: ` context line
--old line 1
- context line
-+new line 1
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 4,
-				NewLines: 4,
-			},
-			Output: &TextFragment{
-				OldLines: 4,
-				NewLines: 4,
-				Lines: []Line{
-					{OpContext, "context line\n"},
-					{OpDelete, "old line 1\n"},
-					{OpContext, "context line\n"},
-					{OpAdd, "new line 1\n"},
-					{OpContext, "context line\n"},
-				},
-				LinesDeleted:    1,
-				LinesAdded:      1,
-				LeadingContext:  1,
-				TrailingContext: 1,
-			},
-		},
-		"deleteFinalNewline": {
-			Input: ` context line
--old line 1
-+new line 1
-\ No newline at end of file
-`,
-			Fragment: TextFragment{
-				OldLines: 2,
-				NewLines: 2,
-			},
-			Output: &TextFragment{
-				OldLines: 2,
-				NewLines: 2,
-				Lines: []Line{
-					{OpContext, "context line\n"},
-					{OpDelete, "old line 1\n"},
-					{OpAdd, "new line 1"},
-				},
-				LinesDeleted:   1,
-				LinesAdded:     1,
-				LeadingContext: 1,
-			},
-		},
-		"addFinalNewline": {
-			Input: ` context line
--old line 1
-\ No newline at end of file
-+new line 1
-`,
-			Fragment: TextFragment{
-				OldLines: 2,
-				NewLines: 2,
-			},
-			Output: &TextFragment{
-				OldLines: 2,
-				NewLines: 2,
-				Lines: []Line{
-					{OpContext, "context line\n"},
-					{OpDelete, "old line 1"},
-					{OpAdd, "new line 1\n"},
-				},
-				LinesDeleted:   1,
-				LinesAdded:     1,
-				LeadingContext: 1,
-			},
-		},
-		"addAll": {
-			Input: `+new line 1
-+new line 2
-+new line 3
-`,
-			Fragment: TextFragment{
-				OldLines: 0,
-				NewLines: 3,
-			},
-			Output: &TextFragment{
-				OldLines: 0,
-				NewLines: 3,
-				Lines: []Line{
-					{OpAdd, "new line 1\n"},
-					{OpAdd, "new line 2\n"},
-					{OpAdd, "new line 3\n"},
-				},
-				LinesAdded: 3,
-			},
-		},
-		"deleteAll": {
-			Input: `-old line 1
--old line 2
--old line 3
-`,
-			Fragment: TextFragment{
-				OldLines: 3,
-				NewLines: 0,
-			},
-			Output: &TextFragment{
-				OldLines: 3,
-				NewLines: 0,
-				Lines: []Line{
-					{OpDelete, "old line 1\n"},
-					{OpDelete, "old line 2\n"},
-					{OpDelete, "old line 3\n"},
-				},
-				LinesDeleted: 3,
-			},
-		},
-		"emptyContextLine": {
-			Input: ` context line
-
-+new line
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 3,
-				NewLines: 4,
-			},
-			Output: &TextFragment{
-				OldLines: 3,
-				NewLines: 4,
-				Lines: []Line{
-					{OpContext, "context line\n"},
-					{OpContext, "\n"},
-					{OpAdd, "new line\n"},
-					{OpContext, "context line\n"},
-				},
-				LinesAdded:      1,
-				LeadingContext:  2,
-				TrailingContext: 1,
-			},
-		},
-		"emptyChunk": {
-			Input: "",
-			Err:   true,
-		},
-		"invalidOperation": {
-			Input: ` context line
-?wat line
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 3,
-				NewLines: 3,
-			},
-			Err: true,
-		},
-		"unbalancedHeader": {
-			Input: ` context line
--old line 1
-+new line 1
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 2,
-				NewLines: 5,
-			},
-			Err: true,
-		},
-		"onlyContext": {
-			Input: ` context line
- context line
-`,
-			Fragment: TextFragment{
-				OldLines: 2,
-				NewLines: 2,
-			},
-			Err: true,
-		},
-		"unexpectedNoNewlineMarker": {
-			Input: `\ No newline at end of file`,
-			Fragment: TextFragment{
-				OldLines: 1,
-				NewLines: 1,
-			},
-			Err: true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			frag := test.Fragment
-			err := p.ParseTextChunk(&frag)
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing text chunk, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("error parsing text chunk: %v", err)
-			}
-
-			if !reflect.DeepEqual(test.Output, &frag) {
-				t.Errorf("incorrect fragment\nexpected: %+v\nactual: %+v", test.Output, &frag)
-			}
-		})
-	}
-}
-
-func TestParseTextFragments(t *testing.T) {
-	tests := map[string]struct {
-		Input string
-		File  File
-
-		Fragments []*TextFragment
-		Err       bool
-	}{
-		"multipleChanges": {
-			Input: `@@ -1,3 +1,2 @@
- context line
--old line 1
- context line
-@@ -8,3 +7,3 @@
- context line
--old line 2
-+new line 1
- context line
-@@ -15,3 +14,4 @@
- context line
--old line 3
-+new line 2
-+new line 3
- context line
-`,
-			Fragments: []*TextFragment{
-				{
-					OldPosition: 1,
-					OldLines:    3,
-					NewPosition: 1,
-					NewLines:    2,
-					Lines: []Line{
-						{OpContext, "context line\n"},
-						{OpDelete, "old line 1\n"},
-						{OpContext, "context line\n"},
-					},
-					LinesDeleted:    1,
-					LeadingContext:  1,
-					TrailingContext: 1,
-				},
-				{
-					OldPosition: 8,
-					OldLines:    3,
-					NewPosition: 7,
-					NewLines:    3,
-					Lines: []Line{
-						{OpContext, "context line\n"},
-						{OpDelete, "old line 2\n"},
-						{OpAdd, "new line 1\n"},
-						{OpContext, "context line\n"},
-					},
-					LinesDeleted:    1,
-					LinesAdded:      1,
-					LeadingContext:  1,
-					TrailingContext: 1,
-				},
-				{
-					OldPosition: 15,
-					OldLines:    3,
-					NewPosition: 14,
-					NewLines:    4,
-					Lines: []Line{
-						{OpContext, "context line\n"},
-						{OpDelete, "old line 3\n"},
-						{OpAdd, "new line 2\n"},
-						{OpAdd, "new line 3\n"},
-						{OpContext, "context line\n"},
-					},
-					LinesDeleted:    1,
-					LinesAdded:      2,
-					LeadingContext:  1,
-					TrailingContext: 1,
-				},
-			},
-		},
-		"badNewFile": {
-			Input: `@@ -1 +1,2 @@
--old line 1
-+new line 1
-+new line 2
-`,
-			File: File{
-				IsNew: true,
-			},
-			Err: true,
-		},
-		"badDeletedFile": {
-			Input: `@@ -1,2 +1 @@
--old line 1
- context line
-`,
-			File: File{
-				IsDelete: true,
-			},
-			Err: true,
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			p := newTestParser(test.Input, true)
-
-			file := test.File
-			n, err := p.ParseTextFragments(&file)
-			if test.Err {
-				if err == nil || err == io.EOF {
-					t.Fatalf("expected error parsing text fragments, but got %v", err)
-				}
-				return
-			}
-			if err != nil {
-				t.Fatalf("error parsing text fragments: %v", err)
-			}
-
-			if len(test.Fragments) != n {
-				t.Fatalf("incorrect number of added fragments: expected %d, actual %d", len(test.Fragments), n)
-			}
-
-			for i, frag := range test.Fragments {
-				if !reflect.DeepEqual(frag, file.TextFragments[i]) {
-					t.Errorf("incorrect fragment at position %d\nexpected: %+v\nactual: %+v", i, frag, file.TextFragments[i])
-				}
-			}
-		})
-	}
-}
pkg/links/links.go
@@ -8,7 +8,7 @@ import (
 
 	"golang.org/x/net/html"
 
-	"github.com/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
 )
 
 type Set map[string]struct{}
pkg/templates/blob.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.BlobParams */ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.BlobParams */ -}}
 {{ define "head" }}
     <style>
         {{ .CSS }}
pkg/templates/branches.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.BranchesParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.BranchesParams*/ -}}
 {{ define "head" }}
     <style>
       .branches {
pkg/templates/commit.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.CommitParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.CommitParams*/ -}}
 {{ define "head" }}
     <style>
       h1 code {
pkg/templates/commits_list.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.CommitsListParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.CommitsListParams*/ -}}
 {{ define "head" }}
     <style>
       .commits {
pkg/templates/file_tree.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype:github.com/antonmedv/gitmal/pkg/templates.FileTreeParams*/ -}}
+{{- /*gotype:mokhan.ca/antonmedv/gitmal/pkg/templates.FileTreeParams*/ -}}
 {{ define "file_tree" }}
     {{ range .Nodes }}
         {{ if .IsDir }}
pkg/templates/header.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.HeaderParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.HeaderParams*/ -}}
 {{ define "header" }}
     <div class="header-container">
         <header>
pkg/templates/layout.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.LayoutParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.LayoutParams*/ -}}
 <!DOCTYPE html>
 <html lang="en">
 <head>
@@ -313,7 +313,6 @@
     </div>
 </main>
 <footer>
-    Generated by <a href="https://github.com/antonmedv/gitmal">Gitmal</a>
 </footer>
 </body>
 </html>
pkg/templates/list.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.ListParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.ListParams*/ -}}
 {{ define "head" }}
     <style>
       .files {
pkg/templates/markdown.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.MarkdownParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.MarkdownParams*/ -}}
 {{ define "head" }}
     <style>
       [id] {
pkg/templates/preview.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.PreviewParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.PreviewParams*/ -}}
 <!DOCTYPE html>
 <html lang="en">
 <head>
pkg/templates/tags.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.TagsParams*/ -}}
+{{- /*gotype: mokhan.ca/antonmedv/gitmal/pkg/templates.TagsParams*/ -}}
 {{ define "head" }}
     <style>
       .tags {
pkg/templates/templates.go
@@ -7,7 +7,7 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
 )
 
 var funcs = FuncMap{
blob.go
@@ -15,10 +15,10 @@ import (
 	"github.com/alecthomas/chroma/v2/lexers"
 	"github.com/alecthomas/chroma/v2/styles"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/links"
-	"github.com/antonmedv/gitmal/pkg/progress_bar"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/links"
+	"mokhan.ca/antonmedv/gitmal/pkg/progress_bar"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 func generateBlobs(files []git.Blob, params Params) error {
branches.go
@@ -6,8 +6,8 @@ import (
 	"path/filepath"
 	"sort"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 // generateBranches creates a branches.html page at the root of the output
commit.go
@@ -17,10 +17,10 @@ import (
 	"github.com/alecthomas/chroma/v2/lexers"
 	"github.com/alecthomas/chroma/v2/styles"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/gitdiff"
-	"github.com/antonmedv/gitmal/pkg/progress_bar"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/gitdiff"
+	"mokhan.ca/antonmedv/gitmal/pkg/progress_bar"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 func generateCommits(commits map[string]git.Commit, params Params) error {
commits_list.go
@@ -6,9 +6,9 @@ import (
 	"path/filepath"
 	"slices"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/progress_bar"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/progress_bar"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 const commitsPerPage = 100
Dockerfile
@@ -1,24 +0,0 @@
-FROM golang:latest as builder
-
-WORKDIR /go
-
-COPY go.mod go.sum ./
-
-RUN go mod download
-
-COPY . .
-
-RUN CGO_ENABLED=0 go build -o gitmal .
-
-
-FROM alpine
-
-RUN apk add --no-cache git
-
-COPY --from=builder /go/gitmal /bin/gitmal
-
-WORKDIR /data
-
-ENV COLORTERM=truecolor
-
-ENTRYPOINT ["/bin/gitmal"]
go.mod
@@ -1,4 +1,4 @@
-module github.com/antonmedv/gitmal
+module mokhan.ca/antonmedv/gitmal
 
 go 1.24.0
 
index.go
@@ -6,9 +6,9 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/links"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/links"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 func generateIndex(files []git.Blob, params Params) error {
LICENSE
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2025 Anton Medvedev
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
list.go
@@ -11,10 +11,10 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/links"
-	"github.com/antonmedv/gitmal/pkg/progress_bar"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/links"
+	"mokhan.ca/antonmedv/gitmal/pkg/progress_bar"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 func generateLists(files []git.Blob, params Params) error {
main.go
@@ -8,7 +8,7 @@ import (
 	"runtime/pprof"
 	"strings"
 
-	"github.com/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
 
 	flag "github.com/spf13/pflag"
 )
markdown.go
@@ -9,7 +9,7 @@ import (
 	"github.com/yuin/goldmark/parser"
 	gmhtml "github.com/yuin/goldmark/renderer/html"
 
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 func createMarkdown(style string) goldmark.Markdown {
post_process.go
@@ -16,7 +16,7 @@ import (
 	"github.com/tdewolff/minify/v2/html"
 	"github.com/tdewolff/minify/v2/svg"
 
-	"github.com/antonmedv/gitmal/pkg/progress_bar"
+	"mokhan.ca/antonmedv/gitmal/pkg/progress_bar"
 )
 
 func postProcessHTML(root string, doMinify bool, doGzip bool) error {
readme.go
@@ -5,8 +5,8 @@ import (
 	"html/template"
 	"strings"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/links"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/links"
 )
 
 func readme(files []git.Blob, dirsSet, filesSet links.Set, params Params, rootHref string) template.HTML {
README.md
@@ -1,67 +0,0 @@
-<p align="center"><img src="img/gitmal-color-logo.webp" alt="Gitmal" width="330" height="330"></p>
-
-# Gitmal
-
-Gitmal is a static page generator for Git repositories. Gitmal generates static HTML pages with files, commits,
-code highlighting, and markdown rendering.
-
-## Installation
-
-```sh
-go install github.com/antonmedv/gitmal@latest
-```
-
-```sh
-docker run --rm -v $(pwd):/repo antonmedv/gitmal /repo
-```
-
-Or download prebuilt binary from [releases](https://github.com/antonmedv/gitmal/releases).
-
-## Usage
-
-Run gitmal in the repository dir. Gitmal will generate pages in _./output_ directory.
-
-```sh
-gitmal .
-```
-
-Run gitmal with `--help` flag, go get a list of available options.
-
-```sh
-gitmal --help
-```
-
-## Screenshots
-
-<p align="center">
-  <a href="img/gitmal-screenshot-code-highlighting.webp"><img src="img/gitmal-screenshot-code-highlighting.webp" alt="Gitmal Code Highlighting" width="400"></a>
-  <a href="img/gitmal-screenshot-file-tree.webp"><img src="img/gitmal-screenshot-file-tree.webp" alt="Gitmal File Tree" width="400"></a><br>
-  <a href="img/gitmal-screenshot-files.webp"><img src="img/gitmal-screenshot-files.webp" alt="Gitmal Files Page" width="400"></a>
-</p>
-
-## Examples
-
-Here are a few examples of repos hosted on my website:
-
-- [git.medv.io/zx/](https://git.medv.io/zx/) — github.com/google/zx
-- [git.medv.io/zig/](https://git.medv.io/zig/) — codeberg.org/ziglang/zig (light theme)
-- [git.medv.io/my-badges/](https://git.medv.io/my-badges/) — github.com/my-badges/my-badges
-
-Gitmal on kubernetes repository works as well. Generation on my MacBook Air M2 with `--minify` and `--gzip` flags
-takes around 25 minutes, and the generated files weigh around 2 GB.
-
-## Themes
-
-Gitmal supports different code highlighting themes. You can customize the theme with `--theme` flag.
-
-```sh
-gitmal --theme github-dark
-```
-
-## Documentation
-
-- [How to Self-Host a Git Repository?](./docs/how-to-self-host-a-git-repository.md)
-
-## License
-
-[MIT](LICENSE)
RELEASE.md
@@ -1,7 +0,0 @@
-# Release
-
-1. Create a [release](https://github.com/antonmedv/gitmal/releases/new) on GitHub.
-2. Run [build.mjs](.github/scripts/build.mjs) to build and upload binaries.
-   ```sh
-   npx zx .github/scripts/build.mjs
-   ```
tags.go
@@ -5,8 +5,8 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 func generateTags(entries []git.Tag, params Params) error {
themes.go
@@ -11,7 +11,7 @@ import (
 	"github.com/alecthomas/chroma/v2/lexers"
 	"github.com/alecthomas/chroma/v2/styles"
 
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 var themeStyles = map[string]string{
utils.go
@@ -6,8 +6,8 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/antonmedv/gitmal/pkg/git"
-	"github.com/antonmedv/gitmal/pkg/templates"
+	"mokhan.ca/antonmedv/gitmal/pkg/git"
+	"mokhan.ca/antonmedv/gitmal/pkg/templates"
 )
 
 const dot = "·"