diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java index 024d34348470..20d98af84bc8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java @@ -186,6 +186,7 @@ protected void exportProblemStatementAndEmbeddedFilesAndExerciseDetails(Exercise if (exercise instanceof ProgrammingExercise programmingExercise) { // Used for a save typecast, this should always be true since this class only works with programming exercises. programmingExerciseTaskService.replaceTestIdsWithNames(programmingExercise); + programmingExercise.setAuxiliaryRepositories(auxiliaryRepositoryRepository.findByExerciseId(exercise.getId())); } super.exportProblemStatementAndEmbeddedFilesAndExerciseDetails(exercise, exportErrors, exportDir, pathsToBeZipped); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java index 3c8b1671cba9..784e29599185 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java @@ -8,6 +8,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -33,6 +35,7 @@ import de.tum.cit.aet.artemis.core.service.FileService; import de.tum.cit.aet.artemis.core.service.ProfileService; import de.tum.cit.aet.artemis.core.service.ZipFileService; +import de.tum.cit.aet.artemis.programming.domain.AuxiliaryRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.Repository; import de.tum.cit.aet.artemis.programming.domain.RepositoryType; @@ -170,44 +173,72 @@ private void importRepositoriesFromFile(ProgrammingExercise newExercise, Path ba Repository templateRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUri(newExercise.getTemplateRepositoryUri()), false); Repository solutionRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUri(newExercise.getSolutionRepositoryUri()), false); Repository testRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUri(newExercise.getTestRepositoryUri()), false); + List auxiliaryRepositories = new ArrayList<>(); + for (AuxiliaryRepository auxiliaryRepository : newExercise.getAuxiliaryRepositories()) { + auxiliaryRepositories.add(gitService.getOrCheckoutRepository(auxiliaryRepository.getVcsRepositoryUri(), false)); + } - copyImportedExerciseContentToRepositories(templateRepo, solutionRepo, testRepo, basePath); - replaceImportedExerciseShortName(Map.of(oldExerciseShortName, newExercise.getShortName()), templateRepo, solutionRepo, testRepo); + copyImportedExerciseContentToRepositories(templateRepo, solutionRepo, testRepo, auxiliaryRepositories, basePath); + replaceImportedExerciseShortName(Map.of(oldExerciseShortName, newExercise.getShortName()), List.of(solutionRepo, templateRepo, testRepo)); + replaceImportedExerciseShortName(Map.of(oldExerciseShortName, newExercise.getShortName()), auxiliaryRepositories); gitService.stageAllChanges(templateRepo); gitService.stageAllChanges(solutionRepo); gitService.stageAllChanges(testRepo); + for (Repository auxRepo : auxiliaryRepositories) { + gitService.stageAllChanges(auxRepo); + } gitService.commitAndPush(templateRepo, "Import template from file", true, user); gitService.commitAndPush(solutionRepo, "Import solution from file", true, user); gitService.commitAndPush(testRepo, "Import tests from file", true, user); + for (Repository auxRepo : auxiliaryRepositories) { + gitService.commitAndPush(auxRepo, "Import auxiliary repo from file", true, user); + } + } - private void replaceImportedExerciseShortName(Map replacements, Repository... repositories) { + private void replaceImportedExerciseShortName(Map replacements, List repositories) { for (Repository repository : repositories) { fileService.replaceVariablesInFileRecursive(repository.getLocalPath(), replacements, SHORT_NAME_REPLACEMENT_EXCLUSIONS); } } - private void copyImportedExerciseContentToRepositories(Repository templateRepo, Repository solutionRepo, Repository testRepo, Path basePath) throws IOException { + private void copyImportedExerciseContentToRepositories(Repository templateRepo, Repository solutionRepo, Repository testRepo, List auxiliaryRepositories, + Path basePath) throws IOException { repositoryService.deleteAllContentInRepository(templateRepo); repositoryService.deleteAllContentInRepository(solutionRepo); repositoryService.deleteAllContentInRepository(testRepo); - copyExerciseContentToRepository(templateRepo, RepositoryType.TEMPLATE, basePath); - copyExerciseContentToRepository(solutionRepo, RepositoryType.SOLUTION, basePath); - copyExerciseContentToRepository(testRepo, RepositoryType.TESTS, basePath); + for (Repository auxRepo : auxiliaryRepositories) { + repositoryService.deleteAllContentInRepository(auxRepo); + } + + copyExerciseContentToRepository(templateRepo, RepositoryType.TEMPLATE.getName(), basePath); + copyExerciseContentToRepository(solutionRepo, RepositoryType.SOLUTION.getName(), basePath); + copyExerciseContentToRepository(testRepo, RepositoryType.TESTS.getName(), basePath); + for (Repository auxRepo : auxiliaryRepositories) { + String[] parts = auxRepo.getLocalPath().toString().split("-"); + var auxRepoName = String.join("-", Arrays.copyOfRange(parts, 1, parts.length)); + copyExerciseContentToRepository(auxRepo, auxRepoName, basePath); + } } /** * Copies everything from the extracted zip file to the repository, except the .git folder * - * @param repository the repository to which the content should be copied - * @param repositoryType the type of the repository - * @param basePath the path to the extracted zip file + * @param repository the repository to which the content should be copied + * @param repoName the name of the repository + * @param basePath the path to the extracted zip file **/ - private void copyExerciseContentToRepository(Repository repository, RepositoryType repositoryType, Path basePath) throws IOException { - FileUtils.copyDirectory(retrieveRepositoryDirectoryPath(basePath, repositoryType.getName()).toFile(), repository.getLocalPath().toFile(), - new NotFileFilter(new NameFileFilter(".git"))); + private void copyExerciseContentToRepository(Repository repository, String repoName, Path basePath) throws IOException { + // @formatter:off + FileUtils.copyDirectory( + retrieveRepositoryDirectoryPath(basePath, repoName).toFile(), + repository.getLocalPath().toFile(), + new NotFileFilter(new NameFileFilter(".git")) + ); + // @formatter:on + try (var files = Files.walk(repository.getLocalPath())) { files.filter(file -> "gradlew".equals(file.getFileName().toString())).forEach(file -> file.toFile().setExecutable(true)); } @@ -242,17 +273,17 @@ private void checkRepositoryForTypeExists(Path path, RepositoryType repoType) th } } - private Path retrieveRepositoryDirectoryPath(Path dirPath, String repoType) { + private Path retrieveRepositoryDirectoryPath(Path dirPath, String repoName) { List result; try (Stream walk = Files.walk(dirPath)) { - result = walk.filter(Files::isDirectory).filter(file -> file.getFileName().toString().endsWith("-" + repoType)).toList(); + result = walk.filter(Files::isDirectory).filter(file -> file.getFileName().toString().endsWith("-" + repoName)).toList(); } catch (IOException e) { throw new BadRequestAlertException("Could not read the directory", "programmingExercise", "couldnotreaddirectory"); } if (result.size() != 1) { throw new IllegalArgumentException( - "There are either no or more than one sub-directories containing " + repoType + " in their name. Please make sure that there is exactly one."); + "There are either no or more than one sub-directories containing " + repoName + " in their name. Please make sure that there is exactly one."); } return result.getFirst(); diff --git a/src/main/webapp/app/exercises/shared/import/from-file/exercise-import-from-file.component.ts b/src/main/webapp/app/exercises/shared/import/from-file/exercise-import-from-file.component.ts index 688b7cd6fb44..79083e180ea2 100644 --- a/src/main/webapp/app/exercises/shared/import/from-file/exercise-import-from-file.component.ts +++ b/src/main/webapp/app/exercises/shared/import/from-file/exercise-import-from-file.component.ts @@ -50,10 +50,17 @@ export class ExerciseImportFromFileComponent implements OnInit { switch (this.exerciseType) { case ExerciseType.PROGRAMMING: this.exercise = JSON.parse(exerciseDetails as string) as ProgrammingExercise; + const progEx = this.exercise as ProgrammingExercise; // This is needed to make sure that old exported programming exercises can be imported - if (!(this.exercise as ProgrammingExercise).buildConfig) { - (this.exercise as ProgrammingExercise).buildConfig = copyBuildConfigFromExerciseJson(exerciseJson as ProgrammingExerciseBuildConfig); + if (!progEx.buildConfig) { + progEx.buildConfig = copyBuildConfigFromExerciseJson(exerciseJson as ProgrammingExerciseBuildConfig); } + if (progEx.auxiliaryRepositories) { + progEx.auxiliaryRepositories!.forEach((repo, index) => { + progEx.auxiliaryRepositories![index].id = undefined; + }); + } + this.exercise = progEx; break; default: this.alertService.error('artemisApp.exercise.importFromFile.notSupportedExerciseType', {