Skip to content

Commit ffcd1f9

Browse files
authored
Merge pull request #180 from rostilos/1.5.7-rc
1.5.7 rc
2 parents 421b60b + bb80bf3 commit ffcd1f9

178 files changed

Lines changed: 26472 additions & 739 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

java-ecosystem/libs/analysis-engine/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,24 @@
139139
</dependencies>
140140

141141
<build>
142+
<sourceDirectory>${project.basedir}/src/main/java/org</sourceDirectory>
142143
<plugins>
143144
<plugin>
144145
<artifactId>maven-compiler-plugin</artifactId>
145146
<configuration>
146147
<source>${java.version}</source>
147148
<target>${java.version}</target>
149+
<useModulePath>false</useModulePath>
150+
</configuration>
151+
</plugin>
152+
<plugin>
153+
<artifactId>maven-jar-plugin</artifactId>
154+
<configuration>
155+
<archive>
156+
<manifestEntries>
157+
<Automatic-Module-Name>org.rostilos.codecrow.analysisengine</Automatic-Module-Name>
158+
</manifestEntries>
159+
</archive>
148160
</configuration>
149161
</plugin>
150162
<plugin>

java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/BranchAnalysisProcessor.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,18 @@ public Map<String, Object> process(
274274
operationsService, client, vcsRepoInfoImpl, prNumber, unanalyzedCommits);
275275

276276
Set<String> changedFiles = DiffParsingUtils.parseFilePathsFromDiff(rawDiff);
277+
for (DiffParsingUtils.FileChange change : DiffParsingUtils.parseFileChanges(rawDiff)) {
278+
if ((change.changeType() == DiffParsingUtils.ChangeType.DELETED
279+
|| change.changeType() == DiffParsingUtils.ChangeType.RENAMED)
280+
&& change.oldPath() != null) {
281+
changedFiles.add(change.oldPath());
282+
}
283+
if (change.changeType() != DiffParsingUtils.ChangeType.DELETED
284+
&& change.newPath() != null) {
285+
changedFiles.add(change.newPath());
286+
}
287+
}
288+
augmentChangedFilesFromPr(changedFiles, project, prNumber);
277289

278290
// Detect first-ever analysis for this branch (no prior successful commit)
279291
boolean isFirstAnalysis = existingBranchOpt.isEmpty()
@@ -306,7 +318,7 @@ public Map<String, Object> process(
306318
project, request, existingBranchOpt.orElse(null));
307319

308320
branchIssueMappingService.mapCodeAnalysisIssuesToBranch(changedFiles, existingFiles, branch,
309-
project);
321+
project, prNumber);
310322
branchIssueReconciliationService.reconcileIssueLineNumbers(rawDiff, changedFiles, branch);
311323

312324
// Update branch issue counts after mapping

