Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated code lifecycle: Add auxiliary repositories in exercise export and import #9612

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Repository> auxiliaryRepositories = new ArrayList<>();
for (AuxiliaryRepository auxiliaryRepository : newExercise.getAuxiliaryRepositories()) {
auxiliaryRepositories.add(gitService.getOrCheckoutRepository(auxiliaryRepository.getVcsRepositoryUri(), false));
}
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved

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);
}
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved

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<String, String> replacements, Repository... repositories) {
private void replaceImportedExerciseShortName(Map<String, String> replacements, List<Repository> 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<Repository> 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);
}
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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));
}
Expand Down Expand Up @@ -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<Path> result;
try (Stream<Path> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved
// 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;
});
}
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved
this.exercise = progEx;
break;
default:
this.alertService.error('artemisApp.exercise.importFromFile.notSupportedExerciseType', {
Expand Down
Loading