-
Notifications
You must be signed in to change notification settings - Fork 48
#1205 : Fix Git implementation #1241
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -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<String> 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<String> 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<String> 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<String> 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()) { | ||
|
Contributor
Author
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. Same es above. If we check if its offlineMode, then we don't need to check if its required to be online.
Contributor
Author
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. Need to be analyzed in detail, because isOfflineMode and requireOnline could be two different things to consider. |
||
| 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<String> 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); | ||
| } | ||
| } | ||
|
|
||
|
|
||
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.
This if clause is relevant to decide if the result was an error or not. If it wasn't an error, we don't need to "handle Errors". We should consider this if-check in the "parent" method or wherever it is called.