mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-12-23 03:08:29 +00:00
139 lines
3.4 KiB
Go
139 lines
3.4 KiB
Go
// Package gitdiff parses and applies patches generated by Git. It supports
|
|
// line-oriented text patches, binary patches, and can also parse standard
|
|
// unified diffs generated by other tools.
|
|
package gitdiff
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Parse parses a patch with changes to one or more files. Any content before
|
|
// the first file is returned as the second value. If an error occurs while
|
|
// parsing, it returns all files parsed before the error.
|
|
func Parse(r io.Reader) ([]*File, string, error) {
|
|
p := newParser(r)
|
|
|
|
if err := p.Next(); err != nil {
|
|
if err == io.EOF {
|
|
return nil, "", nil
|
|
}
|
|
return nil, "", err
|
|
}
|
|
|
|
var preamble string
|
|
var files []*File
|
|
for {
|
|
file, pre, err := p.ParseNextFileHeader()
|
|
if err != nil {
|
|
return files, preamble, err
|
|
}
|
|
if file == nil {
|
|
break
|
|
}
|
|
|
|
for _, fn := range []func(*File) (int, error){
|
|
p.ParseTextFragments,
|
|
p.ParseBinaryFragments,
|
|
} {
|
|
n, err := fn(file)
|
|
if err != nil {
|
|
return files, preamble, err
|
|
}
|
|
if n > 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(files) == 0 {
|
|
preamble = pre
|
|
}
|
|
files = append(files, file)
|
|
}
|
|
|
|
return files, preamble, nil
|
|
}
|
|
|
|
// TODO(bkeyes): consider exporting the parser type with configuration
|
|
// this would enable OID validation, p-value guessing, and prefix stripping
|
|
// by allowing users to set or override defaults
|
|
|
|
// parser invariants:
|
|
// - methods that parse objects:
|
|
// - start with the parser on the first line of the first object
|
|
// - if returning nil, do not advance
|
|
// - if returning an error, do not advance past the object
|
|
// - if returning an object, advance to the first line after the object
|
|
// - any exported parsing methods must initialize the parser by calling Next()
|
|
|
|
type stringReader interface {
|
|
ReadString(delim byte) (string, error)
|
|
}
|
|
|
|
type parser struct {
|
|
r stringReader
|
|
|
|
eof bool
|
|
lineno int64
|
|
lines [3]string
|
|
}
|
|
|
|
func newParser(r io.Reader) *parser {
|
|
if r, ok := r.(stringReader); ok {
|
|
return &parser{r: r}
|
|
}
|
|
return &parser{r: bufio.NewReader(r)}
|
|
}
|
|
|
|
// Next advances the parser by one line. It returns any error encountered while
|
|
// reading the line, including io.EOF when the end of stream is reached.
|
|
func (p *parser) Next() error {
|
|
if p.eof {
|
|
return io.EOF
|
|
}
|
|
|
|
if p.lineno == 0 {
|
|
// on first call to next, need to shift in all lines
|
|
for i := 0; i < len(p.lines)-1; i++ {
|
|
if err := p.shiftLines(); err != nil && err != io.EOF {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
err := p.shiftLines()
|
|
if err != nil && err != io.EOF {
|
|
return err
|
|
}
|
|
|
|
p.lineno++
|
|
if p.lines[0] == "" {
|
|
p.eof = true
|
|
return io.EOF
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *parser) shiftLines() (err error) {
|
|
for i := 0; i < len(p.lines)-1; i++ {
|
|
p.lines[i] = p.lines[i+1]
|
|
}
|
|
p.lines[len(p.lines)-1], err = p.r.ReadString('\n')
|
|
return
|
|
}
|
|
|
|
// Line returns a line from the parser without advancing it. A delta of 0
|
|
// returns the current line, while higher deltas return read-ahead lines. It
|
|
// returns an empty string if the delta is higher than the available lines,
|
|
// either because of the buffer size or because the parser reached the end of
|
|
// the input. Valid lines always contain at least a newline character.
|
|
func (p *parser) Line(delta uint) string {
|
|
return p.lines[delta]
|
|
}
|
|
|
|
// Errorf generates an error and appends the current line information.
|
|
func (p *parser) Errorf(delta int64, msg string, args ...interface{}) error {
|
|
return fmt.Errorf("gitdiff: line %d: %s", p.lineno+delta, fmt.Sprintf(msg, args...))
|
|
}
|