java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/branch/BranchIssueMappingService.java

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ public BranchIssueMappingService(CodeAnalysisIssueRepository codeAnalysisIssueRe
4545
public void mapCodeAnalysisIssuesToBranch(Set<String> changedFiles,
4646
Set<String> filesExistingInBranch,
4747
Branch branch, Project project) {
48+
mapCodeAnalysisIssuesToBranch(changedFiles, filesExistingInBranch, branch, project, null);
49+
}
50+
51+
/**
52+
* Maps unresolved issues to a branch. When {@code sourcePrNumber} is present,
53+
* mapping is scoped to that PR and ordered newest PR iteration first, so a merge
54+
* only carries the logical issues from the PR that actually arrived.
55+
*/
56+
public void mapCodeAnalysisIssuesToBranch(Set<String> changedFiles,
57+
Set<String> filesExistingInBranch,
58+
Branch branch, Project project,
59+
Long sourcePrNumber) {
4860

4961
// ── Build branch-wide content fingerprint set ─────────────────────────
5062
// The unique constraint uq_branch_issue_content_fp is on (branch_id, content_fingerprint)
@@ -83,13 +95,24 @@ public void mapCodeAnalysisIssuesToBranch(Set<String> changedFiles,
8395
continue;
8496
}
8597

86-
// Scope to current branch — prevents pulling in issues from unrelated
87-
// branches / PRs that happen to touch the same file.
88-
List<CodeAnalysisIssue> allIssues = codeAnalysisIssueRepository
89-
.findByProjectIdAndBranchNameAndFilePath(
90-
project.getId(), branch.getBranchName(), filePath);
98+
List<CodeAnalysisIssue> allIssues;
99+
if (sourcePrNumber != null) {
100+
allIssues = codeAnalysisIssueRepository
101+
.findByProjectIdAndPrNumberAndFilePathNewestFirst(
102+
project.getId(), sourcePrNumber, filePath);
103+
} else {
104+
// Scope to current branch — prevents pulling in issues from unrelated
105+
// branches / PRs that happen to touch the same file.
106+
allIssues = codeAnalysisIssueRepository
107+
.findByProjectIdAndBranchNameAndFilePath(
108+
project.getId(), branch.getBranchName(), filePath);
109+
}
110+
111+
List<CodeAnalysisIssue> logicalIssues = sourcePrNumber != null
112+
? filterShadowedPrLineageIssues(allIssues)
113+
: allIssues;
91114

92-
List<CodeAnalysisIssue> unresolvedIssues = allIssues.stream()
115+
List<CodeAnalysisIssue> unresolvedIssues = logicalIssues.stream()
93116
.filter(issue -> !issue.isResolved())
94117
.toList();
95118

@@ -179,6 +202,40 @@ public void mapCodeAnalysisIssuesToBranch(Set<String> changedFiles,
179202
}
180203
}
181204

205+
/**
206+
* Keep only the latest row for each tracked logical PR issue.
207+
* <p>
208+
* PR iteration rows are immutable history. If a newer row has
209+
* {@code trackedFromIssueId}, every ancestor is superseded even when an older
210+
* ancestor still has {@code resolved=false}. That prevents fixed issues from
211+
* early PR iterations from being mapped to the branch after merge.
212+
*/
213+
private List<CodeAnalysisIssue> filterShadowedPrLineageIssues(List<CodeAnalysisIssue> allIssues) {
214+
if (allIssues == null || allIssues.isEmpty()) {
215+
return List.of();
216+
}
217+
218+
Map<Long, CodeAnalysisIssue> byId = allIssues.stream()
219+
.filter(issue -> issue.getId() != null)
220+
.collect(Collectors.toMap(
221+
CodeAnalysisIssue::getId,
222+
issue -> issue,
223+
(first, ignored) -> first));
224+
225+
Set<Long> shadowedAncestorIds = new HashSet<>();
226+
for (CodeAnalysisIssue issue : allIssues) {
227+
Long ancestorId = issue.getTrackedFromIssueId();
228+
while (ancestorId != null && shadowedAncestorIds.add(ancestorId)) {
229+
CodeAnalysisIssue ancestor = byId.get(ancestorId);
230+
ancestorId = ancestor != null ? ancestor.getTrackedFromIssueId() : null;
231+
}
232+
}
233+
234+
return allIssues.stream()
235+
.filter(issue -> issue.getId() == null || !shadowedAncestorIds.contains(issue.getId()))
236+
.toList();
237+
}
238+
182239
// ───────────────── PR issue path lookup ──────────────────────────────────
183240

184241
/**

java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/branch/BranchIssueReconciliationService.java

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,37 +87,96 @@ public void reconcileIssueLineNumbers(String rawDiff, Set<String> changedFiles,
8787
return;
8888

8989
Map<String, String> perFileDiffs = DiffParsingUtils.splitDiffByFile(rawDiff);
90+
List<DiffParsingUtils.FileChange> fileChanges = DiffParsingUtils.parseFileChanges(rawDiff);
91+
92+
if (!fileChanges.isEmpty()) {
93+
for (DiffParsingUtils.FileChange change : fileChanges) {
94+
switch (change.changeType()) {
95+
case ADDED, DELETED -> {
96+
// Added files have no previous branch issues. Deleted files are
97+
// resolved by the file-existence stage in reanalyzeCandidateIssues.
98+
}
99+
case RENAMED -> reconcileRenamedFile(change, branch);
100+
case MODIFIED -> reconcileModifiedFile(
101+
change.newPath(),
102+
change.diff(),
103+
branch);
104+
}
105+
}
106+
return;
107+
}
90108

91109
for (String filePath : changedFiles) {
92110
String fileDiff = perFileDiffs.get(filePath);
93111
if (fileDiff == null)
94112
continue;
95113

96-
List<BranchIssue> issues = branchIssueRepository
97-
.findUnresolvedByBranchIdAndFilePath(branch.getId(), filePath);
98-
if (issues.isEmpty())
114+
reconcileModifiedFile(filePath, fileDiff, branch);
115+
}
116+
}
117+
118+
private void reconcileModifiedFile(String filePath, String fileDiff, Branch branch) {
119+
if (filePath == null || fileDiff == null)
120+
return;
121+
122+
List<BranchIssue> issues = branchIssueRepository
123+
.findUnresolvedByBranchIdAndFilePath(branch.getId(), filePath);
124+
if (issues.isEmpty())
125+
return;
126+
127+
List<LineRemapResult> remaps = reconciliationEngine.remapLinesFromDiff(issues, fileDiff);
128+
applyLineRemaps(remaps, null);
129+
130+
if (!remaps.isEmpty()) {
131+
log.info("Reconciled line numbers for {} branch issues in {} (branch: {})",
132+
remaps.size(), filePath, branch.getBranchName());
133+
}
134+
}
135+
136+
private void reconcileRenamedFile(DiffParsingUtils.FileChange change, Branch branch) {
137+
String oldPath = change.oldPath();
138+
String newPath = change.newPath();
139+
if (oldPath == null || newPath == null)
140+
return;
141+
142+
List<BranchIssue> issues = branchIssueRepository
143+
.findUnresolvedByBranchIdAndFilePath(branch.getId(), oldPath);
144+
if (issues.isEmpty())
145+
return;
146+
147+
List<LineRemapResult> remaps = reconciliationEngine.remapLinesFromDiff(issues, change.diff());
148+
Set<BranchIssue> remapped = new HashSet<>();
149+
applyLineRemaps(remaps, newPath).forEach(remapped::add);
150+
151+
for (BranchIssue issue : issues) {
152+
if (remapped.contains(issue))
99153
continue;
154+
issue.setFilePath(newPath);
155+
branchIssueRepository.save(issue);
156+
}
100157

101-
// Delegate to shared engine
102-
List<LineRemapResult> remaps = reconciliationEngine.remapLinesFromDiff(issues, fileDiff);
158+
log.info("Migrated {} branch issues from {} to {} (branch: {})",
159+
issues.size(), oldPath, newPath, branch.getBranchName());
160+
}
103161

104-
for (LineRemapResult remap : remaps) {
105-
BranchIssue bi = (BranchIssue) remap.issue();
106-
bi.setCurrentLineNumber(remap.newLine());
107-
if (remap.newScopeStartLine() != null) {
108-
bi.setCurrentScopeStartLine(remap.newScopeStartLine());
109-
}
110-
if (remap.newEndLineNumber() != null) {
111-
bi.setCurrentEndLineNumber(remap.newEndLineNumber());
112-
}
113-
branchIssueRepository.save(bi);
162+
private List<BranchIssue> applyLineRemaps(List<LineRemapResult> remaps, String newFilePath) {
163+
List<BranchIssue> updated = new ArrayList<>();
164+
for (LineRemapResult remap : remaps) {
165+
BranchIssue bi = (BranchIssue) remap.issue();
166+
if (newFilePath != null) {
167+
bi.setFilePath(newFilePath);
114168
}
115-
116-
if (!remaps.isEmpty()) {
117-
log.info("Reconciled line numbers for {} branch issues in {} (branch: {})",
118-
remaps.size(), filePath, branch.getBranchName());
169+
bi.setCurrentLineNumber(remap.newLine());
170+
if (remap.newScopeStartLine() != null) {
171+
bi.setCurrentScopeStartLine(remap.newScopeStartLine());
172+
}
173+
if (remap.newEndLineNumber() != null) {
174+
bi.setCurrentEndLineNumber(remap.newEndLineNumber());
119175
}
176+
branchIssueRepository.save(bi);
177+
updated.add(bi);
120178
}
179+
return updated;
121180
}
122181

123182
// ═══════════════════════ Snippet-based verification ══════════════════════

0 commit comments

Comments
 (0)