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

#757: Settings in code repository #850

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private void setupConf(Path template, Path conf) {
}
}

private void updateSettings() {
protected void updateSettings() {

Path settingsPath = this.context.getSettingsPath();
GitContext gitContext = this.context.getGitContext();
Expand All @@ -132,6 +132,7 @@ private void updateSettings() {
}
gitContext.pullOrClone(GitUrl.of(repository), settingsPath);
}
this.context.saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath());
step.success("Successfully updated settings repository.");
} finally {
if (step != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.devonfw.tools.ide.commandlet;

import java.nio.file.Files;
import java.nio.file.Path;

import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.git.GitUrl;
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.property.FlagProperty;
import com.devonfw.tools.ide.property.StringProperty;
Expand All @@ -18,6 +20,9 @@ public class CreateCommandlet extends AbstractUpdateCommandlet {
/** {@link FlagProperty} for skipping the setup of git repositories */
public final FlagProperty skipRepositories;

/** {@link FlagProperty} for creating a project with settings inside a code repository */
public final FlagProperty codeRepositoryFlag;

/**
* The constructor.
*
Expand All @@ -28,6 +33,7 @@ public CreateCommandlet(IdeContext context) {
super(context);
this.newProject = add(new StringProperty("", true, "project"));
this.skipRepositories = add(new FlagProperty("--skip-repositories"));
this.codeRepositoryFlag = add(new FlagProperty("--code"));
add(this.settingsRepo);
}

Expand Down Expand Up @@ -59,12 +65,32 @@ public void run() {
initializeProject(newProjectPath);
this.context.setIdeHome(newProjectPath);
super.run();
hohwille marked this conversation as resolved.
Show resolved Hide resolved

if (this.skipRepositories.isTrue()) {
this.context.info("Skipping the cloning of project repositories as specified by the user.");
} else {
updateRepositories();
}
this.context.success("Successfully created new project '{}'.", newProjectName);

}

private void initializeCodeRepository(String repoUrl) {

// clone the given repository into IDE_HOME/workspaces/main
GitUrl gitUrl = GitUrl.of(repoUrl);
Path codeRepoPath = this.context.getWorkspacePath().resolve(gitUrl.getProjectName());
this.context.getGitContext().pullOrClone(gitUrl, codeRepoPath);
hohwille marked this conversation as resolved.
Show resolved Hide resolved

// check for settings folder and create symlink to IDE_HOME/settings
Path settingsFolder = codeRepoPath.resolve(IdeContext.FOLDER_SETTINGS);
if (Files.exists(settingsFolder)) {
this.context.getFileAccess().symlink(settingsFolder, this.context.getSettingsPath());
// create a file in IDE_HOME with the current local commit id
this.context.saveCurrentCommitId(codeRepoPath, this.context.getSettingsCommitIdPath());
} else {
this.context.warning("No settings folder was found inside the code repository.");
}
}

private void initializeProject(Path newInstancePath) {
Expand All @@ -79,4 +105,15 @@ private void updateRepositories() {

this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class).run();
}

@Override
protected void updateSettings() {

if (codeRepositoryFlag.isTrue() && !settingsRepo.getValue().isBlank()) {
Copy link
Member

Choose a reason for hiding this comment

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

If you do ide create my-test-project you will be asked for the settings git URL.
Now I can do ide create my-test-project --code https://github.com/myorg/myrepo.git to use the new feature.
It seems inconsistent to me that if I call ide create my-test-project --code that I will be asked for a settings git URL and if I provide https://github.com/myorg/myrepo.git this will not be treated as code repo and more or less the --code option has no effect.
If the value is blank, you should ask for the git code repo URL.
Unlike with the settings git URL there will be no default and the question should also indicate that a code repo with settings inside is requested.

String repoUrl = settingsRepo.getValue();
initializeCodeRepository(repoUrl);
} else {
super.updateSettings();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ private void logSettingsLegacyStatus() {
}

private void logSettingsGitStatus() {
Path settingsPath = this.context.getSettingsPath();
Path settingsPath = this.context.getSettingsGitRepository();
if (settingsPath != null) {
GitContext gitContext = this.context.getGitContext();
if (gitContext.isRepositoryUpdateAvailable(settingsPath)) {
if (gitContext.isRepositoryUpdateAvailable(settingsPath, this.context.getSettingsCommitIdPath())) {
this.context.warning("Your settings are not up-to-date, please run 'ide update'.");
} else {
this.context.success("Your settings are up-to-date.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.devonfw.tools.ide.context;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
Expand All @@ -12,6 +13,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

import com.devonfw.tools.ide.cli.CliAbortException;
import com.devonfw.tools.ide.cli.CliArgument;
Expand Down Expand Up @@ -77,6 +79,8 @@ public abstract class AbstractIdeContext implements IdeContext {

protected Path settingsPath;

private Path settingsCommitIdPath;

private Path softwarePath;

private Path softwareExtraPath;
Expand Down Expand Up @@ -241,6 +245,7 @@ public void setCwd(Path userDir, String workspace, Path ideHome) {
this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName);
this.confPath = this.ideHome.resolve(FOLDER_CONF);
this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS);
this.settingsCommitIdPath = this.ideHome.resolve(IdeContext.SETTINGS_COMMIT_ID);
this.softwarePath = this.ideHome.resolve(FOLDER_SOFTWARE);
this.softwareExtraPath = this.softwarePath.resolve(FOLDER_EXTRA);
this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS);
Expand Down Expand Up @@ -430,6 +435,30 @@ public Path getSettingsPath() {
return this.settingsPath;
}

@Override
public Path getSettingsGitRepository() {

Path settingsPath = getSettingsPath();

if (Objects.isNull(settingsPath)) {
error("No settings repository was found.");
}

// check whether the settings path has a .git folder only if its not a symbolic link
if (!Files.exists(settingsPath.resolve(".git")) && !Files.isSymbolicLink(settingsPath)) {
error("Settings repository exists but is not a git repository.");
return null;
}

return settingsPath;
}

@Override
public Path getSettingsCommitIdPath() {

return this.settingsCommitIdPath;
}

@Override
public Path getConfPath() {

Expand Down Expand Up @@ -857,10 +886,11 @@ private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
if (cmd.isIdeHomeRequired()) {
debug(getMessageIdeHomeFound());
}
if (this.settingsPath != null) {
if (getGitContext().isRepositoryUpdateAvailable(this.settingsPath) ||
(getGitContext().fetchIfNeeded(this.settingsPath) && getGitContext().isRepositoryUpdateAvailable(this.settingsPath))) {
interaction("Updates are available for the settings repository. If you want to pull the latest changes, call ide update.");
Path settingsRepository = getSettingsGitRepository();
if (settingsRepository != null) {
if (getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()) ||
(getGitContext().fetchIfNeeded(settingsRepository) && getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()))) {
interaction("Updates are available for the settings repository. If you want to apply the latest changes, call \"ide update\"");
}
}
}
Expand Down Expand Up @@ -1123,4 +1153,21 @@ public IdeStartContextImpl getStartContext() {
public void reload() {
this.variables = null;
}

@Override
public void saveCurrentCommitId(Path repository, Path trackedCommitIdPath) {

trace("Saving commit Id of {} into {}", repository, trackedCommitIdPath);
if (Objects.isNull(repository)) {
return;
}
String currentCommitId = getGitContext().runGitCommandAndGetSingleOutput("Failed to get current commit id.", repository, "rev-parse", "HEAD");
if (currentCommitId != null) {
try {
Files.writeString(trackedCommitIdPath, currentCommitId);
} catch (IOException e) {
throw new IllegalStateException("Failed to save commit ID", e);
}
}
}
}
23 changes: 23 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ public interface IdeContext extends IdeStartContext {
/** Legacy folder name used as compatibility fallback if {@link #FOLDER_TEMPLATES} does not exist. */
String FOLDER_LEGACY_TEMPLATES = "devon";

/**
* file containing the current local commit hash of the settings repository. */
String SETTINGS_COMMIT_ID = ".commit.id";

/**
* @return {@code true} if {@link #isOfflineMode() offline mode} is active or we are NOT {@link #isOnline() online}, {@code false} otherwise.
*/
Expand Down Expand Up @@ -351,6 +355,17 @@ default void requireOnline(String purpose) {
*/
Path getSettingsPath();

/**
*
* @return the {@link Path} to the {@code settings} folder with the cloned git repository containing the project configuration only if the settings repository is in fact a git repository.
*/
Path getSettingsGitRepository();

/**
* @return the {@link Path} to the file containing the last tracked commit Id of the settings repository.
*/
Path getSettingsCommitIdPath();

/**
* @return the {@link Path} to the templates folder inside the {@link #getSettingsPath() settings}. The relative directory structure in this templates folder
* is to be applied to {@link #getIdeHome() IDE_HOME} when the project is set up.
Expand Down Expand Up @@ -588,4 +603,12 @@ default String findBashRequired() {
*/
void logIdeHomeAndRootStatus();

/**
* Saves the current git commit ID of a repository to a file given as an argument.
*
* @param repository the path to the git repository
* @param trackedCommitIdPath the path to the file where the commit Id will be written.
*/
void saveCurrentCommitId(Path repository, Path trackedCommitIdPath);

}
25 changes: 23 additions & 2 deletions cli/src/main/java/com/devonfw/tools/ide/git/GitContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,23 @@ public interface GitContext {
* Checks if there are updates available for the Git repository in the specified target folder by comparing the local commit hash with the remote commit
* hash.
*
* @param repository the {@link Path} to the target folder where the git repository is located. This should be the folder containing the ".git"
* subfolder.
* @param repository the {@link Path} to the target folder where the git repository is located.
hohwille marked this conversation as resolved.
Show resolved Hide resolved
* @return {@code true} if the remote repository contains commits that are not present in the local repository, indicating that updates are available.
* {@code false} if the local and remote repositories are in sync, or if there was an issue retrieving the commit hashes.
*/
boolean isRepositoryUpdateAvailable(Path repository);

/**
* Checks if there are updates available for the Git repository in the specified target folder by comparing the local commit hash with the remote commit
* hash.
*
* @param repository the {@link Path} to the target folder where the git repository is located.
* @param trackedCommitIdPath the {@link Path} to a file containing the last tracked commit ID of this repository.
* @return {@code true} if the remote repository contains commits that are not present in the local repository, indicating that updates are available.
* {@code false} if the local and remote repositories are in sync, or if there was an issue retrieving the commit hashes.
*/
boolean isRepositoryUpdateAvailable(Path repository, Path trackedCommitIdPath);

/**
* Attempts a git pull and reset if required.
*
Expand Down Expand Up @@ -175,4 +185,15 @@ default void reset(Path repository, String branch) {
*/
String determineRemote(Path repository);

/**
* Executes a Git command and returns a single line of output.
*
* @param warningOnError The warning message to log if the command fails or produces unexpected output
* @param directory The directory in which to execute the Git command
* @param args Variable number of command arguments to pass to Git
* @return The single line of output if successful and exactly one line is produced, null otherwise
* (with a warning logged containing warningOnError and additional context)
*/
String runGitCommandAndGetSingleOutput(String warningOnError, Path directory, String... args);

}
18 changes: 17 additions & 1 deletion cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devonfw.tools.ide.git;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
Expand Down Expand Up @@ -59,6 +60,21 @@ public boolean isRepositoryUpdateAvailable(Path repository) {
return !localCommitId.equals(remoteCommitId);
}

@Override
public boolean isRepositoryUpdateAvailable(Path repository, Path trackedCommitIdPath) {

verifyGitInstalled();
String trackedCommitId;
try {
trackedCommitId = Files.readString(trackedCommitIdPath);
} catch (IOException e) {
return false;
}

String remoteCommitId = runGitCommandAndGetSingleOutput("Failed to get the remote commit id.", repository, "rev-parse", "@{u}");
return !trackedCommitId.equals(remoteCommitId);
}

@Override
public void pullOrCloneAndResetIfNeeded(GitUrl gitUrl, Path repository, String remoteName) {

Expand Down Expand Up @@ -262,7 +278,7 @@ private void runGitCommand(Path directory, List<String> args) {
runGitCommand(directory, args.toArray(String[]::new));
}

private String runGitCommandAndGetSingleOutput(String warningOnError, Path directory, String... args) {
public String runGitCommandAndGetSingleOutput(String warningOnError, Path directory, String... args) {

ProcessResult result = runGitCommand(directory, ProcessMode.DEFAULT_CAPTURE, args);
if (result.isSuccessful()) {
Expand Down
15 changes: 15 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/git/GitUrl.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ public String toString() {
return this.url + "#" + this.branch;
}

/**
* Extracts the project name from an git URL.
* For URLs like "https://github.com/devonfw/ide-urls.git" returns "ide-urls"
*
* @return the project name without ".git" extension
*/
public String getProjectName() {
String path = this.url.substring(this.url.indexOf("://") + 3);
if (path.endsWith(".git")) {
path = path.substring(0, path.length() - 4);
}
String[] parts = path.split("/");
return parts[parts.length - 1];
}

/**
* @param gitUrl the {@link #toString() string representation} of a {@link GitUrl}. May contain a branch name as {@code «url»#«branch»}.
* @return the parsed {@link GitUrl}.
Expand Down
1 change: 1 addition & 0 deletions cli/src/main/resources/nls/Help.properties
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ cmd.vscode=Tool commandlet for Visual Studio Code (IDE).
cmd.vscode.detail=Visual Studio Code (VS Code) is a popular code editor developed by Microsoft. Detailed documentation can be found at https://code.visualstudio.com/docs/
commandlets=Available commandlets:
opt.--batch=enable batch mode (non-interactive).
opt.--code=use git repository as both code and settings repository
opt.--debug=enable debug logging.
opt.--force=enable force mode.
opt.--locale=the locale (e.g. '--locale=de' for German language).
Expand Down
1 change: 1 addition & 0 deletions cli/src/main/resources/nls/Help_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ cmd.vscode=Werkzeug Kommando für Visual Studio Code (IDE).
cmd.vscode.detail=Visual Studio Code (VS Code) ist ein beliebter Code-Editor, der von Microsoft entwickelt wurde. Detaillierte Dokumentation ist zu finden unter https://code.visualstudio.com/docs/
commandlets=Verfügbare Kommandos:
opt.--batch=Aktiviert den Batch-Modus (nicht-interaktive Stapelverarbeitung).
opt.--code=Git-Repository sowohl als Code- als auch als Settings-Repository verwenden.
opt.--debug=Aktiviert Debug-Ausgaben (Fehleranalyse).
opt.--force=Aktiviert den Force-Modus (Erzwingen).
opt.--locale=Die Spracheinstellungen (z.B. 'en' für Englisch).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class HelpCommandletTest extends AbstractIdeContextTest {
* Test of {@link HelpCommandlet} does not require home.
*/
@Test
public void testThatHomeIsNotReqired() {
public void testThatHomeIsNotRequired() {

// arrange
IdeContext context = IdeTestContextMock.get();
Expand Down
Loading
Loading