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

devonfw / IDEasy / 14991362095

13 May 2025 08:01AM UTC coverage: 67.673% (-0.006%) from 67.679%
14991362095

Pull #1287

github

web-flow
Merge ea90256dc into b2f3ea46b
Pull Request #1287: #1108: Fix git authentification in terminal

3102 of 4992 branches covered (62.14%)

Branch coverage included in aggregate %.

7976 of 11378 relevant lines covered (70.1%)

3.07 hits per line

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

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

3
import java.io.IOException;
4
import java.nio.file.Files;
5
import java.nio.file.Path;
6
import java.util.ArrayList;
7
import java.util.Arrays;
8
import java.util.List;
9
import java.util.Objects;
10

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

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

24
  private final IdeContext context;
25

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

31
    this.context = context;
3✔
32
  }
1✔
33

34
  @Override
35
  public void pullOrCloneIfNeeded(GitUrl gitUrl, Path repository) {
36

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

40
  @Override
41
  public boolean fetchIfNeeded(Path repository) {
42

43
    return fetchIfNeeded(repository, null, null);
×
44
  }
45

46
  @Override
47
  public boolean fetchIfNeeded(Path repository, String remote, String branch) {
48

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

52
  @Override
53
  public boolean isRepositoryUpdateAvailable(Path repository) {
54

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

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

67
    verifyGitInstalled();
×
68
    String trackedCommitId;
69
    try {
70
      trackedCommitId = Files.readString(trackedCommitIdPath);
×
71
    } catch (IOException e) {
×
72
      return false;
×
73
    }
×
74

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

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

82
    pullOrCloneIfNeeded(gitUrl, repository);
4✔
83
    reset(repository, gitUrl.branch(), remoteName);
6✔
84
    cleanup(repository);
3✔
85
  }
1✔
86

87
  @Override
88
  public void pullOrClone(GitUrl gitUrl, Path repository) {
89

90
    Objects.requireNonNull(repository);
3✔
91
    Objects.requireNonNull(gitUrl);
3✔
92
    if (Files.isDirectory(repository.resolve(GIT_FOLDER))) {
7✔
93
      // checks for remotes
94
      String remote = determineRemote(repository);
4✔
95
      if (remote == null) {
2!
96
        String message = repository + " is a local git repository with no remote - if you did this for testing, you may continue...\n"
×
97
            + "Do you want to ignore the problem and continue anyhow?";
98
        this.context.askToContinue(message);
×
99
      } else {
×
100
        pull(repository);
3✔
101
      }
102
    } else {
1✔
103
      clone(gitUrl, repository);
4✔
104
    }
105
  }
1✔
106

107
  /**
108
   * Handles errors which occurred during git pull.
109
   *
110
   * @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
111
   *     git will by default create a sub-folder by default on clone but the * final folder that will contain the ".git" subfolder.
112
   * @param result the {@link ProcessResult} to evaluate.
113
   */
114
  private void handleErrors(Path targetRepository, ProcessResult result) {
115

116
    if (!result.isSuccessful()) {
×
117
      String message = "Failed to update git repository at " + targetRepository;
×
118
      if (this.context.isOffline()) {
×
119
        this.context.warning(message);
×
120
        this.context.interaction("Continuing as we are in offline mode - results may be outdated!");
×
121
      } else {
122
        this.context.error(message);
×
123
        if (this.context.isOnline()) {
×
124
          this.context.error("See above error for details. If you have local changes, please stash or revert and retry.");
×
125
        } else {
126
          this.context.error("It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline).");
×
127
        }
128
        this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?");
×
129
      }
130
    }
131
  }
×
132

133
  @Override
134
  public void clone(GitUrl gitUrl, Path repository) {
135

136
    verifyGitInstalled();
2✔
137
    GitUrlSyntax gitUrlSyntax = IdeVariables.PREFERRED_GIT_PROTOCOL.get(getContext());
6✔
138
    gitUrl = gitUrlSyntax.format(gitUrl);
4✔
139
    if (this.context.isOfflineMode()) {
4✔
140
      this.context.requireOnline("git clone of " + gitUrl);
×
141
    }
142
    this.context.getFileAccess().mkdirs(repository);
5✔
143
    List<String> args = new ArrayList<>(7);
5✔
144
    args.add("clone");
4✔
145
    if (this.context.isQuietMode()) {
4!
146
      args.add("-q");
×
147
    }
148
    args.add("--recursive");
4✔
149
    args.add(gitUrl.url());
5✔
150
    args.add("--config");
4✔
151
    args.add("core.autocrlf=false");
4✔
152
    args.add(".");
4✔
153
    runGitCommand(repository, args);
4✔
154
    String branch = gitUrl.branch();
3✔
155
    if (branch != null) {
2!
156
      runGitCommand(repository, "switch", branch);
×
157
    }
158
  }
1✔
159

160
  @Override
161
  public void pull(Path repository) {
162

163
    verifyGitInstalled();
2✔
164
    if (this.context.isOffline()) {
4!
165
      this.context.info("Skipping git pull on {} because offline", repository);
×
166
      return;
×
167
    }
168
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT, "--no-pager", "pull", "--quiet");
19✔
169
    if (!result.isSuccessful()) {
3!
170
      String branchName = determineCurrentBranch(repository);
×
171
      this.context.warning("Git pull on branch {} failed for repository {}.", branchName, repository);
×
172
      handleErrors(repository, result);
×
173
    }
174
  }
1✔
175

176
  @Override
177
  public void fetch(Path repository, String remote, String branch) {
178

179
    verifyGitInstalled();
2✔
180
    if (branch == null) {
2!
181
      branch = determineCurrentBranch(repository);
×
182
    }
183
    if (remote == null) {
2!
184
      remote = determineRemote(repository);
×
185
    }
186

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

189
    if (!result.isSuccessful()) {
3!
190
      this.context.warning("Git fetch for '{}/{} failed.'.", remote, branch);
×
191
    }
192
  }
1✔
193

194
  @Override
195
  public String determineCurrentBranch(Path repository) {
196

197
    verifyGitInstalled();
×
198
    return runGitCommandAndGetSingleOutput("Failed to determine current branch of git repository", repository, "branch", "--show-current");
×
199
  }
200

201
  @Override
202
  public String determineRemote(Path repository) {
203

204
    verifyGitInstalled();
2✔
205
    return runGitCommandAndGetSingleOutput("Failed to determine current origin of git repository.", repository, "remote");
11✔
206
  }
207

208
  @Override
209
  public void reset(Path repository, String branchName, String remoteName) {
210

211
    verifyGitInstalled();
2✔
212
    if ((remoteName == null) || remoteName.isEmpty()) {
5!
213
      remoteName = DEFAULT_REMOTE;
×
214
    }
215
    if ((branchName == null) || branchName.isEmpty()) {
5!
216
      branchName = GitUrl.BRANCH_MASTER;
×
217
    }
218
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT, "diff-index", "--quiet", "HEAD");
19✔
219
    if (!result.isSuccessful()) {
3!
220
      // reset to origin/master
221
      this.context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", repository, remoteName, branchName);
18✔
222
      result = runGitCommand(repository, ProcessMode.DEFAULT, "reset", "--hard", remoteName + "/" + branchName);
21✔
223
      if (!result.isSuccessful()) {
3!
224
        this.context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, repository);
×
225
        handleErrors(repository, result);
×
226
      }
227
    }
