-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/repo #3
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
Feat/repo #3
Changes from all commits
c196eaf
49d491b
0cbba12
ede3aaf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.meet.server.common.exception; | ||
|
|
||
| import lombok.Getter; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| @Getter | ||
| public class CodebaseException extends RuntimeException { | ||
|
|
||
| private final String errorCode; | ||
| private final HttpStatus status; | ||
|
|
||
| public CodebaseException(String errorCode, String message, HttpStatus status) { | ||
| super(message); | ||
| this.errorCode = errorCode; | ||
| this.status = status; | ||
| } | ||
|
|
||
| public CodebaseException(String errorCode, String message, HttpStatus status, Throwable cause) { | ||
| super(message, cause); | ||
| this.errorCode = errorCode; | ||
| this.status = status; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.meet.server.feature.codebase; | ||
|
|
||
| import com.meet.server.common.api.ApiResponse; | ||
| import com.meet.server.feature.codebase.dto.CodebaseImportRequest; | ||
| import com.meet.server.feature.codebase.dto.CodebaseImportResponse; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/codebases") | ||
| @RequiredArgsConstructor | ||
| public class CodebaseController { | ||
|
|
||
| private final CodebaseService codebaseService; | ||
|
|
||
| @PostMapping | ||
| public ResponseEntity<ApiResponse<CodebaseImportResponse>> importCodebase( | ||
| Authentication authentication, | ||
| @Valid @RequestBody CodebaseImportRequest request | ||
| ) { | ||
| var response = codebaseService.startClone( | ||
| UUID.fromString(authentication.getName()), | ||
| request); | ||
|
|
||
| return ResponseEntity | ||
| .status(HttpStatus.ACCEPTED) | ||
| .body(new ApiResponse<>( | ||
| true, | ||
| "Codebase import queued", | ||
| Optional.of(response))); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.meet.server.feature.codebase; | ||
|
|
||
| import org.springframework.data.jpa.repository.Modifying; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.repository.query.Param; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.stereotype.Repository; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Repository | ||
| public interface CodebaseRepository extends JpaRepository<Codebase, UUID> { | ||
|
|
||
| @Modifying | ||
| @Transactional | ||
| @Query("update Codebase c set c.lastCommitSha = :lastCommitSha where c.id = :codebaseId") | ||
| int updateLastCommitSha(@Param("codebaseId") UUID codebaseId, | ||
| @Param("lastCommitSha") String lastCommitSha); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| package com.meet.server.feature.codebase; | ||
|
|
||
| import com.meet.server.common.exception.CodebaseException; | ||
| import com.meet.server.feature.codebase.dto.CodebaseImportRequest; | ||
| import com.meet.server.feature.codebase.dto.CodebaseImportResponse; | ||
| import com.meet.server.feature.repositoryfile.RepositoryFileProcessor; | ||
| import com.meet.server.feature.user.UserService; | ||
| import org.springframework.beans.factory.annotation.Qualifier; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.transaction.support.TransactionSynchronization; | ||
| import org.springframework.transaction.support.TransactionSynchronizationManager; | ||
|
|
||
| import java.io.IOException; | ||
| import java.net.InetAddress; | ||
| import java.net.Inet4Address; | ||
| import java.net.URI; | ||
| import java.net.URISyntaxException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.Locale; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.Executor; | ||
|
|
||
| @Service | ||
| public class CodebaseService { | ||
|
|
||
| private final CodebaseRepository codebaseRepository; | ||
| private final UserService userService; | ||
| private final GitService gitService; | ||
| private final RepositoryFileProcessor fileProcessor; | ||
| private final CodebaseStatusService statusService; | ||
| private final Executor codebaseTaskExecutor; | ||
|
|
||
| public CodebaseService( | ||
| CodebaseRepository codebaseRepository, | ||
| UserService userService, | ||
| GitService gitService, | ||
| RepositoryFileProcessor fileProcessor, | ||
| CodebaseStatusService statusService, | ||
| @Qualifier("codebaseTaskExecutor") Executor codebaseTaskExecutor | ||
| ) { | ||
| this.codebaseRepository = codebaseRepository; | ||
| this.userService = userService; | ||
| this.gitService = gitService; | ||
| this.fileProcessor = fileProcessor; | ||
| this.statusService = statusService; | ||
| this.codebaseTaskExecutor = codebaseTaskExecutor; | ||
| } | ||
|
|
||
| @Transactional | ||
| public CodebaseImportResponse startClone(UUID userId, CodebaseImportRequest request) { | ||
| validateCloneUrl(request.cloneUrl()); | ||
|
Comment on lines
+53
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Move
Validate the URL before entering the transactional method, and bound the DNS lookup with an explicit timeout (for example, run it on a separate executor and call Also applies to: 75-101 🤖 Prompt for AI Agents |
||
| var codebase = codebaseRepository.save(Codebase.builder() | ||
| .user(userService.getById(userId)) | ||
| .name(request.name()) | ||
| .cloneUrl(request.cloneUrl()) | ||
| .branch(request.branch() == null || request.branch().isBlank() ? "main" : request.branch()) | ||
| .status(CodebaseStatus.QUEUED) | ||
| .build()); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| var codebaseId = codebase.getId(); | ||
| TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | ||
| @Override | ||
| public void afterCommit() { | ||
| processAsync(codebaseId); | ||
| } | ||
| }); | ||
|
Comment on lines
+65
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect the codebaseTaskExecutor bean definition, queue capacity, and rejection policy.
rg -n -C15 'codebaseTaskExecutor' server/src/main/javaRepository: Meet-08/CodeCompass Length of output: 11116 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== CodebaseService relevant methods =="
sed -n '1,150p' server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java
echo
echo "== StatusService.update references =="
rg -n -C5 'void update|enum Codebase|update\(' server/src/main/java/com/meet/server/feature/codebase server/src/main/java/com/meet/server | head -200
echo
echo "== processAsync callers and suppression patterns =="
rg -n -C3 'processAsync|supplyAsync|whenComplete|handle\(' server/src/main/javaRepository: Meet-08/CodeCompass Length of output: 27178 🌐 Web query:
💡 Result: The default rejection policy for Spring's ThreadPoolTaskExecutor is the AbortPolicy [1][2]. When a task is submitted to a ThreadPoolTaskExecutor and the executor is unable to accept it—typically because the thread pool is at its maximum capacity and the task queue is full—it throws a TaskRejectedException by default [1][2]. This behavior corresponds to the AbortPolicy implementation in the underlying java.util.concurrent.ThreadPoolExecutor [1][3]. While AbortPolicy is the default, the rejection policy is fully configurable using the setRejectedExecutionHandler method [3][4]. Spring supports several alternative policies that can be used to handle task rejections depending on your application's requirements: 1. DiscardPolicy: Silently discards the rejected task. 2. DiscardOldestPolicy: Discards the oldest unhandled task in the queue and then retries the submission of the current task. 3. CallerRunsPolicy: Forces the thread that submitted the task to execute the task itself, which provides a mechanism to throttle incoming load [1][2]. In XML-based configuration, these policies can be selected using the rejection-policy attribute on the task:executor element [1][2]. In Java-based configuration, you can pass an instance of your chosen RejectedExecutionHandler to the setRejectedExecutionHandler method of the ThreadPoolTaskExecutor [4][5]. Citations:
Handle both async failure branches at the
Update either 🤖 Prompt for AI Agents |
||
|
|
||
| return new CodebaseImportResponse(codebaseId, CodebaseStatus.QUEUED, 0); | ||
| } | ||
|
|
||
| private void validateCloneUrl(String cloneUrl) { | ||
| if (cloneUrl == null || cloneUrl.isBlank()) { | ||
| throw invalidCloneUrl(null); | ||
| } | ||
| final URI uri; | ||
| try { | ||
| uri = new URI(cloneUrl); | ||
| } catch (URISyntaxException exception) { | ||
| throw invalidCloneUrl(exception); | ||
| } | ||
|
|
||
| if (!"https".equalsIgnoreCase(uri.getScheme()) | ||
| || uri.getHost() == null | ||
| || uri.getHost().isBlank() | ||
| || uri.getUserInfo() != null) { | ||
| throw invalidCloneUrl(null); | ||
| } | ||
|
|
||
| try { | ||
| for (InetAddress address : InetAddress.getAllByName(uri.getHost())) { | ||
| if (isInternalAddress(address)) { | ||
| throw invalidCloneUrl(null); | ||
| } | ||
| } | ||
| } catch (IOException exception) { | ||
| throw invalidCloneUrl(exception); | ||
| } | ||
|
Comment on lines
+75
to
+101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Check for any DNS pinning / custom resolver already wired into JGit transport =="
rg -n 'ProxySelector|Resolver|InetSocketAddress|setHttpConnectionFactory|TransportHttp|http.followRedirects' server/src/main/java || trueRepository: Meet-08/CodeCompass Length of output: 241 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Candidate files =="
git ls-files | rg 'server/(src/main/java/).*\.(java|yml|ya ml|properties)$|server/pom.xml' | head -200
echo
echo "== CodebaseService outline/lines =="
fd -a 'CodebaseService.java|GitService.java' server/src/main/java || true
for f in $(fd 'CodebaseService.java|GitService.java' server/src/main/java); do
echo "--- $f ($(wc -l < "$f") lines) ---"
ast-grep outline "$f" || true
done
echo
echo "== Relevant CodebaseService lines =="
CODEBASE=$(fd 'CodebaseService.java' server/src/main/java | head -1)
if [ -n "$CODEBASE" ]; then
sed -n '1,180p' "$CODEBASE" | nl -ba
fi
echo
echo "== Relevant GitService clone implementation =="
GIT=$(fd 'GitService.java' server/src/main/java | head -1)
if [ -n "$GIT" ]; then
sed -n '1,260p' "$GIT" | nl -ba
fi
echo
echo "== Scheduler / taskExecutor references =="
rg -n "codebaseTaskExecutor|afterCommit|scheduled|taskExecutor|Async|AsyncRepository|enableAsync" server/src/main/javaRepository: Meet-08/CodeCompass Length of output: 6911 🏁 Script executed: #!/bin/bash
set -euo pipefail
CODEBASE=$(fd 'CodebaseService.java' server/src/main/java | head -1)
GIT=$(fd 'GitService.java' server/src/main/java | head -1)
JAVAC=$(fd '.*\.java' server/src/main/java || true)
echo "== CodebaseService.java =="
python3 - <<'PY' > /tmp/codebase_lines.py
from pathlib import Path
p=Path('/tmp/coderabbit-shell-logs/output')
# no-op placeholder, will be replaced by real file in env
PY
awk '{printf "%6d\t%s\n", NR, $0}' "$CODEBASE"
echo
echo "== GitService.java =="
awk '{printf "%6d\t%s\n", NR, $0}' "$GIT"
echo
echo "== codebaseTaskExecutor / afterCommit references =="
rg -n "codebaseTaskExecutor|afterCommit|scheduled|taskExecutor|Async|AsyncRepository|enableAsync" server/src/main/java || true
echo
echo "== Check JGit import names in server source =="
rg -n "import .*jgit|TransportHttp|HttpTransport|CredentialsProvider|CheckoutCommand|CloneCommand|CheckoutRequest" server/src/main/java || trueRepository: Meet-08/CodeCompass Length of output: 22099 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== AppConfig.java =="
awk '{printf "%6d\t%s\n", NR, $0}' server/src/main/java/com/meet/server/common/config/AppConfig.java
echo
echo "== AppConfig.java outline =="
ast-grep outline server/src/main/java/com/meet/server/common/config/AppConfig.java || true
echo
echo "== Search for executor config across repository =="
rg -n "ThreadPoolTaskExecutor|codebaseTaskExecutor|Executor|`@Bean`|name = |queueCapacity|corePoolSize|maxPoolSize" server/src/main/java || true
echo
echo "== pom.xml jgit version =="
git ls-files | rg 'pom\.xml$' | while read -r f; do
echo "-- $f"
rg -n "jgit|org.eclipse.jgit|version" "$f" || true
doneRepository: Meet-08/CodeCompass Length of output: 7665 🌐 Web query:
💡 Result: In JGit, the CloneCommand's setURI method is used to configure the remote repository location, but hostname resolution for the repository URL does not occur at that time [1][2][3]. Instead, hostname resolution is deferred until the command is executed (via the call method), which initiates the transport operation [2][3]. When using HTTP/HTTPS, JGit utilizes the TransportHttp class to manage the connection [4]. The actual network connection, including DNS resolution, is performed by the underlying HTTP client implementation (such as those provided by the Apache HttpClient integration) at the moment the connection is established to communicate with the remote server [5][6]. For users needing to customize how hostnames or connections are handled—such as bypassing SSL hostname verification or configuring specific connection parameters—JGit provides extension points via the HttpConnectionFactory or by configuring the underlying transport through callbacks [7][5][8]. While the setURI method merely stores the URI string for later use, the full resolution and connection handshake occur during the transport phase triggered by the clone operation [1][2][4]. Citations:
Pin clone traffic to the IP address that passes
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| private boolean isInternalAddress(InetAddress address) { | ||
| String hostAddress = address.getHostAddress().toLowerCase(Locale.ROOT); | ||
| if (address.isAnyLocalAddress() | ||
| || address.isLoopbackAddress() | ||
| || address.isLinkLocalAddress() | ||
| || address.isSiteLocalAddress() | ||
| || address.isMulticastAddress() | ||
| || hostAddress.startsWith("fc") | ||
| || hostAddress.startsWith("fd")) { | ||
| return true; | ||
| } | ||
| if (address instanceof Inet4Address) { | ||
| byte[] bytes = address.getAddress(); | ||
| int first = bytes[0] & 0xff; | ||
| int second = bytes[1] & 0xff; | ||
| return first == 100 && second >= 64 && second <= 127 | ||
| || first == 192 && second == 0 | ||
| || first == 198 && (second == 18 || second == 19) | ||
| || first >= 240; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| private CodebaseException invalidCloneUrl(Throwable cause) { | ||
| return cause == null | ||
| ? new CodebaseException("INVALID_CLONE_URL", "Clone URL must be a public HTTPS URL", HttpStatus.BAD_REQUEST) | ||
| : new CodebaseException("INVALID_CLONE_URL", "Clone URL must be a public HTTPS URL", HttpStatus.BAD_REQUEST, cause); | ||
| } | ||
|
|
||
| public CompletableFuture<CodebaseImportResponse> processAsync(UUID codebaseId) { | ||
| return CompletableFuture.supplyAsync(() -> process(codebaseId), codebaseTaskExecutor); | ||
| } | ||
|
|
||
| private CodebaseImportResponse process(UUID codebaseId) { | ||
| var codebase = codebaseRepository.findById(codebaseId) | ||
| .orElseThrow(() -> new CodebaseException( | ||
| "CODEBASE_NOT_FOUND", | ||
| "Codebase not found", | ||
| HttpStatus.NOT_FOUND)); | ||
| try { | ||
| statusService.update(codebaseId, CodebaseStatus.PROCESSING); | ||
|
|
||
| Path repositoryPath = Files.createTempDirectory("codebase-" + codebaseId); | ||
| try { | ||
| gitService.cloneRepository(codebase.getCloneUrl(), codebase.getBranch(), repositoryPath); | ||
| var commitSha = gitService.currentCommitSha(repositoryPath); | ||
| codebaseRepository.updateLastCommitSha(codebaseId, commitSha); | ||
| var files = gitService.listFiles(repositoryPath); | ||
| var fileCount = fileProcessor.process(codebase, repositoryPath, files, commitSha); | ||
| statusService.update(codebaseId, CodebaseStatus.INDEXED); | ||
| return new CodebaseImportResponse(codebaseId, CodebaseStatus.INDEXED, fileCount); | ||
| } finally { | ||
| gitService.deleteRepository(repositoryPath); | ||
| } | ||
| } catch (IOException exception) { | ||
| statusService.update(codebaseId, CodebaseStatus.FAILED); | ||
| throw new CodebaseException( | ||
| "CODEBASE_WORKSPACE_FAILED", | ||
| "Unable to process codebase workspace", | ||
| HttpStatus.INTERNAL_SERVER_ERROR, | ||
| exception); | ||
| } catch (RuntimeException exception) { | ||
| statusService.update(codebaseId, CodebaseStatus.FAILED); | ||
| throw exception; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.meet.server.feature.codebase; | ||
|
|
||
| import com.meet.server.common.exception.CodebaseException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.time.Instant; | ||
| import java.util.UUID; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class CodebaseStatusService { | ||
|
|
||
| private final CodebaseRepository codebaseRepository; | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void update(UUID codebaseId, CodebaseStatus status) { | ||
| var codebase = codebaseRepository.findById(codebaseId) | ||
| .orElseThrow(() -> new CodebaseException( | ||
| "CODEBASE_NOT_FOUND", | ||
| "Codebase not found", | ||
| HttpStatus.NOT_FOUND)); | ||
|
|
||
| codebase.setStatus(status); | ||
| if (status == CodebaseStatus.INDEXED) { | ||
| codebase.setIndexedAt(Instant.now()); | ||
| } | ||
| codebaseRepository.save(codebase); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Meet-08/CodeCompass
Length of output: 10142
🏁 Script executed:
Repository: Meet-08/CodeCompass
Length of output: 7807
🌐 Web query:
Spring ThreadPoolTaskExecutor TaskRejectedException default rejection policy documentation💡 Result:
When a task is submitted to a Spring ThreadPoolTaskExecutor and cannot be accepted—typically because the thread pool's queue is full and the maximum pool size has been reached—the executor utilizes a RejectedExecutionHandler to determine the behavior [1][2]. The default rejection policy for Spring's ThreadPoolTaskExecutor is the AbortPolicy [1][2]. Under this default configuration, the underlying executor throws a RejectedExecutionException, which the ThreadPoolTaskExecutor catches and wraps in a Spring-specific TaskRejectedException [1][3][4]. While AbortPolicy is the default, the rejection policy is configurable [1]. You can customize the behavior by providing a different RejectedExecutionHandler implementation to the executor [1][5]. Common alternatives include: CallerRunsPolicy: Forces the thread that submitted the task to execute the task itself, effectively providing a throttling mechanism [1]. DiscardPolicy: Silently drops the rejected task [1]. DiscardOldestPolicy: Drops the oldest unhandled task in the queue to make room for the new task [1]. You can configure these policies programmatically or via configuration XML using the rejection-policy attribute [1].
Citations:
Handle
TaskRejectedExceptionfrom the codebase task executor.When
codebaseTaskExecutorhas four active tasks plus 100 queued tasks,CompletableFuture.supplyAsync(...)rejects later submissions with Spring’s defaultTaskRejectedException.processAsync()and theafterCommit()caller do not catch this, so the persistedQUEUEDimport can remain stuck. Catch submission failures inprocessAsync()/afterCommit()and mark the codebaseFAILED, or reject the request before persistingQUEUED.🤖 Prompt for AI Agents