Skip to content

Conversation

ekayandan
Copy link
Contributor

@ekayandan ekayandan commented Jun 17, 2025

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).
  • I documented the Java code using JavaDoc style.

Motivation and Context

This branch introduces in-memory repository exports to avoid creating temporary files on disk. This increases export performance and prevents possible i/o errors.

Description

  • Added new methods in GitService to export repositories directly to memory as snapshots, full-history archives, or Git bundles
  • Updated instructor, student, and auxiliary repository export routines to use these in-memory operations.
  • Added integration tests verifying snapshot, full-history, and bundle exports

Affected Endpoints

ProgrammingExerciseExportImportResource.java now exposes these routes using completely in-memory methods:

  • Instructor repository export – repositoryWithFullHistory

    @GetMapping("programming-exercises/{exerciseId}/export-instructor-repository/{repositoryType}")

  • Instructor auxiliary repository export - repositoryWithFullHistory

    @GetMapping("programming-exercises/{exerciseId}/export-instructor-auxiliary-repository/{repositoryId}")

  • Student-requested repository export - repositorySnapshot

    @GetMapping("programming-exercises/{exerciseId}/export-student-requested-repository")

  • Student repository export - repositorySnapshot

    @GetMapping("programming-exercises/{exerciseId}/export-student-repository/{participationId}")

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 1 Student
  • 1 Programming Exercise with at least one Auxiliary Repository

For testing repositoryWithFullHistory endpoints(Instructor repository export, Instructor auxiliary repository export):

  1. Log in to Artemis as instructor
  2. Navigate to programming exercise management page
  3. Navigate to repositories section and download all repository types: template, test, solution, and auxiliary (for adding a auxiliary repository --> Programming Exercise -> Edit -> Add Auxiliary Repository).
image

For testing Student-requested repository (exportRepositorySnapshot):

  1. Log in to Artemis as student
  2. Make a submission to a programming exercise
  3. Log in as instructor and set deadline and Example Solution Publication Date to now
  4. Log in as student again and navigate to exercise detail page
  5. Navigate to "Example Solution" section and download the solution

For testing Student repository (exportRepositorySnapshot):

  1. Log in to Artemis as student
  2. Make a submission to a programming exercise
  3. Log in as instructor and navigate to "Participations" of the exercise
  4. Navigate to a student repository using the highlighted button
image 5. Download repository

Testserver States

You can manage test servers using Helios. Check environment statuses in the environment list. To deploy to a test server, go to the CI/CD page, find your PR or branch, and trigger the deployment.

Review Progress

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Test Coverage

Class/File Line Coverage Confirmation (assert/expect)
ZipFileService 100%
ZipStreamHelper 85%
GitRepositoryExportService 84%
GitService 80%
ProgrammingExerciseExportService 83%
InMemoryDirCache 80%
InMemoryRepositoryBuilder 88%
ProgrammingExerciseExportImportResource 84%
ProgrammingExerciseRepositoryExportResource 80%

Summary by CodeRabbit

  • New Features

    • In-memory ZIP exports for repositories (snapshot and full-history) with downloadable resources for instructors and students.
  • Refactor

    • Repository export logic consolidated into a dedicated export service and a focused controller for consistent exports and filenames.
  • Bug Fixes

    • Standardized export HTTP responses and improved error semantics (404, 422, 500) for missing resources or export failures.
  • Tests

    • Expanded LocalVC integration tests and added ZIP verification utilities.
  • Chores

    • Added dependency to support enhanced ZIP/export capabilities.

@github-project-automation github-project-automation bot moved this to Work In Progress in Artemis Development Jun 17, 2025
@github-actions github-actions bot added tests server Pull requests that update Java code. (Added Automatically!) core Pull requests that affect the corresponding module programming Pull requests that affect the corresponding module labels Jun 17, 2025
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed55m 29s 753ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exam/ExamDateVerification.spec.ts
ts.Exam date verification › Exam timing › Student can start after start Date❌ failure2m 5s 748ms

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed59m 39s 238ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts
ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors❌ failure2m 18s 678ms

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed56m 33s 820ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts
ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors❌ failure2m 14s 847ms

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅Skipped ⚠️FailedTime ⏱
End-to-End (E2E) Test Report201 ran198 passed3 skipped0 failed51m 26s 659ms
TestResultTime ⏱
No test annotations available

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed56m 41s 359ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts
ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors❌ failure2m 14s 337ms

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed54m 28s 931ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/quiz-exercise/QuizExerciseParticipation.spec.ts
ts.Quiz Exercise Participation › DnD Quiz participation › Student can participate in DnD Quiz❌ failure2m 3s 601ms

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed58m 30s 763ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/quiz-exercise/QuizExerciseManagement.spec.ts
ts.Quiz Exercise Management › Quiz Exercise Creation › Creates a Quiz with Drag and Drop❌ failure2m 4s 530ms

@ekayandan ekayandan changed the title Chore/in memory repo export artemis Development: Export programming repositories in-memory Jun 23, 2025
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed59m 19s 870ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts
ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors❌ failure2m 15s 83ms

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅Skipped ⚠️FailedTime ⏱
End-to-End (E2E) Test Report201 ran198 passed3 skipped0 failed57m 30s 191ms
TestResultTime ⏱
No test annotations available

Copy link

End-to-End (E2E) Test Results Summary

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (1)

172-179: Method name is misleading: rename to reflect pack/index creation.

createGitIndex actually writes the object pack and index, not the working-tree index. Rename for clarity.

Apply:

