-
Notifications
You must be signed in to change notification settings - Fork 89
#1695: Clone settings to temporary directory, analyse, and then move #1878
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
Changes from all commits
a122556
c497948
54a1fed
90e43e7
5dba7b5
aa751ff
b19c6eb
3e936dc
9bee5f1
29f6d1f
8b69339
d1bfbf5
64f1220
462d6d5
373fc19
2552b4d
d32fc4b
154ff6c
6ce1e46
fcc2a0b
900c0f2
45d35be
9cfd047
0e82a26
777b997
b5e23a7
36018b1
94c6511
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,13 +2,18 @@ | |||||
|
|
||||||
| import java.nio.file.Files; | ||||||
| import java.nio.file.Path; | ||||||
| import java.nio.file.StandardCopyOption; | ||||||
| import java.util.function.Predicate; | ||||||
|
|
||||||
| import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | ||||||
|
|
||||||
| import com.devonfw.tools.ide.cli.CliException; | ||||||
| import com.devonfw.tools.ide.context.IdeContext; | ||||||
| import com.devonfw.tools.ide.environment.EnvironmentVariables; | ||||||
| import com.devonfw.tools.ide.git.GitUrl; | ||||||
| import com.devonfw.tools.ide.io.FileAccess; | ||||||
| import com.devonfw.tools.ide.io.FileCopyMode; | ||||||
| import com.devonfw.tools.ide.log.IdeLogLevel; | ||||||
| import com.devonfw.tools.ide.property.FlagProperty; | ||||||
| import com.devonfw.tools.ide.property.StringProperty; | ||||||
|
|
@@ -24,9 +29,6 @@ public class CreateCommandlet extends AbstractUpdateCommandlet { | |||||
| /** {@link StringProperty} for the name of the new project */ | ||||||
| public final StringProperty newProject; | ||||||
|
|
||||||
| /** {@link FlagProperty} for creating a project with settings inside a code repository */ | ||||||
| public final FlagProperty codeRepositoryFlag; | ||||||
|
|
||||||
| /** | ||||||
| * The constructor. | ||||||
| * | ||||||
|
|
@@ -36,7 +38,6 @@ public CreateCommandlet(IdeContext context) { | |||||
|
|
||||||
| super(context); | ||||||
| this.newProject = add(new StringProperty("", true, "project")); | ||||||
| this.codeRepositoryFlag = add(new FlagProperty("--code")); | ||||||
| add(this.settingsRepo); | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -57,16 +58,15 @@ protected void doRun() { | |||||
|
|
||||||
| String newProjectName = this.newProject.getValue(); | ||||||
| Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName); | ||||||
| Path tempProjectPath = this.context.getIdeRoot().resolve("_ide/tmp/projects").resolve(newProjectName); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| LOG.info("Creating new IDEasy project in {}", newProjectPath); | ||||||
| if (!this.context.getFileAccess().isEmptyDir(newProjectPath)) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO we should also check the final target project folder for existence. |
||||||
| this.context.askToContinue("Directory {} already exists. Do you want to continue?", newProjectPath); | ||||||
| } else { | ||||||
| this.context.getFileAccess().mkdirs(newProjectPath); | ||||||
| } | ||||||
|
|
||||||
| initializeProject(newProjectPath); | ||||||
| this.context.setIdeHome(newProjectPath); | ||||||
| initializeProject(tempProjectPath); | ||||||
| this.context.setIdeHome(tempProjectPath); | ||||||
| super.doRun(); | ||||||
| this.context.getFileAccess().writeFileContent(IdeVersion.getVersionString(), newProjectPath.resolve(IdeContext.FILE_SOFTWARE_VERSION)); | ||||||
| IdeLogLevel.SUCCESS.log(LOG, "Successfully created new project '{}'.", newProjectName); | ||||||
|
|
@@ -83,14 +83,91 @@ private void initializeProject(Path newInstancePath) { | |||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| protected boolean isCodeRepository() { | ||||||
| return this.codeRepositoryFlag.isTrue(); | ||||||
| protected void updateSettings() { | ||||||
| super.updateSettings(); | ||||||
| analyzeProject(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. | ||||||
| * The repository can either be a settings repository (with ide.properties or devon.properties on the top level) | ||||||
| * or a code repository (with a settings folder on the top level containing such a file). Otherwise, the project creation fails and an error message is logged. | ||||||
| */ | ||||||
| private void analyzeProject() { | ||||||
| // Settings repository: ide.properties on top levels (or devon.properties for legacy users) | ||||||
| // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) | ||||||
| String projectName = this.context.getProjectName(); | ||||||
| Path actualProjectPath = this.context.getIdeRoot().resolve(projectName); | ||||||
| FileAccess fileAccess = this.context.getFileAccess(); | ||||||
| Path settingsPath = this.context.getSettingsPath(); | ||||||
|
|
||||||
| // Check whether the repository is a valid settings repository, code repository, or neither | ||||||
| if (isSettingsRepository(settingsPath)) { | ||||||
| LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); | ||||||
| moveProject(this.context.getIdeHome(), actualProjectPath); | ||||||
|
|
||||||
| } else if (isCodeRepository(settingsPath)) { | ||||||
| LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); | ||||||
|
|
||||||
| String gitProjectName = GitUrl.of(this.settingsRepo.getValue(0)).getProjectName(); | ||||||
| Path codeFolderPath = actualProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN).resolve(gitProjectName); | ||||||
| // Move temp project to actual project location $IDE_ROOT/<project_name> | ||||||
| moveProject(this.context.getIdeHome(), actualProjectPath); | ||||||
|
|
||||||
| // Move settings fodler containing code to $IDE_ROOT/<project_name>/workspaces/main/<git_project_name> | ||||||
| moveProject(actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS), codeFolderPath); | ||||||
|
|
||||||
| // Set IDE_HOME to new (and actual) project location | ||||||
| this.context.setIdeHome(actualProjectPath); | ||||||
|
|
||||||
| // Link settings folder in IDE_HOME to settings folder in code repository | ||||||
| fileAccess.symlink(codeFolderPath.resolve(IdeContext.FOLDER_SETTINGS), actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS)); | ||||||
|
|
||||||
| } else { | ||||||
| // Repository seems to be invalid. Clean up temporary location and return error | ||||||
| fileAccess.delete(this.context.getIdeHome()); | ||||||
| throw new CliException("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file. " | ||||||
| + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); | ||||||
| } | ||||||
| // Set IDE_HOME to new (and actual) project location | ||||||
| this.context.setIdeHome(actualProjectPath); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Moves files of a new projectfrom the temporary location to the final project location. | ||||||
| * @param oldPath - The path of the file or directory to be moved. | ||||||
| * @param newPath - The path of the destination. | ||||||
| */ | ||||||
| private void moveProject(Path oldPath, Path newPath) { | ||||||
| FileAccess fileAccess = this.context.getFileAccess(); | ||||||
| try { | ||||||
| fileAccess.mkdirs(newPath); | ||||||
| fileAccess.move(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING); | ||||||
| } catch (Exception e) { | ||||||
| LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Checks whether te given repository is a settings repository by checking for the presence of ide.properties or devon.properties on the top level. | ||||||
| * @param repositoryPath - The path of the repository to be checked. | ||||||
| */ | ||||||
| private boolean isSettingsRepository(Path repositoryPath) { | ||||||
| return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES)); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Checks whether te given repository is a code repository by checking for the presence of ide.properties or devon.properties within a settings folder on the top level. | ||||||
| * @param repositoryPath - The path of the repository to be checked. | ||||||
| */ | ||||||
| private boolean isCodeRepository(Path repositoryPath) { | ||||||
| return isSettingsRepository(repositoryPath.resolve(IdeContext.FOLDER_SETTINGS)); | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| protected String getStepMessage() { | ||||||
|
|
||||||
| return "Create (clone) " + (isCodeRepository() ? "code" : "settings") + " repository"; | ||||||
| return "Create (Clone) repository"; | ||||||
| } | ||||||
|
|
||||||
| private void logWelcomeMessage() { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -67,54 +67,7 @@ void testCreateCommandletRun() { | |||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); | ||||||
| } | ||||||
|
|
||||||
| @ParameterizedTest | ||||||
| @ValueSource(strings = { "https://some-code-repository", "ssh://some-settings-repository" }) | ||||||
| void testWarningWhenRepoDoesNotMeetNamingConvention(String invalidRepo, @TempDir Path tempDir) { | ||||||
| // arrange | ||||||
| ProcessContextGitMock gitMock = new ProcessContextGitMock(context, tempDir); | ||||||
| context.setProcessContext(gitMock); | ||||||
| CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); | ||||||
| cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); | ||||||
| cc.codeRepositoryFlag.setValue(!invalidRepo.contains("code")); // raise conflict | ||||||
| cc.settingsRepo.setValue(invalidRepo); | ||||||
| cc.skipTools.setValue(true); | ||||||
| context.setAnswers("yes"); | ||||||
| // act | ||||||
| cc.run(); | ||||||
| // assert | ||||||
| assertThat(context).logAtInteraction().hasMessageContaining("Do you really want to create the project?"); | ||||||
| Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); | ||||||
| assertThat(newProjectPath).exists(); | ||||||
| assertThat(context.getIdeHome()).isEqualTo(newProjectPath); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| void testWarningWhenCodeRepoUsingDefaultMark(@TempDir Path tempDir) { | ||||||
| String invalidCodeRepo = "-"; | ||||||
| // arrange | ||||||
| ProcessContextGitMock gitMock = new ProcessContextGitMock(context, tempDir); | ||||||
| context.setProcessContext(gitMock); | ||||||
| CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); | ||||||
| cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); | ||||||
| cc.settingsRepo.setValue(invalidCodeRepo); | ||||||
| cc.codeRepositoryFlag.setValue(true); | ||||||
| cc.skipTools.setValue(true); | ||||||
| context.setAnswers("https://some-code-repository"); | ||||||
| // act | ||||||
| cc.run(); | ||||||
| // assert | ||||||
| assertThat(context).logAtWarning().hasMessageContaining("'-' is found after '--code'. This is invalid."); | ||||||
| Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); | ||||||
| assertThat(newProjectPath).exists(); | ||||||
| assertThat(context.getIdeHome()).isEqualTo(newProjectPath); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); | ||||||
| assertThat(context.getIdeRoot().resolve("_ide/tmp/projects").resolve(NEW_PROJECT_NAME)).doesNotExist(); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
|
|
@@ -217,9 +170,38 @@ void testWelcomeMessageDisplayed() { | |||||
| // assert | ||||||
| Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); | ||||||
| assertThat(newProjectPath).exists(); | ||||||
| assertThat(context.getIdeHome()).isEqualTo(newProjectPath); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); | ||||||
| assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); | ||||||
| assertThat(context.getIdeRoot().resolve("_ide/tmp/projects").resolve(NEW_PROJECT_NAME)).doesNotExist(); | ||||||
| assertThat(context).logAtInfo().hasMessageContaining("Welcome to your new IDEasy project!"); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| void testProjectWithInvalidRepositoryNotCreated() { | ||||||
|
|
||||||
| // arrange - create a new project that is invalid (does not contain ide.properties file) | ||||||
| GitContextImplMock gitContextImplMock = new GitContextImplMock(context, TEST_RESOURCES.resolve("pypi")); | ||||||
|
|
||||||
| context.setGitContext(gitContextImplMock); | ||||||
| CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); | ||||||
| cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); | ||||||
| cc.settingsRepo.setValue(IdeContext.DEFAULT_SETTINGS_REPO_URL); | ||||||
| cc.skipTools.setValue(true); | ||||||
|
|
||||||
| // act - run the create command | ||||||
| assertThatThrownBy(() -> cc.run()) | ||||||
| .isInstanceOf(CliException.class) | ||||||
| .hasMessageContaining("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file.") | ||||||
| .hasMessageContaining("The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); | ||||||
|
|
||||||
| // assert | ||||||
| Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); | ||||||
| assertThat(newProjectPath).doesNotExist(); | ||||||
| assertThat(context.getIdeRoot().resolve("_ide/tmp/projects").resolve(NEW_PROJECT_NAME)).doesNotExist(); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| void testCreateWithDashPlaceholderAsCliArgument() { | ||||||
| // arrange - see https://github.com/devonfw/IDEasy/issues/2106 | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.