228
  }
1✔
229

230
  @Override
231
  public void cleanup(Path repository) {
232

233
    verifyGitInstalled();
2✔
234
    // check for untracked files
235
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT_CAPTURE, "ls-files", "--other", "--directory", "--exclude-standard");
23✔
236
    if (!result.getOut().isEmpty()) {
4✔
237
      // delete untracked files
238
      this.context.warning("Git detected untracked files in {} and is attempting a cleanup.", repository);
10✔
239
      runGitCommand(repository, "clean", "-df");
13✔
240
    }
241
  }
1✔
242

243
  @Override
244
  public String retrieveGitUrl(Path repository) {
245

246
    verifyGitInstalled();
×
247
    return runGitCommandAndGetSingleOutput("Failed to retrieve git URL for repository", repository, "config", "--get", "remote.origin.url");
×
248
  }
249

250
  IdeContext getContext() {
251

252
    return this.context;
3✔
253
  }
254

255
  /**
256
   * Checks if there is a git installation and throws an exception if there is none
257
   */
258
  private void verifyGitInstalled() {
259

260
    this.context.findBashRequired();
4✔
261
    Path git = Path.of("git");
5✔
262
    Path binaryGitPath = this.context.getPath().findBinary(git);
6✔
263
    if (git == binaryGitPath) {
3!
264
      String message = "Could not find a git installation. We highly recommend installing git since most of our actions require git to work properly!";
×
265
      throw new CliException(message);
×
266
    }
267
    this.context.trace("Git is installed");
4✔
268
  }
