Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent NPE in CSV diff rendering when column removed #17018

Merged
merged 22 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions modules/csv/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ func CreateReaderAndGuessDelimiter(rd io.Reader) (*stdcsv.Reader, error) {
var data = make([]byte, 1e4)
size, err := rd.Read(data)
if err != nil {
if err == io.EOF {
return CreateReader(bytes.NewReader([]byte{}), rune(',')), nil
}
return nil, err
}

Expand Down
135 changes: 87 additions & 48 deletions services/gitdiff/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type csvReader struct {
eof bool
}

// ErrorUndefinedCell is for when a row, column coordinates do not exist in the CSV
var ErrorUndefinedCell = errors.New("undefined cell")

// createCsvReader creates a csvReader and fills the buffer
func createCsvReader(reader *csv.Reader, bufferRowCount int) (*csvReader, error) {
csv := &csvReader{reader: reader}
Expand All @@ -70,7 +73,7 @@ func createCsvReader(reader *csv.Reader, bufferRowCount int) (*csvReader, error)

// GetRow gets a row from the buffer if present or advances the reader to the requested row. On the end of the file only nil gets returned.
func (csv *csvReader) GetRow(row int) ([]string, error) {
if row < len(csv.buffer) {
if row < len(csv.buffer) && row >= 0 {
return csv.buffer[row], nil
}
if csv.eof {
Expand Down Expand Up @@ -131,7 +134,11 @@ func createCsvDiffSingle(reader *csv.Reader, celltype TableDiffCellType) ([]*Tab
}
cells := make([]*TableDiffCell, len(row))
for j := 0; j < len(row); j++ {
cells[j] = &TableDiffCell{LeftCell: row[j], Type: celltype}
if celltype == TableDiffCellDel {
cells[j] = &TableDiffCell{LeftCell: row[j], Type: celltype}
} else {
cells[j] = &TableDiffCell{RightCell: row[j], Type: celltype}
}
}
rows = append(rows, &TableDiffRow{RowIdx: i, Cells: cells})
i++
Expand Down Expand Up @@ -161,31 +168,6 @@ func createCsvDiff(diffFile *DiffFile, baseReader *csv.Reader, headReader *csv.R
createDiffRow := func(aline int, bline int) (*TableDiffRow, error) {
cells := make([]*TableDiffCell, columns)

if aline == 0 || bline == 0 {
var (
row []string
celltype TableDiffCellType
err error
)
if bline == 0 {
row, err = a.GetRow(aline - 1)
celltype = TableDiffCellDel
} else {
row, err = b.GetRow(bline - 1)
celltype = TableDiffCellAdd
}
if err != nil {
return nil, err
}
if row == nil {
return nil, nil
}
for i := 0; i < len(row); i++ {
cells[i] = &TableDiffCell{LeftCell: row[i], Type: celltype}
}
return &TableDiffRow{RowIdx: bline, Cells: cells}, nil
}

arow, err := a.GetRow(aline - 1)
if err != nil {
return nil, err
Expand All @@ -199,25 +181,82 @@ func createCsvDiff(diffFile *DiffFile, baseReader *csv.Reader, headReader *csv.R
}

for i := 0; i < len(a2b); i++ {
richmahn marked this conversation as resolved.
Show resolved Hide resolved
acell, _ := getCell(arow, i)
if a2b[i] == unmappedColumn {
cells[i] = &TableDiffCell{LeftCell: acell, Type: TableDiffCellDel}
var aCell string
aIsUndefined := false
richmahn marked this conversation as resolved.
Show resolved Hide resolved
if aline > 0 {
if cell, err := getCell(arow, i); err != nil {
if err != ErrorUndefinedCell {
return nil, err
}
aIsUndefined = true
} else {
aCell = cell
}
} else {
bcell, _ := getCell(brow, a2b[i])
aIsUndefined = true
}

celltype := TableDiffCellChanged
if acell == bcell {
celltype = TableDiffCellEqual
if a2b[i] == unmappedColumn {
cells[i] = &TableDiffCell{LeftCell: aCell, Type: TableDiffCellDel}
} else {
var bCell string
bIsUndefined := false
richmahn marked this conversation as resolved.
Show resolved Hide resolved
if bline > 0 {
if cell, err := getCell(brow, a2b[i]); err != nil {
if err != ErrorUndefinedCell {
return nil, err
}
bIsUndefined = true
} else {
bCell = cell
}
} else {
bIsUndefined = true
}

cells[i] = &TableDiffCell{LeftCell: acell, RightCell: bcell, Type: celltype}
var cellType TableDiffCellType
if aIsUndefined && !bIsUndefined {
cellType = TableDiffCellAdd
} else if bIsUndefined {
cellType = TableDiffCellDel
} else if aCell == bCell {
cellType = TableDiffCellEqual
} else {
cellType = TableDiffCellChanged
}
cells[i] = &TableDiffCell{LeftCell: aCell, RightCell: bCell, Type: cellType}
}
}
cellsIndex := 0
for i := 0; i < len(b2a); i++ {
if b2a[i] == unmappedColumn {
bcell, _ := getCell(brow, i)
cells[i] = &TableDiffCell{LeftCell: bcell, Type: TableDiffCellAdd}
var bCell string
bIsUndefined := false
richmahn marked this conversation as resolved.
Show resolved Hide resolved
if bline > 0 {
if cell, err := getCell(brow, i); err != nil {
if err != ErrorUndefinedCell {
return nil, err
}
bIsUndefined = true
} else {
bCell = cell
}
} else {
bIsUndefined = true
}
if cells[cellsIndex] != nil && len(cells) >= cellsIndex+1 {
copy(cells[cellsIndex+1:], cells[cellsIndex:])
}
var cellType TableDiffCellType
if bIsUndefined {
cellType = TableDiffCellDel
} else {
cellType = TableDiffCellAdd
}
cells[cellsIndex] = &TableDiffCell{RightCell: bCell, Type: cellType}
} else if cellsIndex < b2a[i] {
cellsIndex = b2a[i]
}
cellsIndex++
}

return &TableDiffRow{RowIdx: bline, Cells: cells}, nil
Expand Down Expand Up @@ -300,26 +339,26 @@ func getColumnMapping(a *csvReader, b *csvReader) ([]int, []int) {

// tryMapColumnsByContent tries to map missing columns by the content of the first lines.
func tryMapColumnsByContent(a *csvReader, a2b []int, b *csvReader, b2a []int) {
start := 0
for i := 0; i < len(a2b); i++ {
if a2b[i] == unmappedColumn {
if b2a[start] == unmappedColumn {
bStart := 0
for a2b[i] == unmappedColumn && bStart < len(b2a) {
if b2a[bStart] == unmappedColumn {
rows := util.Min(maxRowsToInspect, util.Max(0, util.Min(len(a.buffer), len(b.buffer))-1))
same := 0
for j := 1; j <= rows; j++ {
acell, ea := getCell(a.buffer[j], i)
bcell, eb := getCell(b.buffer[j], start+1)
if ea == nil && eb == nil && acell == bcell {
aCell, aErr := getCell(a.buffer[j], i)
bCell, bErr := getCell(b.buffer[j], bStart)
if aErr == nil && bErr == nil && aCell == bCell {
same++
}
}
if (float32(same) / float32(rows)) > minRatioToMatch {
a2b[i] = start + 1
b2a[start+1] = i
a2b[i] = bStart
b2a[bStart] = i
}
}
bStart++
}
start = a2b[i]
}
}

Expand All @@ -328,7 +367,7 @@ func getCell(row []string, column int) (string, error) {
if column < len(row) {
return row[column], nil
}
return "", errors.New("Undefined column")
return "", ErrorUndefinedCell
}

// countUnmappedColumns returns the count of unmapped columns.
Expand Down
Loading