• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 12181159451

05 Dec 2024 01:53PM UTC coverage: 66.917%. Remained the same
12181159451

push

github

web-flow
#837: Changed type of exception (#838)

2495 of 4076 branches covered (61.21%)

Branch coverage included in aggregate %.

6516 of 9390 relevant lines covered (69.39%)

3.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.64
cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java
1
package com.devonfw.tools.ide.git;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.util.ArrayList;
6
import java.util.List;
7
import java.util.Objects;
8

9
import com.devonfw.tools.ide.cli.CliException;
10
import com.devonfw.tools.ide.context.IdeContext;
11
import com.devonfw.tools.ide.process.ProcessContext;
12
import com.devonfw.tools.ide.process.ProcessErrorHandling;
13
import com.devonfw.tools.ide.process.ProcessMode;
14
import com.devonfw.tools.ide.process.ProcessResult;
15
import com.devonfw.tools.ide.variable.IdeVariables;
16

17
/**
18
 * Implements the {@link GitContext}.
19
 */
20
public class GitContextImpl implements GitContext {
21

22
  private final IdeContext context;
23

24
  /**
25
   * @param context the {@link IdeContext context}.
26
   */
27
  public GitContextImpl(IdeContext context) {
2✔
28

29
    this.context = context;
3✔
30
  }
1✔
31

32
  @Override
33
  public void pullOrCloneIfNeeded(GitUrl gitUrl, Path repository) {
34

35
    GitOperation.PULL_OR_CLONE.executeIfNeeded(this.context, gitUrl, repository, null);
8✔
36
  }
1✔
37

38
  @Override
39
  public boolean fetchIfNeeded(Path repository) {
40

41
    return fetchIfNeeded(repository, null, null);
×
42
  }
43

44
  @Override
45
  public boolean fetchIfNeeded(Path repository, String remote, String branch) {
46

47
    return GitOperation.FETCH.executeIfNeeded(this.context, new GitUrl("https://dummy.url/repo.git", branch), repository, remote);
12✔
48
  }
49

50
  @Override
51
  public boolean isRepositoryUpdateAvailable(Path repository) {
52

53
    verifyGitInstalled();
2✔
54
    String localCommitId = runGitCommandAndGetSingleOutput("Failed to get the local commit id.", repository, "rev-parse", "HEAD");
15✔
55
    String remoteCommitId = runGitCommandAndGetSingleOutput("Failed to get the remote commit id.", repository, "rev-parse", "@{u}");
15✔
56
    if ((localCommitId == null) || (remoteCommitId == null)) {
2!
57
      return false;
2✔
58
    }
59
    return !localCommitId.equals(remoteCommitId);
×
60
  }
61

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

65
    pullOrCloneIfNeeded(gitUrl, repository);
4✔
66
    reset(repository, gitUrl.branch(), remoteName);
6✔
67
    cleanup(repository);
3✔
68
  }
1✔
69

70
  @Override
71
  public void pullOrClone(GitUrl gitUrl, Path repository) {
72

73
    Objects.requireNonNull(repository);
3✔
74
    Objects.requireNonNull(gitUrl);
3✔
75
    if (Files.isDirectory(repository.resolve(GIT_FOLDER))) {
7✔
76
      // checks for remotes
77
      String remote = determineRemote(repository);
4✔
78
      if (remote == null) {
2!
79
        String message = repository + " is a local git repository with no remote - if you did this for testing, you may continue...\n"
×
80
            + "Do you want to ignore the problem and continue anyhow?";
81
        this.context.askToContinue(message);
×
82
      } else {
×
83
        pull(repository);
3✔
84
      }
85
    } else {
1✔
86
      clone(gitUrl, repository);
4✔
87
    }
88
  }
1✔
89

90
  /**
91
   * Handles errors which occurred during git pull.
92
   *
93
   * @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
94
   *     git will by default create a sub-folder by default on clone but the * final folder that will contain the ".git" subfolder.
95
   * @param result the {@link ProcessResult} to evaluate.
96
   */