1✔
269

270
  private void runGitCommand(Path directory, String... args) {
271

272
    ProcessResult result = runGitCommand(directory, ProcessMode.DEFAULT, args);
6✔
273
    if (!result.isSuccessful()) {
3!
274
      String command = result.getCommand();
×
275
      this.context.requireOnline(command);
×
276
      result.failOnError();
×
277
    }
278
  }
1✔
279

280
  private void runGitCommand(Path directory, List<String> args) {
281

282
    runGitCommand(directory, args.toArray(String[]::new));
10✔
283
  }
1✔
284

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

287
    ProcessResult result = runGitCommand(directory, ProcessMode.DEFAULT_CAPTURE, args);
6✔
288
    if (result.isSuccessful()) {
3!
289
      List<String> out = result.getOut();
3✔
290
      int size = out.size();
3✔
291
      if (size == 1) {
3✔
292
        return out.get(0);
5✔
293
      } else if (size == 0) {
2!
294
        warningOnError += " - No output received from " + result.getCommand();
6✔
295
      } else {
296
        warningOnError += " - Expected single line of output but received " + size + " lines from " + result.getCommand();
×
297
      }
298
    }
299
    this.context.warning(warningOnError);
4✔
300
    return null;
2✔
301
  }
302

303
  private ProcessResult runGitCommand(Path directory, ProcessMode mode, String... args) {
304

305
    return runGitCommand(directory, mode, ProcessErrorHandling.LOG_WARNING, args);
7✔
306
  }
307

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

310
    boolean batchMode = Arrays.asList(args).contains("--batch");
5✔
311
    ProcessContext processContext;
312

313
    if (batchMode) {
2!
314
      processContext = this.context.newProcess().executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "1").errorHandling(errorHandling)
×
315
          .directory(directory);
×
316
    } else {
317
      processContext = this.context.newProcess().executable("git").errorHandling(errorHandling)
8✔
318
          .directory(directory);
2✔
319
    }
320

321
    processContext.addArgs(args);
4✔
322
    return processContext.run(mode);
4✔
323
  }
324

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

328
    if ((repository == null) || (trackedCommitIdPath == null)) {
×
329
      this.context.warning("Invalid usage of saveCurrentCommitId with null value");
×
330
      return;
×
331
    }
332
    this.context.trace("Saving commit Id of {} into {}", repository, trackedCommitIdPath);
×
333
    String currentCommitId = runGitCommandAndGetSingleOutput("Failed to get current commit id.", repository, "rev-parse", "HEAD");
×
334
    if (currentCommitId != null) {
×
335
      try {
336
        Files.writeString(trackedCommitIdPath, currentCommitId);
×
337
      } catch (IOException e) {
×
338
        throw new IllegalStateException("Failed to save commit ID", e);
×
339
      }
×
340
    }
341
  }
×
342
}
343

344

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