1406 lines · 43.2 KB · Go
RawDownload
	skipping := skipToFile != "" 	diff := &Diff{Files: make([]*DiffFile, 0)} 	sb := strings.Builder{} 	// OK let's set a reasonable buffer size.	// This should be at least the size of maxLineCharacters or 4096 whichever is larger.	readerSize := maxLineCharacters	if readerSize < 4096 {		readerSize = 4096	} 	input := bufio.NewReaderSize(reader, readerSize)	line, err := input.ReadString('\n')	if err != nil {		if err == io.EOF {			return diff, nil		}		return diff, err	} 	prepareValue := func(s, p string) string {		return strings.TrimSpace(strings.TrimPrefix(s, p))	} parsingLoop:	for {		// 1. A patch file always begins with `diff --git ` + `a/path b/path` (possibly quoted)		// if it does not we have bad input!		if !strings.HasPrefix(line, cmdDiffHead) {			return diff, fmt.Errorf("invalid first file line: %s", line)		} 		if maxFiles > -1 && len(diff.Files) >= maxFiles {			lastFile := createDiffFile(diff, line)			diff.End = lastFile.Name			diff.IsIncomplete = true			_, err := io.Copy(io.Discard, reader)			if err != nil {				// By the definition of io.Copy this never returns io.EOF				return diff, fmt.Errorf("error during io.Copy: %w", err)			}			break parsingLoop		} 		curFile = createDiffFile(diff, line)		if skipping {			if curFile.Name != skipToFile {				line, err = skipToNextDiffHead(input)				if err != nil {					if err == io.EOF {						return diff, nil					}					return diff, err				}				continue			}			skipping = false		} 		diff.Files = append(diff.Files, curFile) 		// 2. It is followed by one or more extended header lines:		//		//     old mode <mode>		//     new mode <mode>		//     deleted file mode <mode>		//     new file mode <mode>		//     copy from <path>		//     copy to <path>		//     rename from <path>		//     rename to <path>		//     similarity index <number>		//     dissimilarity index <number>		//     index <hash>..<hash> <mode>		//		// * <mode> 6-digit octal numbers including the file type and file permission bits.		// * <path> does not include the a/ and b/ prefixes		// * <number> percentage of unchanged lines for similarity, percentage of changed		//   lines dissimilarity as integer rounded down with terminal %. 100% => equal files.		// * The index line includes the blob object names before and after the change.		//   The <mode> is included if the file mode does not change; otherwise, separate		//   lines indicate the old and the new mode.		// 3. Following this header the "standard unified" diff format header may be encountered: (but not for every case...)		//		//     --- a/<path>		//     +++ b/<path>		//		// With multiple hunks		//		//     @@ <hunk descriptor> @@		//     +added line		//     -removed line		//      unchanged line		//		// 4. Binary files get:		//		//     Binary files a/<path> and b/<path> differ		//		// but one of a/<path> and b/<path> could be /dev/null.	curFileLoop:		for {			line, err = input.ReadString('\n')			if err != nil {				if err != io.EOF {					return diff, err				}				break parsingLoop			} 			switch {			case strings.HasPrefix(line, cmdDiffHead):				break curFileLoop			case strings.HasPrefix(line, "old mode ") ||				strings.HasPrefix(line, "new mode "): 				if strings.HasPrefix(line, "old mode ") {					curFile.OldMode = prepareValue(line, "old mode ")				}				if strings.HasPrefix(line, "new mode ") {					curFile.Mode = prepareValue(line, "new mode ")				} 				if strings.HasSuffix(line, " 160000\n") {					curFile.IsSubmodule = true				}			case strings.HasPrefix(line, "rename from "):				curFile.IsRenamed = true				curFile.Type = DiffFileRename				if curFile.IsAmbiguous {					curFile.OldName = prepareValue(line, "rename from ")				}			case strings.HasPrefix(line, "rename to "):				curFile.IsRenamed = true				curFile.Type = DiffFileRename				if curFile.IsAmbiguous {					curFile.Name = prepareValue(line, "rename to ")					curFile.IsAmbiguous = false				}			case strings.HasPrefix(line, "copy from "):				curFile.IsRenamed = true				curFile.Type = DiffFileCopy				if curFile.IsAmbiguous {					curFile.OldName = prepareValue(line, "copy from ")				}			case strings.HasPrefix(line, "copy to "):				curFile.IsRenamed = true				curFile.Type = DiffFileCopy				if curFile.IsAmbiguous {					curFile.Name = prepareValue(line, "copy to ")					curFile.IsAmbiguous = false				}			case strings.HasPrefix(line, "new file"):				curFile.Type = DiffFileAdd				curFile.IsCreated = true				if strings.HasPrefix(line, "new file mode ") {					curFile.Mode = prepareValue(line, "new file mode ")				}				if strings.HasSuffix(line, " 160000\n") {					curFile.IsSubmodule = true				}			case strings.HasPrefix(line, "deleted"):				curFile.Type = DiffFileDel				curFile.IsDeleted = true				if strings.HasSuffix(line, " 160000\n") {					curFile.IsSubmodule = true				}			case strings.HasPrefix(line, "index"):				if strings.HasSuffix(line, " 160000\n") {					curFile.IsSubmodule = true				}			case strings.HasPrefix(line, "similarity index 100%"):				curFile.Type = DiffFileRename			case strings.HasPrefix(line, "Binary"):				curFile.IsBin = true			case strings.HasPrefix(line, "--- "):				// Handle ambiguous filenames				if curFile.IsAmbiguous {					// The shortest string that can end up here is:					// "--- a\t\n" without the quotes.					// This line has a len() of 7 but doesn't contain a oldName.					// So the amount that the line need is at least 8 or more.					// The code will otherwise panic for a out-of-bounds.					if len(line) > 7 && line[4] == 'a' {						curFile.OldName = line[6 : len(line)-1]						if line[len(line)-2] == '\t' {							curFile.OldName = curFile.OldName[:len(curFile.OldName)-1]						}					} else {						curFile.OldName = ""					}				}				// Otherwise do nothing with this line			case strings.HasPrefix(line, "+++ "):				// Handle ambiguous filenames				if curFile.IsAmbiguous {					if len(line) > 6 && line[4] == 'b' {						curFile.Name = line[6 : len(line)-1]						if line[len(line)-2] == '\t' {							curFile.Name = curFile.Name[:len(curFile.Name)-1]						}						if curFile.OldName == "" {							curFile.OldName = curFile.Name						}					} else {						curFile.Name = curFile.OldName					}					curFile.IsAmbiguous = false				}				// Otherwise do nothing with this line, but now switch to parsing hunks				lineBytes, isFragment, err := parseHunks(ctx, curFile, maxLines, maxLineCharacters, input)				diff.TotalAddition += curFile.Addition				diff.TotalDeletion += curFile.Deletion				if err != nil {					if err != io.EOF {						return diff, err					}					break parsingLoop				}				sb.Reset()				_, _ = sb.Write(lineBytes)				for isFragment {					lineBytes, isFragment, err = input.ReadLine()					if err != nil {						// Now by the definition of ReadLine this cannot be io.EOF						return diff, fmt.Errorf("unable to ReadLine: %w", err)					}					_, _ = sb.Write(lineBytes)				}				line = sb.String()				sb.Reset() 				break curFileLoop			}		}	} 	// TODO: There are numerous issues with this:	// - we might want to consider detecting encoding while parsing but...	// - we're likely to fail to get the correct encoding here anyway as we won't have enough information	diffLineTypeBuffers := make(map[DiffLineType]*bytes.Buffer, 3)	diffLineTypeDecoders := make(map[DiffLineType]*encoding.Decoder, 3)	diffLineTypeBuffers[DiffLinePlain] = new(bytes.Buffer)	diffLineTypeBuffers[DiffLineAdd] = new(bytes.Buffer)	diffLineTypeBuffers[DiffLineDel] = new(bytes.Buffer)	for _, f := range diff.Files {		f.NameHash = base.EncodeSha1(f.Name) 		for _, buffer := range diffLineTypeBuffers {			buffer.Reset()		}		for _, sec := range f.Sections {			for _, l := range sec.Lines {				if l.Type == DiffLineSection {					continue				}				diffLineTypeBuffers[l.Type].WriteString(l.Content[1:])				diffLineTypeBuffers[l.Type].WriteString("\n")			}		}		for lineType, buffer := range diffLineTypeBuffers {			diffLineTypeDecoders[lineType] = nil			if buffer.Len() == 0 {				continue			}			charsetLabel, err := charset.DetectEncoding(buffer.Bytes())			if charsetLabel != "UTF-8" && err == nil {				encoding, _ := stdcharset.Lookup(charsetLabel)				if encoding != nil {					diffLineTypeDecoders[lineType] = encoding.NewDecoder()				}			}		}		for _, sec := range f.Sections {			for _, l := range sec.Lines {				decoder := diffLineTypeDecoders[l.Type]				if decoder != nil {					if c, _, err := transform.String(decoder, l.Content[1:]); err == nil {						l.Content = l.Content[0:1] + c					}				}			}		}	} 	diff.NumFiles = len(diff.Files)	return diff, nil} func skipToNextDiffHead(input *bufio.Reader) (line string, err error) {	// need to skip until the next cmdDiffHead	var isFragment, wasFragment bool	var lineBytes []byte	for {		lineBytes, isFragment, err = input.ReadLine()		if err != nil {			return "", err		}		if wasFragment {			wasFragment = isFragment			continue		}		if bytes.HasPrefix(lineBytes, []byte(cmdDiffHead)) {			break		}		wasFragment = isFragment	}	line = string(lineBytes)	if isFragment {		var tail string		tail, err = input.ReadString('\n')		if err != nil {			return "", err		}		line += tail	}	return line, err} func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio.Reader) (lineBytes []byte, isFragment bool, err error) {	sb := strings.Builder{} 	var (		curSection        *DiffSection		curFileLinesCount int		curFileLFSPrefix  bool	) 	lastLeftIdx := -1	leftLine, rightLine := 1, 1 	for {		for isFragment {			curFile.IsIncomplete = true			curFile.IsIncompleteLineTooLong = true			_, isFragment, err = input.ReadLine()			if err != nil {				// Now by the definition of ReadLine this cannot be io.EOF				return nil, false, fmt.Errorf("unable to ReadLine: %w", err)			}		}		sb.Reset()		lineBytes, isFragment, err = input.ReadLine()		if err != nil {			if err == io.EOF {				return lineBytes, isFragment, err			}			err = fmt.Errorf("unable to ReadLine: %w", err)			return nil, false, err		}		if lineBytes[0] == 'd' {			// End of hunks			return lineBytes, isFragment, err		} 		switch lineBytes[0] {		case '@':			if maxLines > -1 && curFileLinesCount >= maxLines {				curFile.IsIncomplete = true				continue			} 			_, _ = sb.Write(lineBytes)			for isFragment {				// This is very odd indeed - we're in a section header and the line is too long				// This really shouldn't happen...				lineBytes, isFragment, err = input.ReadLine()				if err != nil {					// Now by the definition of ReadLine this cannot be io.EOF					return nil, false, fmt.Errorf("unable to ReadLine: %w", err)				}				_, _ = sb.Write(lineBytes)			}			line := sb.String() 			// Create a new section to represent this hunk			curSection = &DiffSection{file: curFile}			lastLeftIdx = -1			curFile.Sections = append(curFile.Sections, curSection) 			lineSectionInfo := getDiffLineSectionInfo(curFile.Name, line, leftLine-1, rightLine-1)			diffLine := &DiffLine{				Type:        DiffLineSection,				Content:     line,				SectionInfo: lineSectionInfo,			}			curSection.Lines = append(curSection.Lines, diffLine)			curSection.FileName = curFile.Name			// update line number.			leftLine = lineSectionInfo.LeftIdx			rightLine = lineSectionInfo.RightIdx			continue		case '\\':			if maxLines > -1 && curFileLinesCount >= maxLines {				curFile.IsIncomplete = true				continue			}			// This is used only to indicate that the current file does not have a terminal newline			if !bytes.Equal(lineBytes, []byte("\\ No newline at end of file")) {				return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes))			}			// Technically this should be the end the file!			// FIXME: we should be putting a marker at the end of the file if there is no terminal new line			continue		case '+':			curFileLinesCount++			curFile.Addition++			if maxLines > -1 && curFileLinesCount >= maxLines {				curFile.IsIncomplete = true				continue			}			diffLine := &DiffLine{Type: DiffLineAdd, RightIdx: rightLine, Match: -1}			rightLine++			if curSection == nil {				// Create a new section to represent this hunk				curSection = &DiffSection{file: curFile}				curFile.Sections = append(curFile.Sections, curSection)				lastLeftIdx = -1			}			if lastLeftIdx > -1 {				diffLine.Match = lastLeftIdx				curSection.Lines[lastLeftIdx].Match = len(curSection.Lines)				lastLeftIdx++				if lastLeftIdx >= len(curSection.Lines) || curSection.Lines[lastLeftIdx].Type != DiffLineDel {					lastLeftIdx = -1				}			}			curSection.Lines = append(curSection.Lines, diffLine)		case '-':			curFileLinesCount++			curFile.Deletion++			if maxLines > -1 && curFileLinesCount >= maxLines {				curFile.IsIncomplete = true				continue			}			diffLine := &DiffLine{Type: DiffLineDel, LeftIdx: leftLine, Match: -1}			if leftLine > 0 {				leftLine++			}			if curSection == nil {				// Create a new section to represent this hunk				curSection = &DiffSection{file: curFile}				curFile.Sections = append(curFile.Sections, curSection)				lastLeftIdx = -1			}			if len(curSection.Lines) == 0 || curSection.Lines[len(curSection.Lines)-1].Type != DiffLineDel {				lastLeftIdx = len(curSection.Lines)			}			curSection.Lines = append(curSection.Lines, diffLine)		case ' ':			curFileLinesCount++			if maxLines > -1 && curFileLinesCount >= maxLines {				curFile.IsIncomplete = true				continue			}			diffLine := &DiffLine{Type: DiffLinePlain, LeftIdx: leftLine, RightIdx: rightLine}			leftLine++			rightLine++			lastLeftIdx = -1			if curSection == nil {				// Create a new section to represent this hunk				curSection = &DiffSection{file: curFile}				curFile.Sections = append(curFile.Sections, curSection)			}			curSection.Lines = append(curSection.Lines, diffLine)		default:			// This is unexpected			return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes))		} 		line := string(lineBytes)		if isFragment {			curFile.IsIncomplete = true			curFile.IsIncompleteLineTooLong = true			for isFragment {				lineBytes, isFragment, err = input.ReadLine()				if err != nil {					// Now by the definition of ReadLine this cannot be io.EOF					return lineBytes, isFragment, fmt.Errorf("unable to ReadLine: %w", err)				}			}		}		if len(line) > maxLineCharacters {			curFile.IsIncomplete = true			curFile.IsIncompleteLineTooLong = true			line = line[:maxLineCharacters]		}		curSection.Lines[len(curSection.Lines)-1].Content = line 		// handle LFS		if line[1:] == lfs.MetaFileIdentifier {			curFileLFSPrefix = true		} else if curFileLFSPrefix && strings.HasPrefix(line[1:], lfs.MetaFileOidPrefix) {			oid := strings.TrimPrefix(line[1:], lfs.MetaFileOidPrefix)			if len(oid) == 64 {				m := &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}				count, err := db.CountByBean(ctx, m) 				if err == nil && count > 0 {

Lines 501–1000 of 1406. Previous lines

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t