Skip to content

Commit 0025fdb

Browse files
authored
Merge pull request #140 from rostilos/feature/CA-20-source-code-based-issues-relations
feat: Enhance project resolution by adding slug fallback and improvin…
2 parents 6df49c5 + 31f5909 commit 0025fdb

4 files changed

Lines changed: 66 additions & 27 deletions

File tree

java-ecosystem/libs/rag-engine/src/test/java/org/rostilos/codecrow/ragengine/service/IncrementalRagUpdateServiceTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Set;
2222

2323
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2425
import static org.mockito.ArgumentMatchers.*;
2526
import static org.mockito.Mockito.*;
2627
import static org.mockito.Mockito.doReturn;
@@ -247,13 +248,12 @@ void testPerformIncrementalUpdate_DeleteFails() throws Exception {
247248
when(ragPipelineClient.deleteFiles(anyList(), anyString(), anyString(), anyString()))
248249
.thenThrow(new IOException("Delete failed"));
249250

250-
Map<String, Object> result = service.performIncrementalUpdate(
251+
assertThatThrownBy(() -> service.performIncrementalUpdate(
251252
testProject, vcsConn, "ws-slug", "repo-slug",
252253
"main", "abc123",
253-
Set.of(), Set.of("deleted.java"));
254-
255-
assertThat(result).containsEntry("status", "completed");
256-
assertThat(result).containsKey("deleteError");
254+
Set.of(), Set.of("deleted.java")))
255+
.isInstanceOf(IOException.class)
256+
.hasMessage("Delete failed");
257257
}
258258

259259
@Test

java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/ProviderWebhookController.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,12 @@ public ResponseEntity<?> handleWebhook(
112112
.body(Map.of("error", "missing_repo_id", "message", "Could not extract repository ID"));
113113
}
114114

115-
// Find the project by external repo ID
115+
// Find the project by external repo ID (with slug fallback for legacy bindings)
116116
Optional<Project> projectOpt = projectResolver.findProjectByExternalRepo(
117-
vcsProvider, webhookPayload.externalRepoId());
117+
vcsProvider, webhookPayload.externalRepoId(), webhookPayload.repoSlug());
118118

119119
if (projectOpt.isEmpty()) {
120-
log.warn("No project found for {} repo {}", provider, webhookPayload.externalRepoId());
120+
log.warn("No project found for {} repo {} (slug={})", provider, webhookPayload.externalRepoId(), webhookPayload.repoSlug());
121121
return ResponseEntity.status(HttpStatus.NOT_FOUND)
122122
.body(Map.of("error", "project_not_found",
123123
"message", "No project configured for this repository"));
@@ -176,11 +176,11 @@ public ResponseEntity<?> handleWebhookWithoutToken(
176176
}
177177

178178
Optional<Project> projectOpt = projectResolver.findProjectByExternalRepo(
179-
vcsProvider, webhookPayload.externalRepoId());
179+
vcsProvider, webhookPayload.externalRepoId(), webhookPayload.repoSlug());
180180

181181
if (projectOpt.isEmpty()) {
182-
log.info("No project found for {} repo {} - ignoring webhook",
183-
provider, webhookPayload.externalRepoId());
182+
log.info("No project found for {} repo {} (slug={}) - ignoring webhook",
183+
provider, webhookPayload.externalRepoId(), webhookPayload.repoSlug());
184184
return ResponseEntity.ok(Map.of("status", "ignored",
185185
"message", "Repository not configured"));
186186
}

