Skip to content
Open
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d69c150
add logic to store shorter repo url in db
tobias-lippert Sep 11, 2025
ce2b80f
start adapting tests
tobias-lippert Sep 11, 2025
df20082
add repository conversion util to git
tobias-lippert Sep 12, 2025
49c4901
use getter/setter consistently for template and solution repo
tobias-lippert Sep 13, 2025
152b69a
121 tests failing
tobias-lippert Sep 13, 2025
88e6c57
debug log
tobias-lippert Sep 13, 2025
2cf36b2
34 failing tests
tobias-lippert Sep 14, 2025
0d1b6d8
do not convert to short uri before setting it
tobias-lippert Sep 14, 2025
94ba714
47 failing tests
tobias-lippert Sep 14, 2025
87cd395
9 failing tests
tobias-lippert Sep 14, 2025
065a5b6
still 9 failing tests, different approach
tobias-lippert Sep 14, 2025
7bcd8b7
try to make RepositoryUriConversionUtil really compatible with parall…
tobias-lippert Sep 14, 2025
5fcd4f6
add solution to be threadsafe with @async
tobias-lippert Sep 14, 2025
6fdcbd2
make bean lazy
tobias-lippert Sep 14, 2025
3e50a09
improve code
tobias-lippert Sep 15, 2025
77f757c
Merge branch 'develop' into chore/shorter-repo-url
tobias-lippert Sep 15, 2025
5a2e32a
add tests
tobias-lippert Sep 15, 2025
c4fcc00
add debug log
tobias-lippert Sep 15, 2025
e868065
Revert "improve code"
tobias-lippert Sep 15, 2025
176c997
Reapply "improve code"
tobias-lippert Sep 15, 2025
66d3144
more debug log
tobias-lippert Sep 16, 2025
0698417
Merge branch 'develop' into chore/shorter-repo-url
tobias-lippert Sep 16, 2025
d9c8d36
db changelog update
tobias-lippert Sep 16, 2025
23ebce7
Revert "more debug log"
tobias-lippert Sep 16, 2025
feb8b95
Revert "add debug log"
tobias-lippert Sep 16, 2025
c0d7eb3
reorder changelog
tobias-lippert Sep 16, 2025
1e518cf
Reapply "add debug log"
tobias-lippert Sep 18, 2025
ee68216
Reapply "more debug log"
tobias-lippert Sep 18, 2025
016196a
Merge branch 'develop' into chore/shorter-repo-url
tobias-lippert Sep 18, 2025
527f329
make all tests compatible with parallel test execution
tobias-lippert Sep 18, 2025
164e065
Merge remote-tracking branch 'origin/chore/shorter-repo-url' into cho…
tobias-lippert Sep 18, 2025
cb7f80f
make all tests compatible with parallel test execution
tobias-lippert Sep 18, 2025
f24787a
enable tests again
tobias-lippert Sep 18, 2025
b4dad61
fix server uri not set
tobias-lippert Sep 19, 2025
5fedef7
Revert "Reapply "more debug log""
tobias-lippert Sep 19, 2025
ed8cdc4
Revert "Reapply "add debug log""
tobias-lippert Sep 19, 2025
fa84725
cleanup
tobias-lippert Sep 19, 2025
c67d37c
cleanup
tobias-lippert Sep 19, 2025
6699fa9
cleanup
tobias-lippert Sep 19, 2025
f14a045
cleanup
tobias-lippert Sep 19, 2025
531e3c0
fix migration on mysql
tobias-lippert Sep 19, 2025
d05df12
Merge branch 'develop' into chore/shorter-repo-url
tobias-lippert Sep 20, 2025
f78d8c3
remove unnecessary check
tobias-lippert Sep 20, 2025
ac10ce9
Merge remote-tracking branch 'origin/chore/shorter-repo-url' into cho…
tobias-lippert Sep 20, 2025
f8040d3
Merge branch 'develop' into chore/shorter-repo-url
tobias-lippert Sep 29, 2025
afec7f1
fix typo in db migration
tobias-lippert Sep 29, 2025
73bc357
Merge branch 'develop' into chore/shorter-repo-url
tobias-lippert Oct 2, 2025
de1cbbb
fixed changelog
tobias-lippert Oct 2, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import de.tum.cit.aet.artemis.exercise.domain.Exercise;
import de.tum.cit.aet.artemis.exercise.domain.participation.Participation;
import de.tum.cit.aet.artemis.programming.service.RepositoryUriConversionUtil;
import de.tum.cit.aet.artemis.programming.service.localvc.LocalVCRepositoryUri;

