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

devonfw / IDEasy / 11783359749

11 Nov 2024 05:30PM UTC coverage: 67.195% (+0.06%) from 67.136%
11783359749

push

github

web-flow
#312: Added ability to prefer git (ssh) protocol instead of https for cloning repos (#724)

2444 of 3981 branches covered (61.39%)

Branch coverage included in aggregate %.

6376 of 9145 relevant lines covered (69.72%)

3.07 hits per line

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

51.72
cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java
1
package com.devonfw.tools.ide.context;
2

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

8
import com.devonfw.tools.ide.cli.CliOfflineException;
9
import com.devonfw.tools.ide.process.ProcessContext;
10
import com.devonfw.tools.ide.process.ProcessErrorHandling;
11
import com.devonfw.tools.ide.process.ProcessMode;
12
import com.devonfw.tools.ide.process.ProcessResult;
13
import com.devonfw.tools.ide.variable.IdeVariables;
14

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

20
  private final IdeContext context;
21

22
  private final ProcessContext processContext;
23

24
  private static final ProcessMode PROCESS_MODE = ProcessMode.DEFAULT;
2✔
25
  private static final ProcessMode PROCESS_MODE_FOR_FETCH = ProcessMode.DEFAULT_CAPTURE;
3✔
26

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

32
    this.context = context;
3✔
33
    this.processContext = this.context.newProcess().executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0").errorHandling(ProcessErrorHandling.LOG_WARNING);
12✔
34
  }
1✔
35

36
  @Override
37
  public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) {
38

39
    GitOperation.PULL_OR_CLONE.executeIfNeeded(this.context, repoUrl, targetRepository, null, branch);
9✔
40
  }
1✔
41

42
  @Override
43
  public boolean fetchIfNeeded(Path targetRepository) {
44

45
    return fetchIfNeeded(targetRepository, null, null);
×
46
  }
47

48
  @Override
49
  public boolean fetchIfNeeded(Path targetRepository, String remote, String branch) {
50

51
    return GitOperation.FETCH.executeIfNeeded(this.context, null, targetRepository, remote, branch);
9✔
52
  }
53

54
  @Override
55
  public boolean isRepositoryUpdateAvailable(Path repository) {
56

57
    ProcessResult result = this.processContext.directory(repository).addArg("rev-parse").addArg("HEAD").run(PROCESS_MODE_FOR_FETCH);
11✔
58
    if (!result.isSuccessful()) {
3!
59
      this.context.warning("Failed to get the local commit hash.");
×
60
      return false;
×
61
    }
62
    String localCommitHash = result.getOut().stream().findFirst().orElse("").trim();
9✔
63
    // get remote commit code
64
    result = this.processContext.addArg("rev-parse").addArg("@{u}").run(PROCESS_MODE_FOR_FETCH);
9✔
65
    if (!result.isSuccessful()) {
3!
66
      this.context.warning("Failed to get the remote commit hash.");
×
67
      return false;
×
68
    }
69
    String remote_commit_code = result.getOut().stream().findFirst().orElse("").trim();
9✔
70
    return !localCommitHash.equals(remote_commit_code);
6!
71
  }
72

73
  @Override
74
  public void pullOrCloneAndResetIfNeeded(String repoUrl, Path repository, String branch, String remoteName) {
75

76
    pullOrCloneIfNeeded(repoUrl, branch, repository);
5✔
77

78
    reset(repository, "master", remoteName);
5✔
79

80
    cleanup(repository);
3✔
81
  }
1✔
82

83
  @Override
84
  public void pullOrClone(String gitRepoUrl, Path repository) {
85

86
    pullOrClone(gitRepoUrl, repository, null);
5✔
87
  }
1✔
88

89
  @Override
90
  public void pullOrClone(String gitRepoUrl, Path repository, String branch) {
91

92
    Objects.requireNonNull(repository);
3✔
93
    Objects.requireNonNull(gitRepoUrl);
3✔
94
    if (!gitRepoUrl.startsWith("http")) {
4!
95
      throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!");
×
96
    }
97
    if (Files.isDirectory(repository.resolve(GIT_FOLDER))) {
7✔
98
      // checks for remotes
99
      String remote = determineRemote(repository);
4✔
100
      if (remote == null) {
2!
101
        String message = repository + " is a local git repository with no remote - if you did this for testing, you may continue...\n"
×
102
            + "Do you want to ignore the problem and continue anyhow?";
103
        this.context.askToContinue(message);
×
104
      } else {
×
105
        pull(repository);
3✔
106
      }
107
    } else {
1✔
108
      clone(new GitUrl(gitRepoUrl, branch), repository);
8✔
109
    }
110
  }
1✔
111

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

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

138
  @Override
139
  public void clone(GitUrl gitRepoUrl, Path targetRepository) {
140

141
    GitUrlSyntax gitUrlSyntax = IdeVariables.PREFERRED_GIT_PROTOCOL.get(getContext());
6✔
142
    gitRepoUrl = gitUrlSyntax.format(gitRepoUrl);
4✔
143
    this.processContext.directory(targetRepository);
5✔
144
    ProcessResult result;
145
    if (!this.context.isOffline()) {
4✔
146
      this.context.getFileAccess().mkdirs(targetRepository);
5✔
147
      this.context.requireOnline("git clone of " + gitRepoUrl.url());
6✔
148
      this.processContext.addArg("clone");
5✔
149
      if (this.context.isQuietMode()) {
4!
150
        this.processContext.addArg("-q");
×
151
      }
152
      this.processContext.addArgs("--recursive", gitRepoUrl.url(), "--config", "core.autocrlf=false", ".");
27✔
153
      result = this.processContext.run(PROCESS_MODE);
5✔
154
      if (!result.isSuccessful()) {
3!
155
        this.context.warning("Git failed to clone {} into {}.", gitRepoUrl.url(), targetRepository);
×
156
      }
157
      String branch = gitRepoUrl.branch();
3✔
158
      if (branch != null) {
2!
159
        this.processContext.addArgs("checkout", branch);
×
160
        result = this.processContext.run(PROCESS_MODE);
×
161
        if (!result.isSuccessful()) {
×
162
          this.context.warning("Git failed to checkout to branch {}", branch);
×
163
        }
164
      }
165
    } else {
1✔
166
      throw CliOfflineException.ofClone(gitRepoUrl.parseUrl(), targetRepository);
5✔
167
    }
168
  }
