Skip to content

Commit 1bbdb74

Browse files
Implement git diff fetching and parsing
1 parent 8aa4601 commit 1bbdb74

File tree

10 files changed

+375
-0
lines changed

10 files changed

+375
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package datadog.trace.civisibility.git;
2+
3+
import java.util.BitSet;
4+
import java.util.Collections;
5+
import java.util.Map;
6+
7+
public class Diff {
8+
9+
public static final Diff EMPTY = new Diff(Collections.emptyMap());
10+
11+
private final Map<String, BitSet> linesByRelativePath;
12+
13+
public Diff(Map<String, BitSet> linesByRelativePath) {
14+
this.linesByRelativePath = linesByRelativePath;
15+
}
16+
17+
public Map<String, BitSet> getLinesByRelativePath() {
18+
return Collections.unmodifiableMap(linesByRelativePath);
19+
}
20+
21+
public boolean contains(String relativePath, int startLine, int endLine) {
22+
BitSet lines = linesByRelativePath.get(relativePath);
23+
if (lines == null) {
24+
return false;
25+
}
26+
27+
int changedLine = lines.nextSetBit(startLine);
28+
return changedLine != -1 && changedLine <= endLine;
29+
}
30+
}

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/git/tree/GitClient.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector;
88
import datadog.trace.api.civisibility.telemetry.tag.Command;
99
import datadog.trace.api.civisibility.telemetry.tag.ExitCode;
10+
import datadog.trace.civisibility.git.Diff;
1011
import datadog.trace.civisibility.utils.ShellCommandExecutor;
1112
import datadog.trace.util.Strings;
1213
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -520,6 +521,36 @@ private Path createTempDirectory() throws IOException {
520521
}
521522
}
522523