java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhookhandler/WebhookProjectResolver.java

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,43 @@ public WebhookProjectResolver(
4444
* @return The project with full connection details, or empty if not found
4545
*/
4646
public Optional<Project> findProjectByExternalRepo(EVcsProvider provider, String externalRepoId) {
47-
log.debug("Looking up project for provider={}, externalRepoId={}", provider, externalRepoId);
47+
return findProjectByExternalRepo(provider, externalRepoId, null);
48+
}
49+
50+
/**
51+
* Find a project by VCS provider and external repository ID, with slug fallback.
52+
*
53+
* @param provider The VCS provider
54+
* @param externalRepoId The external repository UUID/ID from the webhook
55+
* @param repoSlug The repository slug from the webhook (used for fallback lookup)
56+
* @return The project with full connection details, or empty if not found
57+
*/
58+
public Optional<Project> findProjectByExternalRepo(EVcsProvider provider, String externalRepoId, String repoSlug) {
59+
log.debug("Looking up project for provider={}, externalRepoId={}, repoSlug={}", provider, externalRepoId, repoSlug);
4860

49-
// Primary lookup by UUID-based external repo ID
61+
// 1. Primary lookup: externalRepoId column matches the UUID from webhook
5062
Optional<Project> result = bindingRepository.findByProviderAndExternalRepoIdWithDetails(provider, externalRepoId)
51-
.flatMap(binding -> {
52-
Long projectId = binding.getProject().getId();
53-
return projectRepository.findByIdWithFullDetails(projectId);
54-
});
55-
63+
.flatMap(binding -> projectRepository.findByIdWithFullDetails(binding.getProject().getId()));
5664
if (result.isPresent()) {
5765
return result;
5866
}
5967

60-
// Fallback: try slug-based lookup for older bindings that stored slug as externalRepoId
61-
log.debug("UUID lookup failed, trying slug-based fallback for provider={}, repoId={}", provider, externalRepoId);
62-
return bindingRepository.findByProviderAndExternalRepoSlugWithDetails(provider, externalRepoId)
63-
.flatMap(binding -> {
64-
Long projectId = binding.getProject().getId();
65-
return projectRepository.findByIdWithFullDetails(projectId);
66-
});
68+
// 2. Fallback: older bindings stored the slug as externalRepoId (when repositoryId was null)
69+
// Try matching externalRepoId column with the slug from webhook payload
70+
if (repoSlug != null && !repoSlug.equals(externalRepoId)) {
71+
log.debug("UUID lookup failed, trying externalRepoId=slug fallback for slug={}", repoSlug);
72+
result = bindingRepository.findByProviderAndExternalRepoIdWithDetails(provider, repoSlug)
73+
.flatMap(binding -> projectRepository.findByIdWithFullDetails(binding.getProject().getId()));
74+
if (result.isPresent()) {
75+
return result;
76+
}
77+
}
78+
79+
// 3. Final fallback: match against externalRepoSlug column
80+
String slugToSearch = repoSlug != null ? repoSlug : externalRepoId;
81+
log.debug("ID lookups failed, trying externalRepoSlug fallback for slug={}", slugToSearch);
82+
return bindingRepository.findByProviderAndExternalRepoSlugWithDetails(provider, slugToSearch)
83+
.flatMap(binding -> projectRepository.findByIdWithFullDetails(binding.getProject().getId()));
6784
}
6885

6986
/**

java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/ProjectService.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,30 @@ public Project bindRepository(Long workspaceId, Long projectId, BindRepositoryRe
396396
binding.setExternalRepoSlug(request.getRepositorySlug());
397397
binding.setExternalNamespace(
398398
request.getWorkspaceId() != null ? request.getWorkspaceId() : connection.getExternalWorkspaceSlug());
399-
binding.setExternalRepoId(
400-
request.getRepositoryId() != null ? request.getRepositoryId() : request.getRepositorySlug());
399+
400+
// Resolve the real repository UUID from the VCS provider API.
401+
// The frontend may send repositoryId (UUID), but if it's missing we MUST fetch it
402+
// from the API — never fall back to the slug, because webhooks match by UUID.
403+
String resolvedRepoId = request.getRepositoryId();
404+
if (resolvedRepoId == null || resolvedRepoId.isBlank()) {
405+
String namespace = request.getWorkspaceId() != null
406+
? request.getWorkspaceId()
407+
: connection.getExternalWorkspaceSlug();
408+
try {
409+
org.rostilos.codecrow.vcsclient.VcsClient client = vcsClientProvider.getClient(connection);
410+
org.rostilos.codecrow.vcsclient.model.VcsRepository repo =
411+
client.getRepository(namespace, request.getRepositorySlug());
412+
resolvedRepoId = repo.id();
413+
log.info("Resolved repository UUID from VCS API: slug={}, id={}",
414+
request.getRepositorySlug(), resolvedRepoId);
415+
} catch (Exception e) {
416+
log.warn("Failed to resolve repository UUID from VCS API for slug={}: {}. " +
417+
"Falling back to slug — webhooks may not match.",
418+
request.getRepositorySlug(), e.getMessage());
419+
resolvedRepoId = request.getRepositorySlug();
420+
}
421+
}
422+
binding.setExternalRepoId(resolvedRepoId);
401423
binding.setDisplayName(request.getName());
402424

403425
if (request.getDefaultBranch() != null && !request.getDefaultBranch().isBlank()) {

0 commit comments

Comments
 (0)