1✔
169

170
  @Override
171
  public void pull(Path repository) {
172

173
    if (this.context.isOffline()) {
4!
174
      this.context.info("Skipping git pull on {} because offline", repository);
×
175
      return;
×
176
    }
177
    ProcessResult result = this.processContext.directory(repository).addArg("--no-pager").addArg("pull").addArg("--quiet").run(PROCESS_MODE);
13✔
178
    if (!result.isSuccessful()) {
3!
179
      String branchName = determineCurrentBranch(repository);
×
180
      this.context.warning("Git pull on branch {} failed for repository {}.", branchName, repository);
×
181
      handleErrors(repository, result);
×
182
    }
183
  }
1✔
184

185
  @Override
186
  public void fetch(Path targetRepository, String remote, String branch) {
187

188
    if (branch == null) {
2!
189
      branch = determineCurrentBranch(targetRepository);
×
190
    }
191
    if (remote == null) {
2!
192
      remote = determineRemote(targetRepository);
×
193
    }
194
    ProcessResult result = this.processContext.directory(targetRepository).addArg("fetch").addArg(remote).addArg(branch).run(PROCESS_MODE_FOR_FETCH);
13✔
195

196
    if (!result.isSuccessful()) {
3!
197
      this.context.warning("Git fetch for '{}/{} failed.'.", remote, branch);
×
198
    }
199
  }
1✔
200

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

204
    ProcessResult remoteResult = this.processContext.directory(repository).addArg("branch").addArg("--show-current").run(ProcessMode.DEFAULT_CAPTURE);
×
205
    if (remoteResult.isSuccessful()) {
×
206
      List<String> remotes = remoteResult.getOut();
×
207
      if (!remotes.isEmpty()) {
×
208
        assert (remotes.size() == 1);
×
209
        return remotes.get(0);
×
210
      }
211
    } else {
×
212
      this.context.warning("Failed to determine current branch of git repository {}", repository);
×
213
    }
214
    return null;
×
215
  }
216

217
  @Override
218
  public String determineRemote(Path repository) {
219

220
    ProcessResult remoteResult = this.processContext.directory(repository).addArg("remote").run(ProcessMode.DEFAULT_CAPTURE);
9✔
221
    if (remoteResult.isSuccessful()) {
3!
222
      List<String> remotes = remoteResult.getOut();
3✔
223
      if (!remotes.isEmpty()) {
3!
224
        assert (remotes.size() == 1);
5!
225
        return remotes.get(0);
5✔
226
      }
227
    } else {
×
228
      this.context.warning("Failed to determine current origin of git repository {}", repository);
×
229
    }
230
    return null;
×
231
  }
232

233
  @Override
234
  public void reset(Path targetRepository, String branchName, String remoteName) {
235

236
    if ((remoteName == null) || remoteName.isEmpty()) {
5!
237
      remoteName = DEFAULT_REMOTE;
×
238
    }
239
    this.processContext.directory(targetRepository);
5✔
240
    ProcessResult result;
241
    // check for changed files
242
    result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(PROCESS_MODE);
11✔
243

244
    if (!result.isSuccessful()) {
3!
245
      // reset to origin/master
246
      this.context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, remoteName, branchName);
18✔
247
      result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName).run(PROCESS_MODE);
13✔
248

249
      if (!result.isSuccessful()) {
3!
250
        this.context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository);
×
251
        handleErrors(targetRepository, result);
×
252
      }
253
    }
254
  }
1✔
255

256
  @Override
257
  public void cleanup(Path targetRepository) {
258

259
    this.processContext.directory(targetRepository);
5✔
260
    ProcessResult result;
261
    // check for untracked files
262
    result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard").run(ProcessMode.DEFAULT_CAPTURE);
13✔
263

264
    if (!result.getOut().isEmpty()) {
4!
265
      // delete untracked files
266
      this.context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository);
10✔
267
      result = this.processContext.addArg("clean").addArg("-df").run(PROCESS_MODE);
9✔
268

269
      if (!result.isSuccessful()) {
3!
270
        this.context.warning("Git failed to clean the repository {}.", targetRepository);
×
271
      }
272
    }
273
  }
1✔
274

275
  @Override
276
  public String retrieveGitUrl(Path repository) {
277

278
    this.processContext.directory(repository);
×
279
    ProcessResult result;
280
    result = this.processContext.addArgs("-C", repository, "remote", "-v").run(ProcessMode.DEFAULT_CAPTURE);
×
281
    for (String line : result.getOut()) {
×
282
      if (line.contains("(fetch)")) {
×
283
        return line.split("\\s+")[1]; // Extract the URL from the line
×
284
      }
285
    }
×
286

287
    this.context.error("Failed to retrieve git URL for repository: {}", repository);
×
288
    return null;
×
289
  }
290

291
  IdeContext getContext() {
292

293
    return this.context;
3✔
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