524+
/**
525+
* Returns Git diff between two commits.
526+
*
527+
* @param baseCommit Commit SHA or reference (HEAD, branch name, etc) of the base commit
528+
* @param targetCommit Commit SHA or reference (HEAD, branch name, etc) of the target commit
529+
* @return Diff between two commits
530+
* @throws IOException If an error was encountered while writing command input or reading output
531+
* @throws TimeoutException If timeout was reached while waiting for Git command to finish
532+
* @throws InterruptedException If current thread was interrupted while waiting for Git command to
533+
* finish
534+
*/
535+
public @NonNull Diff getGitDiff(String baseCommit, String targetCommit)
536+
throws IOException, TimeoutException, InterruptedException {
537+
if (Strings.isNotBlank(baseCommit) && Strings.isNotBlank(targetCommit)) {
538+
return executeCommand(
539+
Command.DIFF,
540+
() ->
541+
commandExecutor.executeCommand(
542+
GitDiffParser::parse,
543+
"git",
544+
"diff",
545+
"-U0",
546+
"--word-diff=porcelain",
547+
baseCommit,
548+
targetCommit));
549+
} else {
550+
return Diff.EMPTY;
551+
}
552+
}
553+
523554
@Override
524555
public String toString() {
525556
return "GitClient{" + repoRoot + "}";
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package datadog.trace.civisibility.git.tree;
2+
3+
import datadog.trace.civisibility.git.Diff;
4+
import edu.umd.cs.findbugs.annotations.NonNull;
5+
import java.io.BufferedReader;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.io.InputStreamReader;
9+
import java.nio.charset.Charset;
10+
import java.util.BitSet;
11+
import java.util.HashMap;
12+
import java.util.Map;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
15+
16+
public class GitDiffParser {
17+
18+
private static final Pattern CHANGED_FILE_PATTERN =
19+
Pattern.compile("^diff --git a/(?<oldfilename>.+) b/(?<newfilename>.+)$");
20+
private static final Pattern CHANGED_LINES_PATTERN =
21+
Pattern.compile("^@@ -\\d+(,\\d+)? \\+(?<startline>\\d+)(,(?<count>\\d+))? @@");
22+
23+
public static @NonNull Diff parse(InputStream input) throws IOException {
24+
Map<String, BitSet> linesByRelativePath = new HashMap<>();
25+
26+
BufferedReader bufferedReader =
27+
new BufferedReader(new InputStreamReader(input, Charset.defaultCharset()));
28+
String changedFile = null;
29+
BitSet changedLines = null;
30+
31+
String line;
32+
while ((line = bufferedReader.readLine()) != null) {
33+
Matcher changedFileMatcher = CHANGED_FILE_PATTERN.matcher(line);
34+
if (changedFileMatcher.matches()) {
35+
if (changedFile != null) {
36+
linesByRelativePath.put(changedFile, changedLines);
37+
}
38+
changedFile = changedFileMatcher.group("newfilename");
39+
changedLines = new BitSet();
40+
41+
} else {
42+
Matcher changedLinesMatcher = CHANGED_LINES_PATTERN.matcher(line);
43+
while (changedLinesMatcher.find()) {
44+
int startLine = Integer.parseInt(changedLinesMatcher.group("startline"));
45+
String stringCount = changedLinesMatcher.group("count");
46+
int count = stringCount != null ? Integer.parseInt(stringCount) : 1;
47+
if (changedLines == null) {
48+
throw new IllegalStateException(
49+
"Line "
50+
+ line
51+
+ " contains changed lines information, but no changed file info is available");
52+
}
53+
changedLines.set(startLine, startLine + count);
54+
}
55+
}
56+
}
57+
58+
if (changedFile != null) {
59+
linesByRelativePath.put(changedFile, changedLines);
60+
}
61+
62+
return new Diff(linesByRelativePath);
63+
}
64+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package datadog.trace.civisibility
2+
3+
abstract class TestUtils {
4+
5+
private TestUtils() {}
6+
7+
static BitSet lines(int ... setBits) {
8+
BitSet bitSet = new BitSet()
9+
for (int bit : setBits) {
10+
bitSet.set(bit)
11+
}
12+
return bitSet
13+
}
14+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package datadog.trace.civisibility.git
2+
3+
import spock.lang.Specification
4+
5+
import static datadog.trace.civisibility.TestUtils.lines
6+
7+
class DiffTest extends Specification {
8+
9+
def "test diff contains line interval"() {
10+
when:
11+
def diff = new Diff(lines)
12+
13+
then:
14+
diff.contains(path, interval[0], interval[1]) == result
15+
16+
where:
17+
lines | path | interval | result
18+
["path": lines(10)] | "path-b" | [0, 9999] | false
19+
["path": lines(10)] | "path-b" | [10, 10] | false
20+
["path": lines(10)] | "path" | [0, 9999] | true
21+
["path": lines(10)] | "path" | [10, 10] | true
22+
["path": lines(10)] | "path" | [9, 9] | false
23+
["path": lines(10)] | "path" | [11, 11] | false
24+
["path": lines(10)] | "path" | [9, 11] | true
25+
["path": lines(10, 11, 13)] | "path" | [9, 11] | true
26+
["path": lines(10, 11, 13)] | "path" | [12, 12] | false
27+
["path": lines(10, 11, 13)] | "path" | [12, 14] | true
28+
["path": lines(10, 11, 13)] | "path" | [9, 14] | true
29+
["path": lines(10, 11, 12, 13)] | "path" | [9, 11] | true
30+
["path": lines(10, 11, 12, 13)] | "path" | [9, 14] | true
31+
["path": lines(10, 11, 12, 13)] | "path" | [11, 12] | true
32+
["path": lines(10, 11, 12, 13)] | "path" | [12, 14] | true
33+
["path": lines(10, 11, 12, 13), "path-b": lines()] | "path-b" | [0, 9999] | false
34+
["path": lines(10, 11, 12, 13), "path-b": lines(8)] | "path-b" | [0, 9999] | true
35+
["path": lines(10, 11, 12, 13), "path-b": lines(8)] | "path-b" | [7, 8] | true
36+
}
37+
}

dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitClientTest.groovy

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import java.nio.file.Files
1111
import java.nio.file.Path
1212
import java.nio.file.Paths
1313

14+
import static datadog.trace.civisibility.TestUtils.lines
15+
1416
class GitClientTest extends Specification {
1517

1618
private static final int GIT_COMMAND_TIMEOUT_MILLIS = 10_000
@@ -301,6 +303,20 @@ class GitClientTest extends Specification {
301303
gitPackObject.getType() == GitObject.COMMIT_TYPE
302304
}
303305

306+
def "test git diff"() {
307+
given:
308+
givenGitRepo()
309+
310+
when:
311+
def gitClient = givenGitClient()
312+
def diff = gitClient.getGitDiff("10599ae3c17d66d642f9f143b1ff3dd236111e2a", "6aaa4085c10d16b63a910043e35dbd35d2ef7f1c")
313+
314+
then:
315+
diff.linesByRelativePath == [
316+
"src/Datadog.Trace/Logging/DatadogLogging.cs": lines(26, 32, 91, 95, 159, 160)
317+
]
318+
}
319+
304320
private void givenGitRepo() {
305321
givenGitRepo("ci/git/with_pack/git")
306322
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package datadog.trace.civisibility.git.tree
2+
3+
import spock.lang.Specification
4+
5+
import static datadog.trace.civisibility.TestUtils.lines
6+
7+
class GitDiffParserTest extends Specification {
8+
9+
def "test git diff parsing: #filename"() {
10+
when:
11+
def diff
12+
try (def gitDiff = GitDiffParserTest.getResourceAsStream(filename)) {
13+
diff = GitDiffParser.parse(gitDiff)
14+
}
15+
16+
then:
17+
diff.linesByRelativePath == result
18+
19+
where:
20+
filename | result
21+
"git-diff.txt" | [
22+
"java/maven-junit4/pom.xml": lines(10),
23+
"java/maven-junit5/pom.xml": lines(14, 41),
24+
]
25+
"larger-git-diff.txt" | [
26+
"java/maven-junit5/pom.xml" : lines(14, 41),
27+
"java/maven-junit5/module-a/pom.xml": lines(8, 9, 10, 13, 14, 15, 20, 21, 22, 27, 28, 36, 40),
28+
"java/maven-junit5/module-b/pom.xml": lines(),
29+
"java/maven-junit4/pom.xml" : lines(10)
30+
]
31+
}
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
diff --git a/java/maven-junit4/pom.xml b/java/maven-junit4/pom.xml
2+
index 6d73cda..2a1f220 100644
3+
--- a/java/maven-junit4/pom.xml
4+
+++ b/java/maven-junit4/pom.xml
5+
@@ -10 +10 @@
6+
7+
-<name>java-maven-junit4</name>
8+
+<name>java-maven-junit4-test-project</name>
9+
~
10+
diff --git a/java/maven-junit5/pom.xml b/java/maven-junit5/pom.xml
11+
index 7b92d64..834a61c 100644
12+
--- a/java/maven-junit5/pom.xml
13+
+++ b/java/maven-junit5/pom.xml
14+
@@ -14 +14 @@
15+
16+
-<module>module-b</module>
17+
+<module>module-c</module>
18+
~
19+
@@ -18,3 +17,0 @@
20+
- <maven.compiler.source>8</maven.compiler.source>
21+
~
22+
- <maven.compiler.target>8</maven.compiler.target>
23+
~
24+
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
25+
~
26+
@@ -34 +30,0 @@
27+
- <scope>test</scope>
28+
~
29+
@@ -45 +41 @@
30+
31+
-<forkCount>1</forkCount>
32+
+<forkCount>2</forkCount>
33+
~

0 commit comments

Comments
 (0)