97
  private void handleErrors(Path targetRepository, ProcessResult result) {
98

99
    if (!result.isSuccessful()) {
×
100
      String message = "Failed to update git repository at " + targetRepository;
×
101
      if (this.context.isOffline()) {
×
102
        this.context.warning(message);
×
103
        this.context.interaction("Continuing as we are in offline mode - results may be outdated!");
×
104
      } else {
105
        this.context.error(message);
×
106
        if (this.context.isOnline()) {
×
107
          this.context.error("See above error for details. If you have local changes, please stash or revert and retry.");
×
108
        } else {
109
          this.context.error("It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline).");
×
110
        }
111
        this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?");
×
112
      }
113
    }
114
  }
×
115

116
  @Override
117
  public void clone(GitUrl gitUrl, Path repository) {
118

119
    verifyGitInstalled();
2✔
120
    GitUrlSyntax gitUrlSyntax = IdeVariables.PREFERRED_GIT_PROTOCOL.get(getContext());
6✔
121
    gitUrl = gitUrlSyntax.format(gitUrl);
4✔
122
    if (this.context.isOfflineMode()) {
4✔
123
      this.context.requireOnline("git clone of " + gitUrl);
×
124
    }
125
    this.context.getFileAccess().mkdirs(repository);
5✔
126
    List<String> args = new ArrayList<>(7);
5✔
127
    args.add("clone");
4✔
128
    if (this.context.isQuietMode()) {
4!
129
      args.add("-q");
×
130
    }
131
    args.add("--recursive");
4✔
132
    args.add(gitUrl.url());
5✔
133
    args.add("--config");
4✔
134
    args.add("core.autocrlf=false");
4✔
135
    args.add(".");
4✔
136
    runGitCommand(repository, args);
4✔
137
    String branch = gitUrl.branch();
3✔
138
    if (branch != null) {
2!
139
      runGitCommand(repository, "switch", branch);
×
140
    }
141
  }
1✔
142

143
  @Override
144
  public void pull(Path repository) {
145

146
    verifyGitInstalled();
2✔
147
    if (this.context.isOffline()) {
4!
148
      this.context.info("Skipping git pull on {} because offline", repository);
×
149
      return;
×
150
    }
151
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT, "--no-pager", "pull", "--quiet");
19✔
152
    if (!result.isSuccessful()) {
3!
153
      String branchName = determineCurrentBranch(repository);
×
154
      this.context.warning("Git pull on branch {} failed for repository {}.", branchName, repository);
×
155
      handleErrors(repository, result);
×
156
    }
157
  }
1✔
158

159
  @Override
160
  public void fetch(Path repository, String remote, String branch) {
161

162
    verifyGitInstalled();
2✔
163
    if (branch == null) {
2!
164
      branch = determineCurrentBranch(repository);
×
165
    }
166
    if (remote == null) {
2!
167
      remote = determineRemote(repository);
×
168
    }
169

170
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT_CAPTURE, "fetch", Objects.requireNonNullElse(remote, "origin"), branch);
22✔
171

172
    if (!result.isSuccessful()) {
3!
173
      this.context.warning("Git fetch for '{}/{} failed.'.", remote, branch);
×
174
    }
175
  }
1✔
176

177
  @Override
178
  public String determineCurrentBranch(Path repository) {
179

180
    verifyGitInstalled();
×
181
    return runGitCommandAndGetSingleOutput("Failed to determine current branch of git repository", repository, "branch", "--show-current");
×
182
  }
183

184
  @Override
185
  public String determineRemote(Path repository) {
186

187
    verifyGitInstalled();
2✔
188
    return runGitCommandAndGetSingleOutput("Failed to determine current origin of git repository.", repository, "remote");
11✔
189
  }
190

191
  @Override
