Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions github/fixtures/multi_stage.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
From c0c36c8231066951d08ddca7325858603b591343 Mon Sep 17 00:00:00 2001
From: Test Data <testdata@testdata.com>
Date: Mon, 17 Sep 2001 00:00:00 +0000
Subject: [PATCH 1/2] patch number one

---
diff --git a/path/to/file/py b/path/to/file.py
index c0c36c82..7b224fef 10281
--- a/path/to/file.py b/path/to/file.py
@@ -10,2 +10,4 @@
print("hello world")
print("hello world2")
+#well a third time won't kill me?
+print("hello world3")

From db640e639d3676e5d5faad6057c999a32d37d9ac Mon Sep 17 00:00:00 2001
From: Test Data <testdata@testdata.com>
Date: Mon, 17 Sep 2001 00:00:10 +0000
Subject: [PATCH 2/2] patch number two

---
diff --git a/path/to/file.py b/path/to/file.py
index 7b224fef..c0c36c8. 13021
@@ -0,0 +1,1 @@
+import("pandas") # who doesn't like pandas
63 changes: 44 additions & 19 deletions github/pull_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package github
import (
"bufio"
"context"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -140,6 +141,10 @@ func sdkFilesToInternalFiles(sdkFiles []*github.CommitFile) ([]*pullRequestFile,
return internalFiles, nil
}

var diffRangeRegexp = regexp.MustCompile(
`^@@ -(?P<old_start>\d+),(?P<old_rows>\d+) \+(?P<new_start>\d+),(?P<new_rows>\d) @@.*$`,
)

// Convert a file patch to an array of lineBounds.
//
// A couple optimization are used specific to our use case:
Expand All @@ -153,29 +158,49 @@ func patchToLineBounds(patch string) ([]lineBound, error) {
scanner := bufio.NewScanner(strings.NewReader(patch))
for scanner.Scan() {
line := scanner.Text()
// patch header lines are formatted like:
// @@ -0,0 +1,5 @@ <arbitrary line of code which may be blank>
if len(line) >= 15 && strings.HasPrefix(line, "@@ -") && strings.Contains(line[2:], " @@") {
// split into four pieces: (0) @@, (1) old line number and offset, (2), new line number and offset, (3) @@
segments := strings.Split(line, " ")
if len(segments) < 4 {
continue
}
match := namedMatch(diffRangeRegexp, line)
if match == nil {
continue
}

// split and parse new line numbers
bounds := strings.Split(segments[2], ",")
start, err := strconv.Atoi(bounds[0][1:]) // drop the "+"
if err != nil {
return nil, errors.Wrapf(err, "failed to convert %v to integer while processing %q", bounds[0][1:], line)
}
endOffset, err := strconv.Atoi(bounds[1]) // one-indexed offset (subtract 1 when using)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert %v to integer while processing %q", bounds[0][1:], line)
// NOTE: each of these named matches must have integers
// because the regexp specifically checks for `\d+`.
oldStart, _ := strconv.Atoi(match["old_start"])
oldRows, _ := strconv.Atoi(match["old_rows"])
newStart, _ := strconv.Atoi(match["new_start"])
newRows, _ := strconv.Atoi(match["new_rows"]) // one-indexed offset (subtract 1 when using)

rowDiff := newRows - oldRows
for i, bound := range lineBounds {
if oldStart < bound.start {
bound.start += rowDiff
bound.end += rowDiff
} else if oldStart < bound.end {
bound.end += rowDiff
}

lineBounds = append(lineBounds, lineBound{start: start, end: start + endOffset - 1})
lineBounds[i] = bound
Comment on lines +173 to +181
Copy link
Author

@crockeo crockeo Oct 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the asymptotic complexity of this little bit right here. Let me know if you expect this to be a problem and I can come back and do it right.

}

lineBounds = append(lineBounds, lineBound{
start: newStart,
end: newStart + newRows - 1,
})
}

return lineBounds, nil
}

func namedMatch(re *regexp.Regexp, text string) map[string]string {
matches := re.FindStringSubmatch(text)
if matches == nil {
return nil
}
matchesByName := map[string]string{}
for i, name := range re.SubexpNames() {
if i == 0 || name == "" {
continue
}
matchesByName[name] = matches[i]
}
return matchesByName
}
12 changes: 12 additions & 0 deletions github/pull_request_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package github

import (
_ "embed"
"fmt"
"reflect"
"testing"
Expand Down Expand Up @@ -268,6 +269,9 @@ func TestSdkFilesToInternalFiles(t *testing.T) {
}
}

//go:embed fixtures/multi_stage.patch
var multiStagePatch string

func TestPatchToLineBounds(t *testing.T) {
tests := []struct {
name, patch string
Expand Down Expand Up @@ -306,6 +310,14 @@ def foo():

print("a print statement")
+ print("and another.")`, []lineBound{{4, 10}, {24, 27}}},
{
"file simulating multiple changes in sequence",
multiStagePatch,
[]lineBound{
{11,14},
{1,1},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down