@MappedSuperclass
Expand All @@ -23,16 +24,16 @@ public abstract class AbstractBaseProgrammingExerciseParticipation extends Parti

@Override
public String getRepositoryUri() {
return repositoryUri;
return RepositoryUriConversionUtil.toFullRepositoryUri(repositoryUri);
}

@Override
public void setRepositoryUri(String repositoryUri) {
this.repositoryUri = repositoryUri;
this.repositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
}

public void setRepositoryUri(@NotNull LocalVCRepositoryUri repositoryUri) {
this.repositoryUri = repositoryUri.getURI().toString();
this.repositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri.getURI().toString());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import de.tum.cit.aet.artemis.core.domain.DomainObject;
import de.tum.cit.aet.artemis.core.exception.localvc.LocalVCInternalException;
import de.tum.cit.aet.artemis.programming.service.RepositoryUriConversionUtil;
import de.tum.cit.aet.artemis.programming.service.localvc.LocalVCRepositoryUri;

@Entity
Expand Down Expand Up @@ -73,11 +74,11 @@ public class AuxiliaryRepository extends DomainObject {
private ProgrammingExercise exercise;

public String getRepositoryUri() {
return repositoryUri;
return RepositoryUriConversionUtil.toFullRepositoryUri(repositoryUri);
}

public void setRepositoryUri(String repositoryUri) {
this.repositoryUri = repositoryUri;
this.repositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
}

public String getCheckoutDirectory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType;
import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPolicy;
import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeature;
import de.tum.cit.aet.artemis.programming.service.RepositoryUriConversionUtil;
import de.tum.cit.aet.artemis.programming.service.localvc.LocalVCRepositoryUri;

/**
Expand Down Expand Up @@ -190,11 +191,11 @@ public void setSolutionRepositoryUri(String solutionRepositoryUri) {
}

public void setTestRepositoryUri(String testRepositoryUri) {
this.testRepositoryUri = testRepositoryUri;
this.testRepositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(testRepositoryUri);
}

public String getTestRepositoryUri() {
return testRepositoryUri;
return RepositoryUriConversionUtil.toFullRepositoryUri(testRepositoryUri);
}

public List<AuxiliaryRepository> getAuxiliaryRepositories() {
Expand Down Expand Up @@ -471,10 +472,10 @@ public LocalVCRepositoryUri getVcsTestRepositoryUri() {
}

try {
return new LocalVCRepositoryUri(testRepositoryUri);
return new LocalVCRepositoryUri(getTestRepositoryUri());
}
catch (LocalVCInternalException e) {
log.warn("Cannot create URI for testRepositoryUri: {} due to the following error: {}", testRepositoryUri, e.getMessage());
log.warn("Cannot create URI for testRepositoryUri: {} due to the following error: {}", getTestRepositoryUri(), e.getMessage());
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import de.tum.cit.aet.artemis.exercise.domain.Exercise;
import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation;
import de.tum.cit.aet.artemis.programming.service.RepositoryUriConversionUtil;
import de.tum.cit.aet.artemis.programming.service.localvc.LocalVCRepositoryUri;

@Entity
Expand All @@ -36,16 +37,16 @@ public ProgrammingExerciseStudentParticipation(String branch) {

@Override
public String getRepositoryUri() {
return repositoryUri;
return RepositoryUriConversionUtil.toFullRepositoryUri(repositoryUri);
}

@Override
public void setRepositoryUri(String repositoryUri) {
this.repositoryUri = repositoryUri;
this.repositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
}

public void setRepositoryUri(@NotNull LocalVCRepositoryUri repositoryUri) {
this.repositoryUri = repositoryUri.getURI().toString();
this.repositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri.getURI().toString());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseStudentParticipation;
import de.tum.cit.aet.artemis.programming.service.RepositoryUriConversionUtil;

/**
* Spring Data JPA repository for the Participation entity.
Expand Down Expand Up @@ -88,13 +89,15 @@ Optional<ProgrammingExerciseStudentParticipation> findByIdWithAllResultsAndRelat
Optional<ProgrammingExerciseStudentParticipation> findWithSubmissionsByRepositoryUri(String repositoryUri);

default ProgrammingExerciseStudentParticipation findWithSubmissionsByRepositoryUriElseThrow(String repositoryUri) {
return getValueElseThrow(findWithSubmissionsByRepositoryUri(repositoryUri));
String shortRepositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
return getValueElseThrow(findWithSubmissionsByRepositoryUri(shortRepositoryUri));
}

Optional<ProgrammingExerciseStudentParticipation> findByRepositoryUri(String repositoryUri);

default ProgrammingExerciseStudentParticipation findByRepositoryUriElseThrow(String repositoryUri) {
return getValueElseThrow(findByRepositoryUri(repositoryUri));
String shortRepositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
return getValueElseThrow(findByRepositoryUri(shortRepositoryUri));
}

@EntityGraph(type = LOAD, attributePaths = { "team.students" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import de.tum.cit.aet.artemis.exercise.domain.Submission_;
import de.tum.cit.aet.artemis.programming.domain.TemplateProgrammingExerciseParticipation;
import de.tum.cit.aet.artemis.programming.domain.TemplateProgrammingExerciseParticipation_;
import de.tum.cit.aet.artemis.programming.service.RepositoryUriConversionUtil;

/**
* Spring Data JPA repository for the Participation entity.
Expand All @@ -49,13 +50,15 @@ public interface TemplateProgrammingExerciseParticipationRepository
Optional<TemplateProgrammingExerciseParticipation> findWithSubmissionsByRepositoryUri(String repositoryUri);

default TemplateProgrammingExerciseParticipation findWithSubmissionsByRepositoryUriElseThrow(String repositoryUri) {
return getValueElseThrow(findWithSubmissionsByRepositoryUri(repositoryUri));
String shortRepositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
return getValueElseThrow(findWithSubmissionsByRepositoryUri(shortRepositoryUri));
}

Optional<TemplateProgrammingExerciseParticipation> findByRepositoryUri(String repositoryUri);

default TemplateProgrammingExerciseParticipation findByRepositoryUriElseThrow(String repositoryUri) {
return getValueElseThrow(findByRepositoryUri(repositoryUri));
String shortRepositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
return getValueElseThrow(findByRepositoryUri(shortRepositoryUri));
}

@EntityGraph(type = LOAD, attributePaths = { "submissions", "submissions.results", "submissions.results.feedbacks", "submissions.results.feedbacks.testCase" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository;
import de.tum.cit.aet.artemis.programming.domain.VcsAccessLog;
import de.tum.cit.aet.artemis.programming.service.RepositoryUriConversionUtil;

/**
* Spring Data JPA repository for the User entity.<br>
Expand Down Expand Up @@ -46,7 +47,7 @@ public interface VcsAccessLogRepository extends ArtemisJpaRepository<VcsAccessLo
/**
* Retrieves the most recent {@link VcsAccessLog} for a specific repository URI of a participation.
*
* @param repositoryUri the URI of the participation to filter by.
* @param repositoryShortUri the short URI of the participation to filter by.
* @return an Optional containing the newest {@link VcsAccessLog} of the participation, or empty if none exists.
*/
@Query("""
Expand All @@ -57,7 +58,12 @@ LEFT JOIN TREAT (vcsAccessLog.participation AS ProgrammingExerciseStudentPartici
ORDER BY vcsAccessLog.id DESC
LIMIT 1
""")
Optional<VcsAccessLog> findNewestByRepositoryUri(@Param("repositoryUri") String repositoryUri);
Optional<VcsAccessLog> findNewestByRepositoryUri(@Param("repositoryUri") String repositoryShortUri);

default Optional<VcsAccessLog> findNewestByFullRepositoryUri(String repositoryUri) {
String shortRepositoryUri = RepositoryUriConversionUtil.toShortRepositoryUri(repositoryUri);
return findNewestByRepositoryUri(shortRepositoryUri);
}

/**
* Retrieves a list of {@link VcsAccessLog} entities associated with the specified participation ID.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package de.tum.cit.aet.artemis.programming.service;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LOCALVC;

import jakarta.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

/**
* Utility class for converting between full (as expected in the domain) and short (stored in the database) repository URIs.
* Parallel-test safe:
* - No globally mutable server URL.
* - Supports per-thread overrides for tests that run multiple Spring contexts in parallel.
*/
@Component
@Profile(PROFILE_LOCALVC)
@Lazy
public final class RepositoryUriConversionUtil {

private static final Logger log = LoggerFactory.getLogger(RepositoryUriConversionUtil.class);

private static final String GIT_PREFIX = "/git/";

private static final String GIT_SUFFIX = ".git";

/**
* Immutable default server URL captured from the (first) Spring context that initializes this bean.
* It is intentionally final and never mutated after init.
*/
private static volatile String defaultServerUrl;

/**
* Per-thread override for tests. If set, static methods will use this value
* instead of {@link #defaultServerUrl}. This prevents cross-test leakage when
* different test threads have different Spring contexts / properties.
* Using {@link InheritableThreadLocal} so that child threads (e.g. in thread pools) also inherit the override.
* To be compatible with @Async methods, we introduced a test-profile decorator for the AsyncTaskExecutor {@link TestAsyncTaskExecutorDecoratorConfig}
*/
private static final InheritableThreadLocal<String> threadServerUrlOverride = new InheritableThreadLocal<>();

@Value("${artemis.version-control.url}")
private String injectedServerUrl;

@PostConstruct
private void init() {
String normalized = normalizeBaseUrl(injectedServerUrl);
// Capture the first initialized value only; don't overwrite on later contexts
// (production has one context; in tests, use per-thread override below).
if (defaultServerUrl == null) {
defaultServerUrl = normalized;
log.debug("RepositoryUriConversionUtil default server URL set to {}", defaultServerUrl);
}
else {
log.debug("RepositoryUriConversionUtil already initialized with default server URL: {}", defaultServerUrl);
}
}

/**
* Converts a full repository URI to a short URI for database storage.
* Removes the server URL, /git/ prefix, and .git suffix.
*
* @param fullUri the full repository URI (e.g., "https://artemis.tum.de/git/project/exercise-name.git")
* @return the short URI (e.g., "project/exercise-name") or null if the input is null/empty
*/

public static String toShortRepositoryUri(String fullUri) {
if (isNullOrEmpty(fullUri)) {
return null;
}
String uri = fullUri;

int gitPrefix = uri.indexOf(GIT_PREFIX);
if (gitPrefix == -1) {
throw new IllegalArgumentException("The provided repository URI does not contain the expected '" + GIT_PREFIX + "' segment: " + fullUri);
}
uri = uri.substring(gitPrefix + GIT_PREFIX.length());

if (uri.endsWith(GIT_SUFFIX)) {
uri = uri.substring(0, uri.length() - GIT_SUFFIX.length());
}

return uri;
}

/**
* Converts a short repository URI to a full URI for domain logic usage.
* Adds the server URL, /git/ prefix, and .git suffix.
*
* @param shortUri the short repository URI (e.g., "project/exercise-name")
* @return the full URI (e.g., "https://artemis.tum.de/git/project/exercise-name.git") or null if the input is null/empty
*/
public static String toFullRepositoryUri(String shortUri) {
if (isNullOrEmpty(shortUri)) {
return null;
}
String base = currentServerUrl();
if (base == null) {
throw new IllegalStateException("Server URL is not configured. Cannot convert to full repository URI.");
}
return base + GIT_PREFIX + shortUri + GIT_SUFFIX;
}

/**
* Override the server URL for the current thread (e.g., in tests that run in parallel).
* Call in @BeforeEach (and clear in @AfterEach) of your test class.
*
* @param serverUrl the server URL to use for this thread (e.g., "http://localhost:49152")
* @see #clearServerUrlOverrideForCurrentThread()
*/
public static void overrideServerUrlForCurrentThread(String serverUrl) {
threadServerUrlOverride.set(normalizeBaseUrl(serverUrl));
}

/**
* Clear the per-thread override. Call in @AfterEach.
*
* @see #overrideServerUrlForCurrentThread(String)
*/
public static void clearServerUrlOverrideForCurrentThread() {
threadServerUrlOverride.remove();
}

private static String currentServerUrl() {
String override = threadServerUrlOverride.get();
return override != null ? override : defaultServerUrl;
}

private static String normalizeBaseUrl(String url) {
if (url == null) {
return null;
}
return url.replaceAll("/+$", "");
}

private static boolean isNullOrEmpty(String uriString) {
return uriString == null || uriString.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void updateCommitHash(ProgrammingExerciseParticipation participation, Str
@Async
public void updateRepositoryActionType(LocalVCRepositoryUri localVCRepositoryUri, RepositoryActionType repositoryActionType) {
var repositoryURL = localVCRepositoryUri.toString().replace("/git-upload-pack", "").replace("/git-receive-pack", "");
var vcsAccessLog = vcsAccessLogRepository.findNewestByRepositoryUri(repositoryURL);
var vcsAccessLog = vcsAccessLogRepository.findNewestByFullRepositoryUri(repositoryURL);
if (vcsAccessLog.isPresent()) {
vcsAccessLog.get().setRepositoryActionType(repositoryActionType);
vcsAccessLogRepository.save(vcsAccessLog.get());
Expand Down
Loading
Loading