-    private static void createGitIndex(InMemoryRepository repo, ObjectId commitId, ZipArchiveOutputStream zipOutputStream, Set<String> createdDirs) throws IOException {
+    private static void writePackAndIndex(InMemoryRepository repo, ObjectId commitId, ZipArchiveOutputStream zipOutputStream, Set<String> createdDirs) throws IOException {

and

-            createGitIndex(repo, commitId, zipOutputStream, createdDirs);
+            writePackAndIndex(repo, commitId, zipOutputStream, createdDirs);

Also applies to: 225-263

🧹 Nitpick comments (6)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (3)

57-69: Avoid re-sorting DirCache entries; rely on Builder’s canonical order.

DirCacheBuilder#finish already sorts entries correctly (including stage ordering). The custom byte-wise sort risks breaking canonical ordering for edge-cases (e.g., non-zero stages). Iterate in the existing order instead.

Apply:

-        Arrays.sort(entries, (cacheEntryA, cacheEntryB) -> {
-            byte[] pathABytes = cacheEntryA.getPathString().getBytes(StandardCharsets.UTF_8);
-            byte[] pathBBytes = cacheEntryB.getPathString().getBytes(StandardCharsets.UTF_8);
-            int minLength = Math.min(pathABytes.length, pathBBytes.length);
-            for (int i = 0; i < minLength; i++) {
-                int diff = (pathABytes[i] & 0xff) - (pathBBytes[i] & 0xff);
-                if (diff != 0) {
-                    return diff;
-                }
-            }
-            return pathABytes.length - pathBBytes.length;
-        });
+        // DirCache.Builder#finish already produced canonical order; no extra sort needed.

83-90: Consider populating mtime/ctime and size when available.

Zeroing times and size is valid but can cause immediate “stat dirty” states. If you can cheaply obtain stat info (or reuse commit time for mtime), write those for better UX. Otherwise, keep as-is.

Also applies to: 100-106


142-155: SHA-1 is mandated by Git index trailer; add a clarifying comment/suppression.

Static analysis will flag SHA-1, but Git index v2 requires a SHA-1 trailer. Add a short comment or suppression to avoid false positives in SAST.

Apply:

-            MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+            // SHA-1 is required by the Git index format (v2 trailer); not used as a security primitive.
+            MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (3)

121-123: Verify DirCache construction with null File/FS.

new InMemoryDirCache(null, null) assumes JGit’s DirCache never dereferences these. If JGit changes or a future call path touches them, this could NPE. Consider a dedicated factory (e.g., InMemoryDirCache.newInCore()) or pass a harmless placeholder FS.

I can provide a small newInCore() factory and update call sites if you want.


153-162: Symlink materialization choice: document portability trade-off.

Emitting symlinks as plain files improves Windows portability but loses link semantics on Unix. A short comment in the API/JavaDoc would help set expectations.


109-116: Minor: BEST_COMPRESSION may be CPU heavy; consider DEFAULT.

Re-using pack compression inside a ZIP yields diminishing returns. DEFAULT compression often strikes a better CPU/size balance.

Also applies to: 174-181

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff73e94 and f55b52e.

📒 Files selected for processing (3)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

Files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java
🧠 Learnings (5)
📚 Learning: 2025-08-27T09:46:36.480Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java:1839-1839
Timestamp: 2025-08-27T09:46:36.480Z
Learning: In the Artemis codebase, when stubbing GitService.getBareRepository() in tests, it's valid to return a JGit Repository (org.eclipse.jgit.lib.Repository) even though the method signature returns the domain Repository (de.tum.cit.aet.artemis.programming.domain.Repository), because the domain Repository extends JGit's FileRepository, making them polymorphically compatible.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-19T09:13:51.235Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitArchiveHelper.java:0-0
Timestamp: 2025-08-19T09:13:51.235Z
Learning: The domain Repository class (de.tum.cit.aet.artemis.programming.domain.Repository) extends JGit's FileRepository, making it polymorphically compatible with JGit APIs while providing additional Artemis-specific methods like getParticipation() and getLocalPath().

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T13:21:25.531Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java:184-186
Timestamp: 2025-08-11T13:21:25.531Z
Learning: The `getBareRepository` method in `GitService` returns a domain `Repository` object (from `de.tum.cit.aet.artemis.programming.domain.Repository`), not a JGit `Repository` (from `org.eclipse.jgit.lib.Repository`), even though its JavaDoc mentions "JGit repository".

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T13:21:25.531Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java:184-186
Timestamp: 2025-08-11T13:21:25.531Z
Learning: The `getBareRepository` method in `GitService` returns a domain `Repository` object (from `de.tum.cit.aet.artemis.programming.domain.Repository`), not a JGit `Repository` (from `org.eclipse.jgit.lib.Repository`).

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T12:57:51.535Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java:1455-1465
Timestamp: 2025-08-11T12:57:51.535Z
Learning: In the Artemis project, when ekayandan responds with "same as above" to a code review suggestion, it means they want to defer the suggested change to a follow-up PR to keep the current PR scope minimal and focused on the core functionality being implemented.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
🧬 Code graph analysis (2)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (2)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (1)
  • InMemoryDirCache (21-170)
src/main/java/de/tum/cit/aet/artemis/programming/domain/Repository.java (1)
  • Repository (17-84)
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (4)
src/main/java/de/tum/cit/aet/artemis/core/util/FileUtil.java (1)
  • FileUtil (57-932)
src/main/java/de/tum/cit/aet/artemis/programming/domain/Repository.java (1)
  • Repository (17-84)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (1)
  • InMemoryRepositoryBuilder (56-368)
src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCRepositoryUri.java (1)
  • LocalVCRepositoryUri (17-318)
🪛 ast-grep (0.38.6)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java

[warning] 241-241: Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic signature. Instead, use PBKDF2 for password hashing or SHA256 or SHA512 for other hash function applications.
Context: MessageDigest.getInstance("SHA-1")
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-sha1-java)

src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java

[warning] 145-145: Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic signature. Instead, use PBKDF2 for password hashing or SHA256 or SHA512 for other hash function applications.
Context: MessageDigest.getInstance("SHA-1")
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-sha1-java)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Build and Push Docker Image / Build linux/amd64 Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build linux/arm64 Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-tests
  • GitHub Check: client-style
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: Analyse
  • GitHub Check: bean-instantiation-check
