Skip to content
Merged
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
21 changes: 19 additions & 2 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,35 @@ dependencies {
implementation 'com.bucket4j:bucket4j_jdk17-redis-common:8.14.0'
implementation 'com.bucket4j:bucket4j_jdk17-lettuce:8.14.0'
implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.flywaydb:flyway-database-postgresql'
implementation 'org.springframework.ai:spring-ai-starter-model-ollama'
implementation 'org.springframework.ai:spring-ai-starter-vector-store-pgvector'
implementation 'org.springframework.ai:spring-ai-vector-store-advisor'
compileOnly 'org.projectlombok:lombok'
implementation 'org.eclipse.jgit:org.eclipse.jgit:7.6.0.202603022253-r'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
implementation 'com.pgvector:pgvector:0.1.6'
runtimeOnly 'org.postgresql:postgresql'
implementation 'io.github.bonede:tree-sitter:0.26.3'
runtimeOnly 'io.github.bonede:tree-sitter-java:0.23.5'
runtimeOnly 'io.github.bonede:tree-sitter-python:0.25.0'
runtimeOnly 'io.github.bonede:tree-sitter-kotlin:0.3.8.1'
runtimeOnly 'io.github.bonede:tree-sitter-javascript:0.25.0'
runtimeOnly 'io.github.bonede:tree-sitter-typescript:0.23.2'
runtimeOnly 'io.github.bonede:tree-sitter-tsx:0.23.2'
runtimeOnly 'io.github.bonede:tree-sitter-go:0.23.3'
runtimeOnly 'io.github.bonede:tree-sitter-rust:0.23.1'
runtimeOnly 'io.github.bonede:tree-sitter-c:0.24.1'
runtimeOnly 'io.github.bonede:tree-sitter-cpp:0.23.4'
runtimeOnly 'io.github.bonede:tree-sitter-c-sharp:0.23.1'
runtimeOnly 'io.github.bonede:tree-sitter-php:0.23.11'
runtimeOnly 'io.github.bonede:tree-sitter-ruby:0.23.1'
runtimeOnly 'io.github.bonede:tree-sitter-swift:0.5.0'
runtimeOnly 'io.github.bonede:tree-sitter-html:0.23.2'
runtimeOnly 'io.github.bonede:tree-sitter-css:0.25.0'
developmentOnly 'org.springframework.ai:spring-ai-spring-boot-docker-compose'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
Expand Down
2 changes: 2 additions & 0 deletions server/src/main/java/com/meet/server/ServerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
@EnableAsync
public class ServerApplication {

private static final Logger log = LogManager.getLogger(ServerApplication.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.meet.server.common.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class AppConfig {
Expand All @@ -26,4 +27,15 @@ public void setCookieSecure(String env) {
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}

@Bean(name = "codebaseTaskExecutor")
public ThreadPoolTaskExecutor codebaseTaskExecutor() {
var executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("codebase-");
executor.initialize();
return executor;
}
Comment on lines +31 to +40

Copy link
Copy Markdown

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:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'AppConfig.java|CodebaseService.java' . | sed 's#^\./##'

echo "== AppConfig bean =="
if [ -f server/src/main/java/com/meet/server/common/config/AppConfig.java ]; then
  cat -n server/src/main/java/com/meet/server/common/config/AppConfig.java | sed -n '1,120p'
fi

echo "== CodebaseService outline and relevant lines =="
for f in $(fd 'CodebaseService.java' .); do
  echo "--- $f"
  wc -l "$f"
  ast-grep outline "$f" --view compact || true
  rg -n "afterCommit|QUEUED|FAILED|submit|execute|task" "$f" -C 3
done

echo "== references to QUEUED/status =="
rg -n "QUEUED|set.*status|transition|afterCommit|codebaseTaskExecutor" . -g '*.java' -C 2

Repository: Meet-08/CodeCompass

Length of output: 10142


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect exact transaction commit hook usage and submit path without executing repo code.
python3 - <<'PY'
from pathlib import Path
root=Path('.')
for p in root.rglob('*.java'):
    text=p.read_text(errors='ignore')
    if 'afterCommit' in text or 'codebaseTaskExecutor' in text or 'TaskRejectedException' in text:
        lines=text.splitlines()
        print(f"## {p}")
        for i,l in enumerate(lines,1):
            if 'afterCommit' in l or 'codebaseTaskExecutor' in l or 'TaskRejectedException' in l or '`@Async`' in l or '`@Transactional`' in l:
                start=max(1,i-8); end=min(len(lines),i+12)
                print(f"--- around {i}")
                for n in range(start,end+1):
                    print(f"{n:5}: {lines[n-1]}")
PY

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 TaskRejectedException from the codebase task executor.

When codebaseTaskExecutor has four active tasks plus 100 queued tasks, CompletableFuture.supplyAsync(...) rejects later submissions with Spring’s default TaskRejectedException. processAsync() and the afterCommit() caller do not catch this, so the persisted QUEUED import can remain stuck. Catch submission failures in processAsync()/afterCommit() and mark the codebase FAILED, or reject the request before persisting QUEUED.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/main/java/com/meet/server/common/config/AppConfig.java` around
lines 31 - 40, Handle TaskRejectedException when submitting work through
codebaseTaskExecutor in processAsync() and its afterCommit() caller so rejected
CompletableFuture.supplyAsync submissions cannot leave a persisted QUEUED import
stuck. Either catch the submission failure and mark the codebase FAILED, or
reject the request before persisting QUEUED, while preserving normal successful
processing.

}
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
Expand Up @@ -61,6 +61,12 @@ public ResponseEntity<ApiResponse<Void>> handleAuthException(AuthException excep
return response(exception.getStatus(), exception.getMessage());
}

@ExceptionHandler(CodebaseException.class)
public ResponseEntity<ApiResponse<Void>> handleCodebaseException(CodebaseException exception) {
log.warn("Codebase operation failed [{}]: {}", exception.getErrorCode(), exception.getMessage());
return response(exception.getStatus(), exception.getMessage());
}

@ExceptionHandler(InvalidTokenException.class)
public ResponseEntity<ApiResponse<Void>> handleInvalidToken(InvalidTokenException exception) {
return response(HttpStatus.UNAUTHORIZED, exception.getMessage());
Expand Down
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Move validateCloneUrl's DNS lookup outside the open transaction, and bound it with a timeout.

startClone is @Transactional, and validateCloneUrl runs InetAddress.getAllByName(uri.getHost()) synchronously as the first statement in that method body. This blocks the request thread and holds the transaction open for as long as DNS resolution takes. InetAddress.getAllByName has no built-in timeout, so a slow or unresponsive DNS server for an attacker-supplied hostname extends the transaction indefinitely.

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 .get(timeout, unit)).

Also applies to: 75-101

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java`
around lines 53 - 55, Move the `validateCloneUrl` call and its DNS resolution
out of transactional `startClone`, using a non-transactional entry point that
validates before delegating to the transactional clone operation. Update
`validateCloneUrl` to perform `InetAddress.getAllByName(uri.getHost())` through
a separate executor with an explicit timeout, and handle
timeout/interruption/execution failures as URL validation failures while
preserving existing validation behavior.

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());
Comment thread
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

Copy link
Copy Markdown

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:

#!/bin/bash
# Inspect the codebaseTaskExecutor bean definition, queue capacity, and rejection policy.
rg -n -C15 'codebaseTaskExecutor' server/src/main/java

Repository: 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/java

Repository: Meet-08/CodeCompass

Length of output: 27178


🌐 Web query:

Spring ThreadPoolTaskExecutor default rejection policy setRejectedExecutionHandler REJECTED_EXECUTION_POLICY

💡 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 processAsync call site.

afterCommit, processAsync completes exceptionally when process fails, but the future is not observed, so only the database status changes to FAILED and the cause is not logged. If the executor rejects the task, CompletableFuture.supplyAsync throws before the future is created, so processAsync itself is not invoked again and there is no retry.

Update either startClone or a shared async entry point so that process failures are observed and rejected submissions are caught, logged, and marked FAILED on the committed transaction boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java`
around lines 59 - 64, Update the async flow around startClone and processAsync
so afterCommit observes exceptional completion from process and logs the failure
while marking the committed codebase transaction as FAILED. Also catch executor
rejection from CompletableFuture.supplyAsync before future creation, log it, and
apply the same FAILED-status handling without relying on a retry through
processAsync.


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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 || true

Repository: 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/java

Repository: 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 || true

Repository: 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
done

Repository: Meet-08/CodeCompass

Length of output: 7665


🌐 Web query:

JGit CloneCommand setURI clone repository HTTP resolves hostname at connect time TransportHttp

💡 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 validateCloneUrl.

validateCloneUrl rejects internal hostmaps once, but the validated InetAddress values are not passed to JGit. GitService.cloneRepository later clones the original cloneUrl string, so DNS can resolve to an internal address at connect time via DNS rebinding. Resolve once, keep only validated IPs, and either pass the resolved IP to the clone or use a custom resolver/transport for JGit so it cannot re-resolve the hostname independently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java`
around lines 75 - 101, Update validateCloneUrl and the
GitService.cloneRepository flow to resolve the clone hostname once, retain only
the validated InetAddress values, and ensure JGit connects using those resolved
IPs rather than re-resolving the original hostname. Pass the validated address
through the existing call path or configure a custom resolver/transport, while
preserving HTTPS certificate/host handling.

}

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);
}
}
Loading