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

devonfw / IDEasy / 12356873353

16 Dec 2024 04:22PM UTC coverage: 67.215% (-0.2%) from 67.459%
12356873353

Pull #850

github

web-flow
Merge 532436561 into 52afdd7c7
Pull Request #850: #757: Settings in code repository

2574 of 4176 branches covered (61.64%)

Branch coverage included in aggregate %.

6668 of 9574 relevant lines covered (69.65%)

3.06 hits per line

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

56.67
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
    ProcessResult result = runGitCommand(repository, ProcessMode.DEFAULT, "diff-index", "--quiet", "HEAD");
19✔
215
    if (!result.isSuccessful()) {
3!
216
      // reset to origin/master
217
      this.context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", repository, remoteName, branchName);
18✔
218
      result = runGitCommand(repository, ProcessMode.DEFAULT, "reset", "--hard", remoteName + "/" + branchName);
21✔
219
      if (!result.isSuccessful()) {
3!
220
        this.context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, repository);
×
221
        handleErrors(repository, result);
×
222
      }
223
    }
224
  }
1✔
225

226
  @Override
227
  public void cleanup(Path repository) {
228

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

239
  @Override
240
  public String retrieveGitUrl(Path repository) {
241

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

246
  IdeContext getContext() {
247

248
    return this.context;
3✔
249
  }
250

251
  /**
252
   * Checks if there is a git installation and throws an exception if there is none
253
   */
254
  private void verifyGitInstalled() {
255

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

266
  private void runGitCommand(Path directory, String... args) {
267

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

276
  private void runGitCommand(Path directory, List<String> args) {
277

278
    runGitCommand(directory, args.toArray(String[]::new));
10✔
279
  }
1✔
280

281
  private String runGitCommandAndGetSingleOutput(String warningOnError, Path directory, String... args) {
282

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

299
  private ProcessResult runGitCommand(Path directory, ProcessMode mode, String... args) {
300

301
    return runGitCommand(directory, mode, ProcessErrorHandling.LOG_WARNING, args);
7✔
302
  }
303

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

306
    ProcessContext processContext = this.context.newProcess().executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0").errorHandling(errorHandling)
11✔
307
        .directory(directory);
2✔
308
    processContext.addArgs(args);
4✔
309
    return processContext.run(mode);
4✔
310
  }
311

312
  @Override
313
  public void saveCurrentCommitId(Path repository, Path trackedCommitIdPath) {
314

315
    this.context.trace("Saving commit Id of {} into {}", repository, trackedCommitIdPath);
×
316
    if (Objects.isNull(repository)) {
×
317
      return;
×
318
    }
319
    String currentCommitId = runGitCommandAndGetSingleOutput("Failed to get current commit id.", repository, "rev-parse", "HEAD");
×
320
    if (currentCommitId != null) {
×
321
      try {
322
        Files.writeString(trackedCommitIdPath, currentCommitId);
×
323
      } catch (IOException e) {
×
324
        throw new IllegalStateException("Failed to save commit ID", e);
×
325
      }
×
326
    }
327
  }
×
328
}
329

330

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