🔇 Additional comments (10)
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (7)

56-62: Proper JGit ZIP format registration with error handling.

The ArchiveCommand.registerFormat call is correctly placed in the constructor to ensure the ZIP format is available for archive operations. Good error handling prevents the service from failing to initialize if registration fails.


101-107: Good filename sanitization implementation.

The method correctly uses FileUtil.sanitizeFilename and handles the .zip extension case-insensitively. This addresses previous review feedback about comprehensive filename sanitization.


142-155: Well-designed InputStreamResource factory method.

This helper method properly creates an InputStreamResource with accurate filename and content length information, which is essential for proper HTTP responses.


167-171: Efficient snapshot export implementation.

The method correctly uses the domain Repository type (which extends JGit's FileRepository) and delegates to the in-memory archive creation. The approach avoids disk I/O while providing working tree content only.


183-187: Excellent use of InMemoryRepositoryBuilder for full history exports.

This method leverages the InMemoryRepositoryBuilder to create a complete Git repository with working tree and .git directory, all in memory. Perfect for instructor repository exports that need full history.


239-240: Appropriate export strategy for student repositories.

Using snapshot export for student repositories is a good privacy decision - it excludes the .git directory and provides only the working tree content, which is what students typically need.


272-284: Robust archive creation with proper error handling.

The method correctly handles the case where HEAD cannot be resolved (returning empty byte array) and uses JGit's ArchiveCommand for efficient ZIP creation.

src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (1)

116-129: Fix index v2 long-name semantics (0xFFF) and padding.

For names >= 0xFFF bytes the NUL terminator must be omitted, and padding must be computed accordingly. Current code always writes a NUL and pads based on +1, which is incorrect for very long paths.

Apply:

-            // flags: [15..12]=stage, [11..0]=name length (or 0xFFF if >= 0xFFF)
+            // flags: bit15=assume-valid(0), bit14=extended(0), bits13..12=stage, bits11..0=name length (0xFFF if >= 0xFFF)
             int stage = entry.getStage() & 0x3;
             byte[] pathBytes = entry.getPathString().getBytes(StandardCharsets.UTF_8);
-            int nameLength = Math.min(pathBytes.length, 0xFFF);
-            int flags = (stage << 12) | (nameLength == 0xFFF ? 0x0FFF : nameLength);
+            boolean longName = pathBytes.length >= 0xFFF;
+            int nameLength = longName ? 0x0FFF : pathBytes.length;
+            int flags = (stage << 12) | nameLength;
             buffer.write((flags >>> 8) & 0xFF);
             buffer.write(flags & 0xFF);
 
             buffer.write(pathBytes);
-            buffer.write(0); // NUL
-            // The entry (from ctime sec to NUL) must make the total entry size a multiple of 8
-            int entryLength = 62 /* fixed */ + pathBytes.length + 1; // 62 = 10*4 + 20 + 2
+            if (!longName) {
+                buffer.write(0); // NUL only for short names
+            }
+            // Pad entry to 8-byte boundary (from ctime sec up to end-of-name (and optional NUL))
+            int entryLength = 62 /* fixed */ + pathBytes.length + (longName ? 0 : 1); // 62 = 10*4 + 20 + 2
             int padding = (8 - (entryLength % 8)) % 8;
-            IntStream.range(0, padding).map(i -> 0).forEach(buffer::write);
+            for (int i = 0; i < padding; i++) {
+                buffer.write(0);
+            }

Likely an incorrect or invalid review comment.

src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (2)

134-143: Index finalize/write is correct; nice separation.

The build-flow adds entries, calls finish(), then serializes once. Looks good.

Also applies to: 164-170


274-305: Directory entry helpers look solid.

Idempotent mkdir + parent-dir materialization with normalization and POSIX modes is neat and robust.

Also applies to: 315-333, 345-352

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java (1)

281-285: Avoid hard dependency on LocalVC; commit fails on non-LocalVC profiles

Using localVCServletService.orElseThrow() will throw when LocalVC is not active (e.g., Jenkins/GitLab CI), causing 500s on commit. Previously, a ProfileService.isLocalCIActive() guard guaranteed bean presence; that protection is gone after removing ProfileService. Call LocalVC only if present.

Apply:

-            localVCServletService.orElseThrow().processNewPush(null, repository, user, Optional.empty(), Optional.empty(), vcsAccessLog);
+            // Only available in LocalVC/LocalCI. Remote CI (e.g., Jenkins) is triggered via VCS webhooks.
+            if (localVCServletService.isPresent()) {
+                localVCServletService.get().processNewPush(null, repository, user, Optional.empty(), Optional.empty(), vcsAccessLog);
+            }
🧹 Nitpick comments (2)
src/main/java/de/tum/cit/aet/artemis/programming/web/repository/AuxiliaryRepositoryResource.java (1)

197-199: Wrong repository label passed to access check ("test" vs "auxiliary")

The human-readable repo type is used for error/log messages. Passing "test" here is misleading.

Apply:

-            repositoryAccessService.checkAccessTestOrAuxRepositoryElseThrow(true, exercise, userRepository.getUserWithGroupsAndAuthorities(principal.getName()), "test");
+            repositoryAccessService.checkAccessTestOrAuxRepositoryElseThrow(true, exercise, userRepository.getUserWithGroupsAndAuthorities(principal.getName()), "auxiliary");
src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java (1)

208-216: JavaDoc route and access-level mismatch with implementation

Comment says GET /repository/{participationId}/files/{commitId} and “enforces at least instructor”, but the mapping is repository-files-content/{commitId} with @EnforceAtLeastStudent.

Apply:

-    /**
-     * GET /repository/{participationId}/files/{commitId} : Gets the files of the repository with the given participationId at the given commitId.
-     * This enforces at least instructor access rights.
+    /**
+     * GET /repository-files-content/{commitId} : Gets the files of the repository at the given commitId.
+     * Requires at least student access (checked at runtime against repository ownership/permissions).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f55b52e and 5233258.

📒 Files selected for processing (4)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/AuxiliaryRepositoryResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/TestRepositoryResource.java (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

Files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/AuxiliaryRepositoryResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/TestRepositoryResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java
🧠 Learnings (5)
📚 Learning: 2025-06-15T13:49:43.096Z
Learnt from: ahmetsenturk
PR: ls1intum/Artemis#10916
File: src/main/java/de/tum/cit/aet/artemis/atlas/web/LearnerProfileResource.java:36-41
Timestamp: 2025-06-15T13:49:43.096Z
Learning: In the Artemis codebase, the development team has decided to allow direct injection of repositories into REST resources rather than always delegating to service layers. This architectural decision was communicated and decided with peer developers.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/AuxiliaryRepositoryResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/TestRepositoryResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java
📚 Learning: 2025-08-08T08:56:49.931Z
Learnt from: tobias-lippert
PR: ls1intum/Artemis#11248
File: src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java:288-291
Timestamp: 2025-08-08T08:56:49.931Z
Learning: In the Artemis codebase, the LocalCI profile is only active when LocalVC is active (LocalCI ⇒ LocalVC). Therefore, in RepositoryResource.commitChanges, guarding the LocalVCServletService invocation with profileService.isLocalCIActive() is correct and guarantees the bean’s presence.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java
📚 Learning: 2025-08-19T09:13:51.235Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitArchiveHelper.java:0-0
Timestamp: 2025-08-19T09:13:51.235Z
Learning: The domain Repository class (de.tum.cit.aet.artemis.programming.domain.Repository) extends JGit's FileRepository, making it polymorphically compatible with JGit APIs while providing additional Artemis-specific methods like getParticipation() and getLocalPath().

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java
📚 Learning: 2025-04-10T20:38:13.424Z
Learnt from: florian-glombik
PR: ls1intum/Artemis#10656
File: src/main/java/de/tum/cit/aet/artemis/core/repository/webauthn/ArtemisUserCredentialRepository.java:114-137
Timestamp: 2025-04-10T20:38:13.424Z
Learning: In the ArtemisUserCredentialRepository class, the findByUserId method intentionally returns an empty list rather than throwing an exception when not fully implemented, as throwing an exception would cause authentication to fail.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java
📚 Learning: 2024-06-10T19:44:09.116Z
Learnt from: pzdr7
PR: ls1intum/Artemis#8703
File: src/main/java/de/tum/in/www1/artemis/web/rest/QuizParticipationResource.java:39-39
Timestamp: 2024-06-10T19:44:09.116Z
Learning: The `Profile(PROFILE_CORE)` annotation in `QuizParticipationResource.java` is used to ensure that all nodes, not just those with the scheduling profile, can handle quiz participations.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: client-style
  • GitHub Check: client-tests
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: Build and Push Docker Image / Build linux/amd64 Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build linux/arm64 Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: bean-instantiation-check
  • GitHub Check: Analyse
🔇 Additional comments (4)
src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryResource.java (1)

73-74: Constructor DI refactor — LGTM

Removing ProfileService from the constructor and standardizing DI matches the current pattern used across resources.

src/main/java/de/tum/cit/aet/artemis/programming/web/repository/TestRepositoryResource.java (1)

59-62: Constructor signature update — LGTM

Constructor/super call now align with the new base class signature without ProfileService.

src/main/java/de/tum/cit/aet/artemis/programming/web/repository/AuxiliaryRepositoryResource.java (1)

63-67: Constructor signature update — LGTM

Matches the base class change and keeps wiring consistent.

src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java (1)

87-94: Constructor changes — LGTM

DI updates (dropping ProfileService, adjusting super call) are consistent with the refactor.

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report205 ran200 passed3 skipped2 failed1h 8m 2s 509ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/quiz-exercise/QuizExerciseManagement.spec.ts
ts.Quiz Exercise Management › Quiz Exercise Creation › Creates a Quiz with Drag and Drop❌ failure2m 6s 307ms
e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts
ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors❌ failure2m 22s 652ms

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (4)

153-162: Symlink handling: confirm policy or encode as real symlink

You currently materialize symlinks as plain files. If symlinks are disallowed in Artemis anyway, skip them; otherwise encode as symlink (Unix mode 0120000) so Git restores correctly.

Would you like me to switch this to real symlink entries, or skip entirely?

#!/bin/bash
# Verify repo-wide symlink policy/usages
rg -n --type java -C2 'symlink|core\.symlinks|reject.*symlink|FileMode\.SYMLINK'

79-87: Close InMemoryRepository via try-with-resources to prevent leaks

InMemoryRepository holds buffers/readers; currently never closed.

Apply:

-        InMemoryRepository repo = new InMemoryRepository.Builder().setRepositoryDescription(new DfsRepositoryDescription("inmem")).setFS(FS.DETECTED).build();
+        try (InMemoryRepository repo = new InMemoryRepository.Builder()
+                .setRepositoryDescription(new DfsRepositoryDescription("inmem"))
+                .setFS(FS.DETECTED)
+                .build()) {
...
-        return outputStream.toByteArray();
+        }
+        return outputStream.toByteArray();

Also applies to: 181-184


90-92: Add network timeout on Transport.fetch to avoid hanging exports

Unbounded fetch can hang threads on IO glitches.

-        try (Transport transport = Transport.open(repo, remoteUri.toString())) {
+        try (Transport transport = Transport.open(repo, remoteUri.toString())) {
+            transport.setTimeout(60); // seconds
             transport.fetch(NullProgressMonitor.INSTANCE, List.of(new RefSpec("+refs/heads/*:refs/remotes/origin/*")));
         }

239-253: Use JGit’s canonical pack name; drop manual SHA-1 digest

Pack name must match JGit’s canonical name; computing SHA-1 yourself risks mismatches and triggers crypto lint noise.

-            // Write .pack while computing its SHA-1 (Git pack filename = sha1 of content)
-            MessageDigest sha1;
-            try {
-                sha1 = MessageDigest.getInstance("SHA-1");
-            }
-            catch (NoSuchAlgorithmException e) {
-                throw new RuntimeException(e);
-            }
-            ByteArrayOutputStream packOut = new ByteArrayOutputStream();
-            try (DigestOutputStream digestOut = new DigestOutputStream(packOut, sha1)) {
-                packWriter.writePack(NullProgressMonitor.INSTANCE, NullProgressMonitor.INSTANCE, digestOut);
-            }
-            packBytes = packOut.toByteArray();
-            packHashHex = toHex(sha1.digest());
+            // Write .pack and use JGit's computed name
+            ByteArrayOutputStream packOut = new ByteArrayOutputStream();
+            packWriter.writePack(NullProgressMonitor.INSTANCE, NullProgressMonitor.INSTANCE, packOut);
+            packBytes = packOut.toByteArray();
+            packHashHex = packWriter.computeName().name();

Also applies to: 255-258, 260-263

src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java (1)

118-133: IDOR: ensure auxiliary repository belongs to the exercise

Currently any auxiliary repository id can be exported if the caller has TA rights on the provided exercise id. Validate association to prevent cross-exercise data leakage.

-        AuxiliaryRepository auxiliaryRepository = auxiliaryRepositoryRepository.findByIdElseThrow(repositoryId);
+        AuxiliaryRepository auxiliaryRepository = auxiliaryRepositoryRepository.findByIdElseThrow(repositoryId);
+        if (auxiliaryRepository.getProgrammingExercise() == null
+                || !auxiliaryRepository.getProgrammingExercise().getId().equals(programmingExercise.getId())) {
+            return ResponseEntity.notFound()
+                    .headers(HeaderUtil.createFailureAlert(applicationName, true, ENTITY_NAME, "auxiliaryRepositoryNotFound",
+                            "Auxiliary repository " + repositoryId + " was not found for this exercise."))
+                    .build();
+        }
🧹 Nitpick comments (5)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (2)

172-173: Rename createGitIndex(...) → writePackedObjects(...) for clarity

The method writes pack and idx, not the Git index; name is misleading.

-    private static void createGitIndex(InMemoryRepository repo, ObjectId commitId, ZipArchiveOutputStream zipOutputStream, Set<String> createdDirs) throws IOException {
+    private static void writePackedObjects(InMemoryRepository repo, ObjectId commitId, ZipArchiveOutputStream zipOutputStream, Set<String> createdDirs) throws IOException {

And at call site:

-            createGitIndex(repo, commitId, zipOutputStream, createdDirs);
+            writePackedObjects(repo, commitId, zipOutputStream, createdDirs);

Also applies to: 225-226


109-111: Reproducibility: pin ZIP timestamps

To get deterministic ZIPs across runs, set a stable timestamp (e.g., commit time or epoch) on entries (files and dirs).

Example:

-            ZipArchiveEntry zipEntry = new ZipArchiveEntry(path.replace('\\', '/'));
+            ZipArchiveEntry zipEntry = new ZipArchiveEntry(path.replace('\\', '/'));
+            zipEntry.setTime(0L); // or commit.getCommitTime()*1000L

Apply similarly in mkdir(...).

Also applies to: 324-329

src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java (3)

18-19: Add missing imports for Content-Disposition header

 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ContentDisposition;

189-191: Use consistent duration logging helper

Elsewhere you use formatDurationFrom(start); keep it consistent here.

-            log.info("Successfully exported repository for programming exercise {} with title {} in {} ms", programmingExercise.getId(), programmingExercise.getTitle(),
-                    (System.nanoTime() - start) / 1000000);
+            log.info("Successfully exported repository for programming exercise {} with title '{}' in {}.",
+                    programmingExercise.getId(), programmingExercise.getTitle(), formatDurationFrom(start));

87-104: Add preflight size/guardrails to avoid OOM on in-memory exports

Given fully in-memory zips, add a conservative size check or circuit breaker (e.g., estimate pack/archive size, cap at configured MB, reject with 413) to protect the node.

Happy to draft a small helper in GitRepositoryExportService to estimate size and enforce a configurable limit (e.g., artemis.exports.max-bytes).

Also applies to: 114-143, 153-200, 210-236

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5233258 and 7c77415.

📒 Files selected for processing (2)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

Files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java
🧠 Learnings (1)
📚 Learning: 2024-11-26T20:43:17.588Z
Learnt from: magaupp
PR: ls1intum/Artemis#9751
File: src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseFactory.java:143-148
Timestamp: 2024-11-26T20:43:17.588Z
Learning: In `src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseFactory.java`, the test package name assigned in `populateUnreleasedProgrammingExercise` does not need to adhere to naming conventions.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java
🧬 Code graph analysis (1)
src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java (6)
src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java (1)
  • Constants (8-516)
src/main/java/de/tum/cit/aet/artemis/core/util/TimeLogUtil.java (1)
  • TimeLogUtil (5-52)
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (1)
  • Profile (41-285)
src/main/java/de/tum/cit/aet/artemis/core/util/FileUtil.java (1)
  • FileUtil (57-932)
src/main/java/de/tum/cit/aet/artemis/exam/config/ExamApiNotPresentException.java (1)
  • ExamApiNotPresentException (11-19)
src/main/java/de/tum/cit/aet/artemis/programming/domain/VcsRepositoryUri.java (1)
  • VcsRepositoryUri (11-180)
🪛 ast-grep (0.38.6)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java

[warning] 241-241: Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic signature. Instead, use PBKDF2 for password hashing or SHA256 or SHA512 for other hash function applications.
Context: MessageDigest.getInstance("SHA-1")
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-sha1-java)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Build and Push Docker Image / Build linux/amd64 Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build linux/arm64 Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-style
  • GitHub Check: client-tests
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: bean-instantiation-check
  • GitHub Check: Analyse

@ekayandan ekayandan temporarily deployed to artemis-test3.artemis.cit.tum.de September 12, 2025 12:46 — with GitHub Actions Inactive
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report205 ran199 passed3 skipped3 failed1h 6m 54s 162ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exam/ExamDateVerification.spec.ts
ts.Exam date verification › Exam timing › Student can start after start Date❌ failure2m 4s 782ms
e2e/exercise/quiz-exercise/QuizExerciseManagement.spec.ts
ts.Quiz Exercise Management › Quiz Exercise Creation › Creates a Quiz with Drag and Drop❌ failure2m 3s 507ms
e2e/exercise/quiz-exercise/QuizExerciseParticipation.spec.ts
ts.Quiz Exercise Participation › DnD Quiz participation › Student can participate in DnD Quiz❌ failure2m 3s 884ms

@ekayandan ekayandan temporarily deployed to artemis-test3.artemis.cit.tum.de September 12, 2025 13:34 — with GitHub Actions Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (1)

48-138: Consider extracting complex sorting and entry writing logic into helper methods.

The writeTo method handles multiple responsibilities: sorting entries, writing header, processing each entry's fields, and buffering content. This could benefit from decomposition for better maintainability.

🧹 Nitpick comments (3)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (2)

62-73: Optimize path comparison with direct byte array comparison.

The current sorting implementation converts paths to UTF-8 bytes on every comparison, which is inefficient for large repositories. Consider caching the byte arrays or using a more efficient comparison.

-        Arrays.sort(entries, (cacheEntryA, cacheEntryB) -> {
-            byte[] pathABytes = cacheEntryA.getPathString().getBytes(StandardCharsets.UTF_8);
-            byte[] pathBBytes = cacheEntryB.getPathString().getBytes(StandardCharsets.UTF_8);
-            int minLength = Math.min(pathABytes.length, pathBBytes.length);
-            for (int i = 0; i < minLength; i++) {
-                int diff = (pathABytes[i] & 0xff) - (pathBBytes[i] & 0xff);
-                if (diff != 0) {
-                    return diff;
-                }
-            }
-            return pathABytes.length - pathBBytes.length;
-        });
+        // Cache path bytes to avoid repeated UTF-8 conversion
+        Arrays.sort(entries, (cacheEntryA, cacheEntryB) -> {
+            return cacheEntryA.getPathString().compareTo(cacheEntryB.getPathString());
+        });

Alternatively, if byte-level sorting is required for Git compatibility, cache the byte arrays in a map before sorting.


134-134: Replace functional stream with simple loop for padding.

The padding logic uses an unnecessarily complex functional approach that's harder to read.

-            IntStream.range(0, padding).map(i -> 0).forEach(buffer::write);
+            for (int i = 0; i < padding; i++) {
+                buffer.write(0);
+            }
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (1)

286-298: Add null check for empty repositories to prevent potential issues.

While the method handles the case where HEAD cannot be resolved, it should also verify that the repository is not empty before attempting archive operations.

     public byte[] createInMemoryZipArchive(Repository repository) throws GitAPIException, IOException {
         ObjectId treeId = repository.resolve("HEAD");
         if (treeId == null) {
             log.debug("Could not resolve tree for HEAD");
             return new byte[0];
         }

+        // Verify the tree exists and is not empty
+        try (var objectReader = repository.newObjectReader()) {
+            if (!objectReader.has(treeId)) {
+                log.debug("Tree object does not exist for HEAD");
+                return new byte[0];
+            }
+        }
+
         ByteArrayOutputStream archiveData = new ByteArrayOutputStream();
         try (Git git = new Git(repository)) {
             git.archive().setFormat("zip").setTree(treeId).setOutputStream(archiveData).call();
         }
         return archiveData.toByteArray();
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c77415 and 3ed79c7.

📒 Files selected for processing (5)
  • src/main/java/de/tum/cit/aet/artemis/core/service/ZipStreamHelper.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseRepositoryExportResource.java
  • src/main/java/de/tum/cit/aet/artemis/core/service/ZipStreamHelper.java
🧰 Additional context used
📓 Path-based instructions (1)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

Files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
🧠 Learnings (5)
📚 Learning: 2025-08-27T09:46:36.480Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java:1839-1839
Timestamp: 2025-08-27T09:46:36.480Z
Learning: In the Artemis codebase, when stubbing GitService.getBareRepository() in tests, it's valid to return a JGit Repository (org.eclipse.jgit.lib.Repository) even though the method signature returns the domain Repository (de.tum.cit.aet.artemis.programming.domain.Repository), because the domain Repository extends JGit's FileRepository, making them polymorphically compatible.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-19T09:13:51.235Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitArchiveHelper.java:0-0
Timestamp: 2025-08-19T09:13:51.235Z
Learning: The domain Repository class (de.tum.cit.aet.artemis.programming.domain.Repository) extends JGit's FileRepository, making it polymorphically compatible with JGit APIs while providing additional Artemis-specific methods like getParticipation() and getLocalPath().

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T13:21:25.531Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java:184-186
Timestamp: 2025-08-11T13:21:25.531Z
Learning: The `getBareRepository` method in `GitService` returns a domain `Repository` object (from `de.tum.cit.aet.artemis.programming.domain.Repository`), not a JGit `Repository` (from `org.eclipse.jgit.lib.Repository`), even though its JavaDoc mentions "JGit repository".

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T13:21:25.531Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java:184-186
Timestamp: 2025-08-11T13:21:25.531Z
Learning: The `getBareRepository` method in `GitService` returns a domain `Repository` object (from `de.tum.cit.aet.artemis.programming.domain.Repository`), not a JGit `Repository` (from `org.eclipse.jgit.lib.Repository`).

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T12:57:51.535Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java:1455-1465
Timestamp: 2025-08-11T12:57:51.535Z
Learning: In the Artemis project, when ekayandan responds with "same as above" to a code review suggestion, it means they want to defer the suggested change to a follow-up PR to keep the current PR scope minimal and focused on the core functionality being implemented.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
🧬 Code graph analysis (1)
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (3)
src/main/java/de/tum/cit/aet/artemis/core/util/FileUtil.java (1)
  • FileUtil (57-932)
src/main/java/de/tum/cit/aet/artemis/programming/domain/VcsRepositoryUri.java (1)
  • VcsRepositoryUri (11-180)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (1)
  • InMemoryRepositoryBuilder (53-363)
🪛 ast-grep (0.38.6)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java

[warning] 150-150: Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic signature. Instead, use PBKDF2 for password hashing or SHA256 or SHA512 for other hash function applications.
Context: MessageDigest.getInstance("SHA-1")
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-sha1-java)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: client-tests
  • GitHub Check: server-tests
  • GitHub Check: Build and Push Docker Image / Build linux/arm64 Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build linux/amd64 Docker Image for ls1intum/artemis
  • GitHub Check: Analyse
🔇 Additional comments (8)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryDirCache.java (2)

26-46: LGTM! Solid foundation for in-memory Git index representation.

The class extends DirCache appropriately and the constructor follows JGit conventions. The constants for file modes are correctly defined.


150-151: SHA-1 usage is appropriate for Git index format compatibility.

While static analysis flags SHA-1 as insecure, this is a false positive. The Git index format specification requires SHA-1 checksums, making this usage necessary for compatibility rather than cryptographic security.

src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (6)

55-76: LGTM! Proper service configuration and JGit archive format registration.

The Spring annotations are correct for the CORE profile, and the constructor properly registers the ZIP format for JGit's ArchiveCommand with appropriate error handling.


115-121: LGTM! Consistent filename sanitization implementation.

The private helper method provides consistent sanitization logic and follows the established pattern from past review feedback.


156-169: LGTM! Clean InputStreamResource wrapper with proper metadata.

The anonymous class extension properly provides filename and content length metadata, which is essential for HTTP responses.


181-185: LGTM! Proper repository snapshot export implementation.

The method correctly delegates to the appropriate services and uses the domain Repository type as confirmed in retrieved learnings.


197-201: LGTM! Consistent full-history export using InMemoryRepositoryBuilder.

The method properly integrates with the new InMemoryRepositoryBuilder for comprehensive repository exports including Git history.


236-263: LGTM! Comprehensive student repository export with proper error handling.

The method includes proper null checks, error logging, and uses snapshot export (without .git directory) for student privacy as intended in the PR objectives.

…positoryExportService.java

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Sep 12, 2025
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅SkippedFailedTime ⏱
End-to-End (E2E) Test Report1 ran1 passed0 skipped0 failed1s 430ms
TestResultTime ⏱
No test annotations available

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (8)
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (8)

69-75: Guard ArchiveCommand format registration against duplicates

Registering the ZIP format on each bean construction can throw on duplicates and pollute logs. Treat duplicate registration as benign.

-        try {
-            ArchiveCommand.registerFormat("zip", new ZipFormat());
-        }
-        catch (Exception e) {
-            log.error("Could not register zip format", e);
-        }
+        try {
+            ArchiveCommand.registerFormat("zip", new ZipFormat());
+        }
+        catch (IllegalArgumentException alreadyRegistered) {
+            log.debug("JGit zip format already registered");
+        }
+        catch (Exception e) {
+            log.warn("Could not register JGit zip format", e);
+        }

186-195: Fix JavaDoc: not using ArchiveCommand for full-history export

This method relies on InMemoryRepositoryBuilder, not ArchiveCommand; update docs to avoid misleading maintenance.

-    /**
-     * Exports a repository with full history including the .git directory directly to memory.
-     * This method uses JGit's ArchiveCommand to create a zip of the working tree and combines it
-     * with the .git directory for full history, all done in memory without disk checkout.
-     *
+    /**
+     * Exports a repository with full history (including a synthetic .git directory) directly to memory.
+     * This uses InMemoryRepositoryBuilder to materialize a full, usable Git working copy without disk I/O.
+     *

203-212: JavaDoc claims “or null if export failed” but method throws on failure

The method does not return null; it propagates IOException. Adjust the JavaDoc for correctness.

-     * @return an InputStreamResource containing the zipped repository, or null if export failed
+     * @return an InputStreamResource containing the zipped repository
+     * @throws IOException if the export fails

214-225: JavaDoc: remove “or null if export failed” (method throws)

-     * @return an InputStreamResource containing the zipped repository, or null if export failed
+     * @return an InputStreamResource containing the zipped repository
+     * @throws IOException if the export fails

122-131: Doc example: use a segment-aware .git exclusion

The suggested path.toString().contains(".git") can over/under-match. Provide a segment-aware example.

-     * // Exclude .git directory
-     * Predicate<Path> excludeGit = path -> !path.toString().contains(".git");
+     * // Exclude any path that is inside a .git directory (segment-aware)
+     * Predicate<Path> excludeGit = path -> {
+     *     for (Path seg : path) {
+     *         if (".git".equals(seg.toString())) {
+     *             return false;
+     *         }
+     *     }
+     *     return true;
+     * };

155-166: Sanitize response filename before returning it

Even though call sites sanitize, defense-in-depth: sanitize here to avoid accidental unsafe filenames leaking to Content-Disposition.

-    private InputStreamResource createZipInputStreamResource(byte[] zipData, String filename) {
+    private InputStreamResource createZipInputStreamResource(byte[] zipData, String filename) {
+        final String safeBase = FileUtil.sanitizeFilename(filename);
         return new InputStreamResource(new ByteArrayInputStream(zipData)) {
             @Override
             public String getFilename() {
-                return filename + ".zip";
+                return safeBase + ".zip";
             }
             @Override
             public long contentLength() {
                 return zipData.length;
             }
         };
     }

235-254: Optional: enforce an upper bound on in-memory export size

To prevent OOM on very large repos, consider rejecting oversized responses early. The PR discussion mentions a deferred size check; wiring it here is straightforward since we know the byte length via contentLength().

@@
-            // For student repositories, we use snapshot export to exclude .git directory for privacy
-            return exportRepositorySnapshot(participation.getVcsRepositoryUri(), repoName);
+            // For student repositories, we use snapshot export to exclude .git directory for privacy
+            var resource = exportRepositorySnapshot(participation.getVcsRepositoryUri(), repoName);
+            // Optional guardrail: reject overly large exports (value configurable)
+            long maxBytes = /* inject via config, e.g., ExportProperties.getMaxZipBytes() */ 0L;
+            if (maxBytes > 0 && resource.contentLength() > maxBytes) {
+                String msg = "Export exceeds max size (" + resource.contentLength() + " > " + maxBytes + " bytes) for participation " + participation.getId();
+                log.warn(msg);
+                exportErrors.add(msg);
+                return null;
+            }
+            return resource;

If you prefer to keep this out of service, implement the check in the REST resource and return HTTP 413.


87-112: Folder exports named “...-student-submission.git” may be confusing

When zipOutput == false, creating a directory that ends with “.git” is surprising (folders with “.git” often indicate Git internals). Consider appending the “.git” suffix only for archive names, not directory copies.

@@
-        if (hideStudentName) {
-            sanitizedRepoName += "-student-submission.git";
-        }
+        if (hideStudentName) {
+            sanitizedRepoName += zipOutput ? "-student-submission.git" : "-student-submission";
+        }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e806b05 and afe5664.

📒 Files selected for processing (1)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

Files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
🧠 Learnings (5)
📚 Learning: 2025-08-27T09:46:36.480Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java:1839-1839
Timestamp: 2025-08-27T09:46:36.480Z
Learning: In the Artemis codebase, when stubbing GitService.getBareRepository() in tests, it's valid to return a JGit Repository (org.eclipse.jgit.lib.Repository) even though the method signature returns the domain Repository (de.tum.cit.aet.artemis.programming.domain.Repository), because the domain Repository extends JGit's FileRepository, making them polymorphically compatible.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-19T09:13:51.235Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitArchiveHelper.java:0-0
Timestamp: 2025-08-19T09:13:51.235Z
Learning: The domain Repository class (de.tum.cit.aet.artemis.programming.domain.Repository) extends JGit's FileRepository, making it polymorphically compatible with JGit APIs while providing additional Artemis-specific methods like getParticipation() and getLocalPath().

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T13:21:25.531Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java:184-186
Timestamp: 2025-08-11T13:21:25.531Z
Learning: The `getBareRepository` method in `GitService` returns a domain `Repository` object (from `de.tum.cit.aet.artemis.programming.domain.Repository`), not a JGit `Repository` (from `org.eclipse.jgit.lib.Repository`), even though its JavaDoc mentions "JGit repository".

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T13:21:25.531Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java:184-186
Timestamp: 2025-08-11T13:21:25.531Z
Learning: The `getBareRepository` method in `GitService` returns a domain `Repository` object (from `de.tum.cit.aet.artemis.programming.domain.Repository`), not a JGit `Repository` (from `org.eclipse.jgit.lib.Repository`).

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
📚 Learning: 2025-08-11T12:57:51.535Z
Learnt from: ekayandan
PR: ls1intum/Artemis#11027
File: src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java:1455-1465
Timestamp: 2025-08-11T12:57:51.535Z
Learning: In the Artemis project, when ekayandan responds with "same as above" to a code review suggestion, it means they want to defer the suggested change to a follow-up PR to keep the current PR scope minimal and focused on the core functionality being implemented.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java
🧬 Code graph analysis (1)
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (4)
src/main/java/de/tum/cit/aet/artemis/core/util/FileUtil.java (1)
  • FileUtil (57-932)
src/main/java/de/tum/cit/aet/artemis/programming/domain/Repository.java (1)
  • Repository (17-84)
src/main/java/de/tum/cit/aet/artemis/programming/service/git/InMemoryRepositoryBuilder.java (1)
  • InMemoryRepositoryBuilder (53-363)
src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCRepositoryUri.java (1)
  • LocalVCRepositoryUri (17-318)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Build and Push Docker Image / Build linux/amd64 Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build linux/arm64 Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-tests
  • GitHub Check: client-style
  • GitHub Check: server-style
  • GitHub Check: server-tests
  • GitHub Check: Analyse
  • GitHub Check: bean-instantiation-check
🔇 Additional comments (1)
src/main/java/de/tum/cit/aet/artemis/programming/service/GitRepositoryExportService.java (1)

180-200: Type usage between domain Repository and JGit is correct

Acknowledging retrieved learnings: domain Repository extends JGit’s FileRepository, so using it with Git and resolve is valid here. No action needed.

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅SkippedFailedTime ⏱
End-to-End (E2E) Test Report1 ran1 passed0 skipped0 failed1s 666ms
TestResultTime ⏱
No test annotations available

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report205 ran200 passed3 skipped2 failed1h 4m 50s 384ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/quiz-exercise/QuizExerciseManagement.spec.ts
ts.Quiz Exercise Management › Quiz Exercise Creation › Creates a Quiz with Drag and Drop❌ failure2m 4s 738ms
e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts
ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors❌ failure2m 19s 219ms

@krusche krusche self-assigned this Sep 12, 2025
@krusche krusche merged commit d668841 into develop Sep 12, 2025
25 of 30 checks passed
@krusche krusche deleted the chore/in-memory-repo-export-artemis branch September 12, 2025 17:26
@github-project-automation github-project-automation bot moved this from Ready For Review to Merged in Artemis Development Sep 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Pull requests that affect the corresponding module programming Pull requests that affect the corresponding module ready for review server Pull requests that update Java code. (Added Automatically!) tests
Projects
Status: Merged
Development

Successfully merging this pull request may close these issues.