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

devonfw / IDEasy / 12180969410

05 Dec 2024 01:42PM UTC coverage: 67.265%. Remained the same
12180969410

push

github

web-flow
#840: fixed fetch determine remotes (#841)

2509 of 4080 branches covered (61.5%)

Branch coverage included in aggregate %.

6565 of 9410 relevant lines covered (69.77%)

3.08 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.context.IdeContext;
10
import com.devonfw.tools.ide.process.ProcessContext;
11
import com.devonfw.tools.ide.process.ProcessErrorHandling;
12
import com.devonfw.tools.ide.process.ProcessMode;
13
import com.devonfw.tools.ide.process.ProcessResult;
14
import com.devonfw.tools.ide.variable.IdeVariables;
15

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

21
  private final IdeContext context;
22

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

229
  IdeContext getContext() {
230

231
    return this.context;
3✔
232
  }
233

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

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

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

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

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

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

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

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

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

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

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

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

296

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