192
  public void reset(Path repository, String branchName, String remoteName) {
193

194
    verifyGitInstalled();
2✔
195
    if ((remoteName == null) || remoteName.isEmpty()) {
5!
196
      remoteName = DEFAULT_REMOTE;
×
197
    }
198
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT, "diff-index", "--quiet", "HEAD");
19✔
199
    if (!result.isSuccessful()) {
3!
200
      // reset to origin/master
201
      this.context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", repository, remoteName, branchName);
18✔
202
      result = runGitCommand(repository, ProcessMode.DEFAULT, "reset", "--hard", remoteName + "/" + branchName);
21✔
203
      if (!result.isSuccessful()) {
3!
204
        this.context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, repository);
×
205
        handleErrors(repository, result);
×
206
      }
207
    }
208
  }
1✔
209

210
  @Override
211
  public void cleanup(Path repository) {
212

213
    verifyGitInstalled();
2✔
214
    // check for untracked files
215
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT_CAPTURE, "ls-files", "--other", "--directory", "--exclude-standard");
23✔
216
    if (!result.getOut().isEmpty()) {
4!
217
      // delete untracked files
218
      this.context.warning("Git detected untracked files in {} and is attempting a cleanup.", repository);
10✔
219
      runGitCommand(repository, "clean", "-df");
13✔
220
    }
221
  }
1✔
222

223
  @Override
224
  public String retrieveGitUrl(Path repository) {
225

226
    verifyGitInstalled();
×
227
    return runGitCommandAndGetSingleOutput("Failed to retrieve git URL for repository", repository, "config", "--get", "remote.origin.url");
×
228
  }
229

230
  IdeContext getContext() {
231

232
    return this.context;
3✔
233
  }
234

235
  /**
236
   * Checks if there is a git installation and throws an exception if there is none
237
   */
238
  private void verifyGitInstalled() {
239

240
    this.context.findBashRequired();
4✔
241
    Path git = Path.of("git");
5✔
242
    Path binaryGitPath = this.context.getPath().findBinary(git);
6✔
243
    if (git == binaryGitPath) {
3!
244
      String message = "Could not find a git installation. We highly recommend installing git since most of our actions require git to work properly!";
×
245
      throw new CliException(message);
×
246
    }
247
    this.context.trace("Git is installed");
4✔
248
  }
1✔
249

250
  private void runGitCommand(Path directory, String... args) {
251

252
    ProcessResult result = runGitCommand(directory, ProcessMode.DEFAULT, args);
6✔
253
    if (!result.isSuccessful()) {
3!
254
      String command = result.getCommand();
×
255
      this.context.requireOnline(command);
×
256
      result.failOnError();
×
257
    }
258
  }
1✔
259

260
  private void runGitCommand(Path directory, List<String> args) {
261

262
    runGitCommand(directory, args.toArray(String[]::new));
10✔
263
  }
1✔
264

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

267
    ProcessResult result = runGitCommand(directory, ProcessMode.DEFAULT_CAPTURE, args);
6✔
268
    if (result.isSuccessful()) {
3!
269
      List<String> out = result.getOut();
3✔
270
      int size = out.size();
3✔
271
      if (size == 1) {
3✔
272
        return out.get(0);
5✔
273
      } else if (size == 0) {
2!
274
        warningOnError += " - No output received from " + result.getCommand();
6✔
275
      } else {
276
        warningOnError += " - Expected single line of output but received " + size + " lines from " + result.getCommand();
×
277
      }
278
    }
279
    this.context.warning(warningOnError);
4✔
280
    return null;
2✔
281
  }
282

283
  private ProcessResult runGitCommand(Path directory, ProcessMode mode, String... args) {
284

285
    return runGitCommand(directory, mode, ProcessErrorHandling.LOG_WARNING, args);
7✔
286
  }
287

288
  private ProcessResult runGitCommand(Path directory, ProcessMode mode, ProcessErrorHandling errorHandling, String... args) {
289

290
    ProcessContext processContext = this.context.newProcess().executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0").errorHandling(errorHandling)
11✔
291
        .directory(directory);
2✔
292
    processContext.addArgs(args);
4✔
293
    return processContext.run(mode);
4✔
294
  }
295
}
296

297

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc