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

devonfw / IDEasy / 15143174113

20 May 2025 04:44PM UTC coverage: 67.704% (-0.02%) from 67.719%
15143174113

Pull #1287

github

web-flow
Merge 2f45b5b63 into 7a33b065d
Pull Request #1287: #1108: Fix git authentication in terminal

3121 of 5018 branches covered (62.2%)

Branch coverage included in aggregate %.

8017 of 11433 relevant lines covered (70.12%)

3.07 hits per line

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

55.8
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.List;
8
import java.util.Objects;
9

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

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

23
  private final IdeContext context;
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

249
  IdeContext getContext() {
250

251
    return this.context;
3✔
252
  }
253

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

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

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

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

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

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

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

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

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

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

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

309
    ProcessContext processContext;
310

311
    if (this.context.isBatchMode()) {
4!
312
      processContext = this.context.newProcess().executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0").withEnvVar("GCM_INTERACTIVE", "never")
×
313
          .withEnvVar("GIT_ASKPASS", "echo").withEnvVar("SSH_ASKPASS", "echo").errorHandling(errorHandling).directory(directory);
×
314
    } else {
315
      processContext = this.context.newProcess().executable("git").errorHandling(errorHandling)
8✔
316
          .directory(directory);
2✔
317
    }
318

319
    processContext.addArgs(args);
4✔
320
    return processContext.run(mode);
4✔
321
  }
322

323
  @Override
324
  public void saveCurrentCommitId(Path repository, Path trackedCommitIdPath) {
325

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

342

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

© 2025 Coveralls, Inc