From 6bd21c9560f6d3299bd6c3161669e24ff902657a Mon Sep 17 00:00:00 2001 From: mbilda Date: Tue, 15 Apr 2025 08:34:31 +0200 Subject: [PATCH] https://github.com/devonfw/IDEasy/issues/1205 : Fix Git implementation - refactored GitContextImpl --- .../devonfw/tools/ide/git/GitContextImpl.java | 266 ++++++------------ 1 file changed, 89 insertions(+), 177 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java index 267938f6f7..e098958394 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java @@ -3,14 +3,11 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.process.ProcessContext; -import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; import com.devonfw.tools.ide.variable.IdeVariables; @@ -32,47 +29,29 @@ public GitContextImpl(IdeContext context) { @Override public void pullOrCloneIfNeeded(GitUrl gitUrl, Path repository) { - - GitOperation.PULL_OR_CLONE.executeIfNeeded(this.context, gitUrl, repository, null); + executeGitOperation(GitOperation.PULL_OR_CLONE, gitUrl, repository, null); } @Override public boolean fetchIfNeeded(Path repository) { - - return fetchIfNeeded(repository, null, null); + return executeGitOperation(GitOperation.FETCH, new GitUrl("https://dummy.url/repo.git", null), repository, null); } @Override public boolean fetchIfNeeded(Path repository, String remote, String branch) { - - return GitOperation.FETCH.executeIfNeeded(this.context, new GitUrl("https://dummy.url/repo.git", branch), repository, remote); + return executeGitOperation(GitOperation.FETCH, new GitUrl("https://dummy.url/repo.git", branch), repository, remote); } @Override public boolean isRepositoryUpdateAvailable(Path repository) { - - verifyGitInstalled(); - String localCommitId = runGitCommandAndGetSingleOutput("Failed to get the local commit id.", repository, "rev-parse", "HEAD"); - String remoteCommitId = runGitCommandAndGetSingleOutput("Failed to get the remote commit id.", repository, "rev-parse", "@{u}"); - if ((localCommitId == null) || (remoteCommitId == null)) { - return false; - } - return !localCommitId.equals(remoteCommitId); + return compareCommitIds(repository, "HEAD", "@{u}"); } @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); + String trackedCommitId = readFileContent(trackedCommitIdPath); + String remoteCommitId = getCommitId(repository, "@{u}"); + return !Objects.equals(trackedCommitId, remoteCommitId); } @Override @@ -88,13 +67,10 @@ public void pullOrClone(GitUrl gitUrl, Path repository) { Objects.requireNonNull(repository); Objects.requireNonNull(gitUrl); - if (Files.isDirectory(repository.resolve(GIT_FOLDER))) { - // checks for remotes + if (isGitRepository(repository)) { String remote = determineRemote(repository); if (remote == null) { - String message = repository + " is a local git repository with no remote - if you did this for testing, you may continue...\n" - + "Do you want to ignore the problem and continue anyhow?"; - this.context.askToContinue(message); + this.context.askToContinue(repository + " is a local git repository with no remote. Do you want to continue?"); } else { pull(repository); } @@ -103,57 +79,16 @@ public void pullOrClone(GitUrl gitUrl, Path repository) { } } - /** - * Handles errors which occurred during git pull. - * - * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. It is not the parent directory where - * git will by default create a sub-folder by default on clone but the * final folder that will contain the ".git" subfolder. - * @param result the {@link ProcessResult} to evaluate. - */ - private void handleErrors(Path targetRepository, ProcessResult result) { - - if (!result.isSuccessful()) { - String message = "Failed to update git repository at " + targetRepository; - if (this.context.isOffline()) { - this.context.warning(message); - this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); - } else { - this.context.error(message); - if (this.context.isOnline()) { - this.context.error("See above error for details. If you have local changes, please stash or revert and retry."); - } else { - this.context.error("It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); - } - this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); - } - } - } - @Override public void clone(GitUrl gitUrl, Path repository) { verifyGitInstalled(); - GitUrlSyntax gitUrlSyntax = IdeVariables.PREFERRED_GIT_PROTOCOL.get(getContext()); - gitUrl = gitUrlSyntax.format(gitUrl); - if (this.context.isOfflineMode()) { - this.context.requireOnline("git clone of " + gitUrl); - } + gitUrl = IdeVariables.PREFERRED_GIT_PROTOCOL.get(context).format(gitUrl); + requireOnline("git clone of " + gitUrl); this.context.getFileAccess().mkdirs(repository); - List args = new ArrayList<>(7); - args.add("clone"); - if (this.context.isQuietMode()) { - args.add("-q"); - } - args.add("--recursive"); - args.add(gitUrl.url()); - args.add("--config"); - args.add("core.autocrlf=false"); - args.add("."); + List args = List.of("clone", "--recursive", gitUrl.url(), "--config", "core.autocrlf=false", "."); runGitCommand(repository, args); - String branch = gitUrl.branch(); - if (branch != null) { - runGitCommand(repository, "switch", branch); - } + switchBranch(repository, gitUrl.branch()); } @Override @@ -165,64 +100,36 @@ public void pull(Path repository) { return; } ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT, "--no-pager", "pull", "--quiet"); - if (!result.isSuccessful()) { - String branchName = determineCurrentBranch(repository); - this.context.warning("Git pull on branch {} failed for repository {}.", branchName, repository); - handleErrors(repository, result); - } + handleErrors(repository, result, "Git pull failed."); } @Override public void fetch(Path repository, String remote, String branch) { verifyGitInstalled(); - if (branch == null) { - branch = determineCurrentBranch(repository); - } - if (remote == null) { - remote = determineRemote(repository); - } - - ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT_CAPTURE, "fetch", Objects.requireNonNullElse(remote, "origin"), branch); - - if (!result.isSuccessful()) { - this.context.warning("Git fetch for '{}/{} failed.'.", remote, branch); - } + runGitCommand(repository, ProcessMode.DEFAULT_CAPTURE, "fetch", Objects.requireNonNullElse(remote, "origin"), branch); } @Override public String determineCurrentBranch(Path repository) { - - verifyGitInstalled(); - return runGitCommandAndGetSingleOutput("Failed to determine current branch of git repository", repository, "branch", "--show-current"); + return getCommitId(repository, "branch", "--show-current"); } @Override public String determineRemote(Path repository) { - - verifyGitInstalled(); - return runGitCommandAndGetSingleOutput("Failed to determine current origin of git repository.", repository, "remote"); + return getCommitId(repository, "remote"); } @Override public void reset(Path repository, String branchName, String remoteName) { verifyGitInstalled(); - if ((remoteName == null) || remoteName.isEmpty()) { - remoteName = DEFAULT_REMOTE; - } - if ((branchName == null) || branchName.isEmpty()) { - branchName = GitUrl.BRANCH_MASTER; - } + branchName = Objects.requireNonNullElse(branchName, GitUrl.BRANCH_MASTER); + remoteName = Objects.requireNonNullElse(remoteName, DEFAULT_REMOTE); ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT, "diff-index", "--quiet", "HEAD"); if (!result.isSuccessful()) { - // reset to origin/master - this.context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", repository, remoteName, branchName); - result = runGitCommand(repository, ProcessMode.DEFAULT, "reset", "--hard", remoteName + "/" + branchName); - if (!result.isSuccessful()) { - this.context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, repository); - handleErrors(repository, result); - } + this.context.warning("Resetting {} to {}/{}.", repository, remoteName, branchName); + runGitCommand(repository, ProcessMode.DEFAULT, "reset", "--hard", remoteName + "/" + branchName); } } @@ -233,101 +140,106 @@ public void cleanup(Path repository) { // check for untracked files ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT_CAPTURE, "ls-files", "--other", "--directory", "--exclude-standard"); if (!result.getOut().isEmpty()) { - // delete untracked files - this.context.warning("Git detected untracked files in {} and is attempting a cleanup.", repository); + this.context.warning("Cleaning up untracked files in {}.", repository); runGitCommand(repository, "clean", "-df"); } } @Override public String retrieveGitUrl(Path repository) { + return getCommitId(repository, "config", "--get", "remote.origin.url"); + } - verifyGitInstalled(); - return runGitCommandAndGetSingleOutput("Failed to retrieve git URL for repository", repository, "config", "--get", "remote.origin.url"); + @Override + public void saveCurrentCommitId(Path repository, Path trackedCommitIdPath) { + String currentCommitId = getCommitId(repository, "rev-parse", "HEAD"); + if (currentCommitId != null) { + writeFileContent(trackedCommitIdPath, currentCommitId); + } } - IdeContext getContext() { + private boolean executeGitOperation(GitOperation operation, GitUrl gitUrl, Path repository, String remote) { + return operation.executeIfNeeded(this.context, gitUrl, repository, remote); + } - return this.context; + private boolean compareCommitIds(Path repository, String localRef, String remoteRef) { + String localCommitId = getCommitId(repository, "rev-parse", localRef); + String remoteCommitId = getCommitId(repository, "rev-parse", remoteRef); + return !Objects.equals(localCommitId, remoteCommitId); } - /** - * Checks if there is a git installation and throws an exception if there is none - */ - private void verifyGitInstalled() { + private String getCommitId(Path repository, String... args) { + ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT_CAPTURE, args); + return result.isSuccessful() && !result.getOut().isEmpty() ? result.getOut().getFirst() : null; + } - this.context.findBashRequired(); - Path git = Path.of("git"); - Path binaryGitPath = this.context.getPath().findBinary(git); - if (git == binaryGitPath) { - String message = "Could not find a git installation. We highly recommend installing git since most of our actions require git to work properly!"; - throw new CliException(message); + private void switchBranch(Path repository, String branch) { + if (branch != null) { + runGitCommand(repository, "switch", branch); } - this.context.trace("Git is installed"); } - private void runGitCommand(Path directory, String... args) { - - ProcessResult result = runGitCommand(directory, ProcessMode.DEFAULT, args); + private void handleErrors(Path repository, ProcessResult result, String errorMessage) { if (!result.isSuccessful()) { - String command = result.getCommand(); - this.context.requireOnline(command); - result.failOnError(); + this.context.warning(errorMessage); + if (this.context.isOnline()) { + this.context.error("See above error for details. If you have local changes, please stash or revert and retry."); + } else { + this.context.error("Ensure Internet connectivity and retry or activate offline mode."); + } + this.context.askToContinue("Do you want to continue anyway?"); } } - private void runGitCommand(Path directory, List args) { - - runGitCommand(directory, args.toArray(String[]::new)); + private void verifyGitInstalled() { + Path git = Path.of("git"); + if (this.context.getPath().findBinary(git) == git) { + throw new CliException("Git installation not found. Please install git."); + } } - private String runGitCommandAndGetSingleOutput(String warningOnError, Path directory, String... args) { - - ProcessResult result = runGitCommand(directory, ProcessMode.DEFAULT_CAPTURE, args); - if (result.isSuccessful()) { - List out = result.getOut(); - int size = out.size(); - if (size == 1) { - return out.get(0); - } else if (size == 0) { - warningOnError += " - No output received from " + result.getCommand(); - } else { - warningOnError += " - Expected single line of output but received " + size + " lines from " + result.getCommand(); - } + private void requireOnline(String action) { + if (this.context.isOfflineMode()) { + this.context.requireOnline(action); } - this.context.warning(warningOnError); - return null; } - private ProcessResult runGitCommand(Path directory, ProcessMode mode, String... args) { + private String readFileContent(Path path) { + try { + return Files.readString(path); + } catch (IOException e) { + this.context.warning("Failed to read file: {}", path); + return null; + } + } - return runGitCommand(directory, mode, ProcessErrorHandling.LOG_WARNING, args); + private void writeFileContent(Path path, String content) { + try { + Files.writeString(path, content); + } catch (IOException e) { + throw new IllegalStateException("Failed to write file: " + path, e); + } } - private ProcessResult runGitCommand(Path directory, ProcessMode mode, ProcessErrorHandling errorHandling, String... args) { + private boolean isGitRepository(Path repository) { + return Files.isDirectory(repository.resolve(GIT_FOLDER)); + } - ProcessContext processContext = this.context.newProcess().executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0").errorHandling(errorHandling) - .directory(directory); - processContext.addArgs(args); - return processContext.run(mode); + private ProcessResult runGitCommand(Path directory, ProcessMode mode, String... args) { + return this.context.newProcess() + .executable("git") + .withEnvVar("GIT_TERMINAL_PROMPT", "0") + .directory(directory) + .addArgs(args) + .run(mode); } - @Override - public void saveCurrentCommitId(Path repository, Path trackedCommitIdPath) { + private ProcessResult runGitCommand(Path directory, List args) { + return runGitCommand(directory, ProcessMode.DEFAULT, args.toArray(String[]::new)); + } - if ((repository == null) || (trackedCommitIdPath == null)) { - this.context.warning("Invalid usage of saveCurrentCommitId with null value"); - return; - } - this.context.trace("Saving commit Id of {} into {}", repository, trackedCommitIdPath); - String currentCommitId = 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); - } - } + private ProcessResult runGitCommand(Path directory, String... args) { + return runGitCommand(directory, ProcessMode.DEFAULT, args); } }