func parseName(s string, term byte, dropPrefix int) (name string, n int, err error) {
	if len(s) > 0 && s[0] == '"' {
		name, n, err = parseQuotedName(s)
	} else {
		name, n, err = parseUnquotedName(s, term)
	}
	if err != nil {
		return "", 0, err
	}
	if name == devNull {
		return name, n, nil
	}
	return cleanName(name, dropPrefix), n, nil
}
parseName extracts a file name from the start of a string and returns the
name and the index of the first character after the name. If the name is
unquoted and term is non-zero, parsing stops at the first occurrence of
term.

If the name is exactly "/dev/null", no further processing occurs. Otherwise,
if dropPrefix is greater than zero, that number of prefix components
separated by forward slashes are dropped from the name and any duplicate
slashes are collapsed.
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
}
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
}
func parseGitHeaderOldName(f *File, line, defaultName string) error {
	name, _, err := parseName(line, '\t', 1)
	if err != nil {
		return err
	}
	if f.OldName == "" && !f.IsNew {
		f.OldName = name
		return nil
	}
	return verifyGitHeaderName(name, f.OldName, f.IsNew, "old")
}
func parseGitHeaderNewName(f *File, line, defaultName string) error {
	name, _, err := parseName(line, '\t', 1)
	if err != nil {
		return err
	}
	if f.NewName == "" && !f.IsDelete {
		f.NewName = name
		return nil
	}
	return verifyGitHeaderName(name, f.NewName, f.IsDelete, "new")
}
func parseGitHeaderOldMode(f *File, line, defaultName string) (err error) {
	f.OldMode, err = parseMode(line)
	return
}
func parseGitHeaderNewMode(f *File, line, defaultName string) (err error) {
	f.NewMode, err = parseMode(line)
	return
}
func parseGitHeaderDeletedMode(f *File, line, defaultName string) error {
	f.IsDelete = true
	f.OldName = defaultName
	return parseGitHeaderOldMode(f, line, defaultName)
}
func parseGitHeaderCreatedMode(f *File, line, defaultName string) error {
	f.IsNew = true
	f.NewName = defaultName
	return parseGitHeaderNewMode(f, line, defaultName)
}
func parseGitHeaderCopyFrom(f *File, line, defaultName string) (err error) {
	f.IsCopy = true
	f.OldName, _, err = parseName(line, 0, 0)
	return
}
func parseGitHeaderCopyTo(f *File, line, defaultName string) (err error) {
	f.IsCopy = true
	f.NewName, _, err = parseName(line, 0, 0)
	return
}
func parseGitHeaderRenameFrom(f *File, line, defaultName string) (err error) {
	f.IsRename = true
	f.OldName, _, err = parseName(line, 0, 0)
	return
}
func parseGitHeaderRenameTo(f *File, line, defaultName string) (err error) {
	f.IsRename = true
	f.NewName, _, err = parseName(line, 0, 0)
	return
}
func parseGitHeaderScore(f *File, line, defaultName string) error {
	score, err := strconv.ParseInt(strings.TrimSuffix(line, "%"), 10, 32)
	if err != nil {
		nerr := err.(*strconv.NumError)
		return fmt.Errorf("invalid score line: %v", nerr.Err)
	}
	if score <= 100 {
		f.Score = int(score)
	}
	return nil
}
func parseGitHeaderIndex(f *File, line, defaultName string) error {
	const sep = ".."
	parts := strings.SplitN(line, " ", 2)
	oids := strings.SplitN(parts[0], sep, 2)
	if len(oids) < 2 {
		return fmt.Errorf("invalid index line: missing %q", sep)
	}
	f.OldOIDPrefix, f.NewOIDPrefix = oids[0], oids[1]
	if len(parts) > 1 {
		return parseGitHeaderOldMode(f, parts[1], defaultName)
	}
	return nil
}
func parseMode(s string) (os.FileMode, error) {
	mode, err := strconv.ParseInt(s, 8, 32)
	if err != nil {
		nerr := err.(*strconv.NumError)
		return os.FileMode(0), fmt.Errorf("invalid mode line: %v", nerr.Err)
	}
	return os.FileMode(mode), nil
}