internal/git/conflict/parser.go (159 lines of code) (raw):

package conflict // This file is a direct port of Ruby source previously hosted at // ruby/lib/gitlab/git/conflict/parser.rb (git show fb5717dd5567082) // ruby/lib/gitlab/git/conflict/file.rb (git show 6f787458251b5f) import ( "bufio" "bytes" "crypto/sha1" "errors" "fmt" "io" "strings" ) // File contains an ordered list of lines with metadata about potential // conflicts. type File struct { path string lines []line ancestor, our, their *Entry } func (f File) sectionID(l line) string { pathSHA1 := sha1.Sum([]byte(f.path)) return fmt.Sprintf("%x_%d_%d", pathSHA1, l.oldIndex, l.newIndex) } // Resolve will iterate through each conflict line and replace it with the // specified resolution func (f File) Resolve(resolution Resolution) ([]byte, error) { var sectionID string if len(resolution.Sections) == 0 { return []byte(resolution.Content), nil } resolvedLines := make([]string, 0, len(f.lines)) for _, l := range f.lines { if l.section == sectionNone { sectionID = "" resolvedLines = append(resolvedLines, l.payload) continue } if sectionID == "" { sectionID = f.sectionID(l) } r, ok := resolution.Sections[sectionID] if !ok { return nil, fmt.Errorf("Missing resolution for section ID: %s", sectionID) } switch r { case head: if l.section != sectionNew { continue } case origin: if l.section != sectionOld { continue } default: return nil, fmt.Errorf("Missing resolution for section ID: %s", sectionID) } resolvedLines = append(resolvedLines, l.payload) } resolvedContents := strings.Join(resolvedLines, "\n") if bytes.HasSuffix(f.our.Contents, []byte{'\n'}) { resolvedContents += "\n" } return []byte(resolvedContents), nil } // Entry is a conflict entry with a path and its original preimage contents. type Entry struct { Path string Mode uint Contents []byte } // Parse will read each line and maintain which conflict section it belongs to func Parse(src io.Reader, ancestor, our, their *Entry) (File, error) { parentPath := our.Path if ancestor != nil { parentPath = ancestor.Path } var ( // conflict markers start = "<<<<<<< " + our.Path middle = "=======" end = ">>>>>>> " + their.Path f = File{ path: parentPath, ancestor: ancestor, our: our, their: their, } objIndex, oldIndex, newIndex uint = 0, 1, 1 currentSection section bytesRead int s = bufio.NewScanner(src) ) s.Buffer(make([]byte, 4096), fileLimit) // allow for line scanning up to the file limit s.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { defer func() { bytesRead += advance }() if bytesRead >= fileLimit { return 0, nil, ErrUnmergeableFile } // The remaining function is a modified version of // bufio.ScanLines that does not consume carriage returns if atEOF && len(data) == 0 { return 0, nil, nil } if i := bytes.IndexByte(data, '\n'); i >= 0 { // We have a full newline-terminated line. return i + 1, data[0:i], nil } if atEOF { return len(data), data, nil } return 0, nil, nil }) for s.Scan() { switch l := s.Text(); l { case start: if currentSection != sectionNone { return File{}, ErrUnexpectedDelimiter } currentSection = sectionNew case middle: if currentSection != sectionNew { return File{}, ErrUnexpectedDelimiter } currentSection = sectionOld case end: if currentSection != sectionOld { return File{}, ErrUnexpectedDelimiter } currentSection = sectionNone default: if len(l) > 0 && l[0] == '\\' { currentSection = sectionNoNewline f.lines = append(f.lines, line{ objIndex: objIndex, oldIndex: oldIndex, newIndex: newIndex, payload: l, section: currentSection, }) continue } f.lines = append(f.lines, line{ objIndex: objIndex, oldIndex: oldIndex, newIndex: newIndex, payload: l, section: currentSection, }) objIndex++ if currentSection != sectionNew { oldIndex++ } if currentSection != sectionOld { newIndex++ } } } if err := s.Err(); err != nil { if errors.Is(err, bufio.ErrTooLong) { return File{}, ErrUnmergeableFile } return File{}, err } if currentSection == sectionOld || currentSection == sectionNew { return File{}, ErrMissingEndDelimiter } if bytesRead == 0 { return File{}, ErrUnmergeableFile // typically a binary file } return f, nil }