func parseGitHeaderData(f *File, line, defaultName string) (end bool, err error) {
	if len(line) > 0 && line[len(line)-1] == '\n' {
		line = line[:len(line)-1]
	}
	for _, hdr := range []struct {
		prefix string
		end    bool
		parse  func(*File, string, string) error
	}{
		{"@@ -", true, nil},
		{"--- ", false, parseGitHeaderOldName},
		{"+++ ", false, parseGitHeaderNewName},
		{"old mode ", false, parseGitHeaderOldMode},
		{"new mode ", false, parseGitHeaderNewMode},
		{"deleted file mode ", false, parseGitHeaderDeletedMode},
		{"new file mode ", false, parseGitHeaderCreatedMode},
		{"copy from ", false, parseGitHeaderCopyFrom},
		{"copy to ", false, parseGitHeaderCopyTo},
		{"rename old ", false, parseGitHeaderRenameFrom},
		{"rename new ", false, parseGitHeaderRenameTo},
		{"rename from ", false, parseGitHeaderRenameFrom},
		{"rename to ", false, parseGitHeaderRenameTo},
		{"similarity index ", false, parseGitHeaderScore},
		{"dissimilarity index ", false, parseGitHeaderScore},
		{"index ", false, parseGitHeaderIndex},
	} {
		if strings.HasPrefix(line, hdr.prefix) {
			if hdr.parse != nil {
				err = hdr.parse(f, line[len(hdr.prefix):], defaultName)
			}
			return hdr.end, err
		}
	}
	return true, nil
}
parseGitHeaderData parses a single line of metadata from a Git file header.
It returns true when header parsing is complete; in that case, line was the
first line of non-header content.
package gitdiff
import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"
)
const (
	devNull = "/dev/null"
)
func (p *parser) ParseNextFileHeader() (*File, string, error) {
	var preamble strings.Builder
	var file *File
	for {
		frag, err := p.ParseTextFragmentHeader()
		if err != nil {
			goto NextLine
		}
		if frag != nil {
			return nil, "", p.Errorf(-1, "patch fragment without file header: %s", frag.Header())
		}
		file, err = p.ParseGitFileHeader()
		if err != nil {
			return nil, "", err
		}
		if file != nil {
			return file, preamble.String(), nil
		}
		file, err = p.ParseTraditionalFileHeader()
		if err != nil {
			return nil, "", err
		}
		if file != nil {
			return file, preamble.String(), nil
		}
	NextLine:
		preamble.WriteString(p.Line(0))
		if err := p.Next(); err != nil {
			if err == io.EOF {
				break
			}
			return nil, "", err
		}
	}
	return nil, "", nil
}
func (p *parser) ParseGitFileHeader() (*File, error) {
	const prefix = "diff --git "
	if !strings.HasPrefix(p.Line(0), prefix) {
		return nil, nil
	}
	header := p.Line(0)[len(prefix):]
	defaultName, err := parseGitHeaderName(header)
	if err != nil {
		return nil, p.Errorf(0, "git file header: %v", err)
	}
	f := &File{}
	for {
		end, err := parseGitHeaderData(f, p.Line(1), defaultName)
		if err != nil {
			return nil, p.Errorf(1, "git file header: %v", err)
		}
		if err := p.Next(); err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
		if end {
			break
		}
	}
	if f.OldName == "" && f.NewName == "" {
		if defaultName == "" {
			return nil, p.Errorf(0, "git file header: missing filename information")
		}
		f.OldName = defaultName
		f.NewName = defaultName
	}
	if (f.NewName == "" && !f.IsDelete) || (f.OldName == "" && !f.IsNew) {
		return nil, p.Errorf(0, "git file header: missing filename information")
	}
	return f, nil
}
func (p *parser) ParseTraditionalFileHeader() (*File, error) {
	const shortestValidFragHeader = "@@ -1 +1 @@\n"
	const (
		oldPrefix = "--- "
		newPrefix = "+++ "
	)
	oldLine, newLine := p.Line(0), p.Line(1)
	if !strings.HasPrefix(oldLine, oldPrefix) || !strings.HasPrefix(newLine, newPrefix) {
		return nil, nil
	}
	if len(p.Line(2)) < len(shortestValidFragHeader) || !strings.HasPrefix(p.Line(2), "@@ -") {
		return nil, nil
	}
	if err := p.Next(); err != nil {
		return nil, err
	}
	if err := p.Next(); err != nil {
		return nil, err
	}
	oldName, _, err := parseName(oldLine[len(oldPrefix):], '\t', 0)
	if err != nil {
		return nil, p.Errorf(0, "file header: %v", err)
	}
	newName, _, err := parseName(newLine[len(newPrefix):], '\t', 0)
	if err != nil {
		return nil, p.Errorf(1, "file header: %v", err)
	}
	f := &File{}
	switch {
	case oldName == devNull || hasEpochTimestamp(oldLine):
		f.IsNew = true
		f.NewName = newName
	case newName == devNull || hasEpochTimestamp(newLine):
		f.IsDelete = true
		f.OldName = oldName
	default:
		if strings.HasPrefix(newName, oldName) {
			f.OldName = oldName
			f.NewName = oldName
		} else {
			f.OldName = newName
			f.NewName = newName
		}
	}
	return f, nil
}
func parseGitHeaderName(header string) (string, error) {
	header = strings.TrimSuffix(header, "\n")
	if len(header) == 0 {
		return "", nil
	}
	var err error
	var first, second string
	quote := strings.IndexByte(header, '"')
	switch {
	case quote < 0:
		first = header
	case quote > 0:
		first = header[:quote-1]
		if !isSpace(header[quote-1]) {
			return "", fmt.Errorf("missing separator")
		}
		second, _, err = parseQuotedName(header[quote:])
		if err != nil {
			return "", err
		}
	case quote == 0:
		var n int
		first, n, err = parseQuotedName(header)
		if err != nil {
			return "", err
		}
		for n < len(header) && isSpace(header[n]) {
			n++
		}
		if n == len(header) {
			return "", nil
		}
		if header[n] == '"' {
			second, _, err = parseQuotedName(header[n:])
			if err != nil {
				return "", err
			}
		} else {
			second = header[n:]
		}
	}
	first = trimTreePrefix(first, 1)
	if second != "" {
		if first == trimTreePrefix(second, 1) {
			return first, nil
		}
		return "", nil
	}
	for i := 0; i < len(first)-1; i++ {
		if !isSpace(first[i]) {
			continue
		}
		second = trimTreePrefix(first[i+1:], 1)
		if name := first[:i]; name == second {
			return name, nil
		}
	}
	return "", nil
}