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

devonfw / IDEasy / 9907372175

12 Jul 2024 11:49AM UTC coverage: 61.142% (-0.02%) from 61.162%
9907372175

push

github

hohwille
fixed tests

1997 of 3595 branches covered (55.55%)

Branch coverage included in aggregate %.

5296 of 8333 relevant lines covered (63.55%)

2.8 hits per line

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

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

3
import java.io.IOException;
4
import java.net.URL;
5
import java.nio.file.Files;
6
import java.nio.file.Path;
7
import java.nio.file.attribute.FileTime;
8
import java.time.Duration;
9
import java.util.AbstractMap;
10
import java.util.HashMap;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.Objects;
14

15
import com.devonfw.tools.ide.cli.CliOfflineException;
16
import com.devonfw.tools.ide.process.ProcessContext;
17
import com.devonfw.tools.ide.process.ProcessErrorHandling;
18
import com.devonfw.tools.ide.process.ProcessMode;
19
import com.devonfw.tools.ide.process.ProcessResult;
20

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

26
  private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMinutes(30);
3✔
27

28
  private final IdeContext context;
29

30
  private final ProcessContext processContext;
31

32
  private static final ProcessMode PROCESS_MODE = ProcessMode.DEFAULT;
3✔
33

34
  /**
35
   * @param context the {@link IdeContext context}.
36
   */
37
  public GitContextImpl(IdeContext context) {
2✔
38

39
    this.context = context;
3✔
40
    this.processContext = this.context.newProcess().executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0");
10✔
41
  }
1✔
42

43
  @Override
44
  public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) {
45

46
    Path gitDirectory = targetRepository.resolve(".git");
4✔
47

48
    // Check if the .git directory exists
49
    if (!this.context.isForceMode() && Files.isDirectory(gitDirectory)) {
9!
50
      Path magicFilePath = gitDirectory.resolve("HEAD");
4✔
51
      long currentTime = System.currentTimeMillis();
2✔
52
      // Get the modification time of the magic file
53
      try {
54
        long fileModifiedTime = Files.getLastModifiedTime(magicFilePath).toMillis();
6✔
55
        // Check if the file modification time is older than the delta threshold
56
        if ((currentTime - fileModifiedTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis())) {
7!
57
          pullOrClone(repoUrl, targetRepository);
×
58
          try {
59
            Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime));
×
60
          } catch (IOException e) {
×
61
            this.context.warning().log(e, "Cound not update modification-time of {}", magicFilePath);
×
62
          }
×
63
          return;
×
64
        }
65
      } catch (IOException e) {
×
66
        this.context.error(e);
×
67
      }
1✔
68
    }
69
    // If the .git directory does not exist or in case of an error, perform git operation directly
70
    pullOrClone(repoUrl, branch, targetRepository);
5✔
71
  }
1✔
72

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

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

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

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

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

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

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

92
    Objects.requireNonNull(targetRepository);
3✔
93
    Objects.requireNonNull(gitRepoUrl);
3✔
94

95
    if (!gitRepoUrl.startsWith("http")) {
4!
96
      throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!");
×
97
    }
98

99
    if (Files.isDirectory(targetRepository.resolve(".git"))) {
7✔
100
      // checks for remotes
101
      this.processContext.directory(targetRepository);
5✔
102
      ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE);
7✔
103
      List<String> remotes = result.getOut();
3✔
104
      if (remotes.isEmpty()) {
3!
105
        String message = targetRepository + " is a local git repository with no remote - if you did this for testing, you may continue...\n"
×
106
            + "Do you want to ignore the problem and continue anyhow?";
107
        this.context.askToContinue(message);
×
108
      } else {
×
109
        this.processContext.errorHandling(ProcessErrorHandling.WARNING);
5✔
110

111
        if (!this.context.isOffline()) {
4!
112
          pull(targetRepository);
3✔
113
        }
114
      }
115
    } else {
1✔
116
      clone(new GitUrl(gitRepoUrl, branch), targetRepository);
8✔
117
    }
118
  }
1✔
119

120
  /**
121
   * Handles errors which occurred during git pull.
122
   *
123
   * @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 git
124
   * will by default create a sub-folder by default on clone but the * final folder that will contain the ".git" subfolder.
125
   * @param result the {@link ProcessResult} to evaluate.
126
   */
127
  private void handleErrors(Path targetRepository, ProcessResult result) {
128

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

146
  @Override
147
  public void clone(GitUrl gitRepoUrl, Path targetRepository) {
148

149
    URL parsedUrl = gitRepoUrl.parseUrl();
3✔
150
    this.processContext.directory(targetRepository);
5✔
151
    ProcessResult result;
152
    if (!this.context.isOffline()) {
4✔
153
      this.context.getFileAccess().mkdirs(targetRepository);
5✔
154
      this.context.requireOnline("git clone of " + parsedUrl);
5✔
155
      this.processContext.addArg("clone");
5✔
156
      if (this.context.isQuietMode()) {
4!
157
        this.processContext.addArg("-q");
×
158
      }
159
      this.processContext.addArgs("--recursive", gitRepoUrl.url(), "--config", "core.autocrlf=false", ".");
27✔
160
      result = this.processContext.run(PROCESS_MODE);
5✔
161
      if (!result.isSuccessful()) {
3!
162
        this.context.warning("Git failed to clone {} into {}.", parsedUrl, targetRepository);
×
163
      }
164
      String branch = gitRepoUrl.branch();
3✔
165
      if (branch != null) {
2!
166
        this.processContext.addArgs("checkout", branch);
×
167
        result = this.processContext.run(PROCESS_MODE);
×
168
        if (!result.isSuccessful()) {
×
169
          this.context.warning("Git failed to checkout to branch {}", branch);
×
170
        }
171
      }
172
    } else {
1✔
173
      throw new CliOfflineException("Could not clone " + parsedUrl + " to " + targetRepository + " because you are offline.");
7✔
174
    }
175
  }
1✔
176

177
  @Override
178
  public void pull(Path targetRepository) {
179

180
    this.processContext.directory(targetRepository);
5✔
181
    ProcessResult result;
182
    // pull from remote
183
    result = this.processContext.addArg("--no-pager").addArg("pull").addArg("--quiet").run(PROCESS_MODE);
11✔
184

185
    if (!result.isSuccessful()) {
3!
186
      Map<String, String> remoteAndBranchName = retrieveRemoteAndBranchName();
×
187
      this.context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), remoteAndBranchName.get("branch"),
×
188
          targetRepository);
189
      handleErrors(targetRepository, result);
×
190
    }
191
  }
1✔
192

193
  private Map<String, String> retrieveRemoteAndBranchName() {
194

195
    Map<String, String> remoteAndBranchName = new HashMap<>();
×
196
    ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(PROCESS_MODE);
×
197
    List<String> remotes = remoteResult.getOut();
×
198
    if (!remotes.isEmpty()) {
×
199
      for (String remote : remotes) {
×
200
        if (remote.startsWith("*")) {
×
201
          String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]"));
×
202
          remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/")));
×
203
          // check if current repo is behind remote and omit message
204
          if (checkedOutBranch.contains(":")) {
×
205
            remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":")));
×
206
          } else {
207
            remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1));
×
208
          }
209

210
        }
211
      }
×
212
    } else {
213
      return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), new AbstractMap.SimpleEntry<>("branch", "unknown"));
×
214
    }
215

216
    return remoteAndBranchName;
×
217
  }
218

219
  @Override
220
  public void reset(Path targetRepository, String branchName, String remoteName) {
221

222
    if ((remoteName == null) || remoteName.isEmpty()) {
5!
223
      remoteName = DEFAULT_REMOTE;
×
224
    }
225
    this.processContext.directory(targetRepository);
5✔
226
    ProcessResult result;
227
    // check for changed files
228
    result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(PROCESS_MODE);
11✔
229

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

235
      if (!result.isSuccessful()) {
3!
236
        this.context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository);
×
237
        handleErrors(targetRepository, result);
×
238
      }
239
    }
240
  }
1✔
241

242
  @Override
243
  public void cleanup(Path targetRepository) {
244

245
    this.processContext.directory(targetRepository);
5✔
246
    ProcessResult result;
247
    // check for untracked files
248
    result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard").run(ProcessMode.DEFAULT_CAPTURE);
13✔
249

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

255
      if (!result.isSuccessful()) {
3!
256
        this.context.warning("Git failed to clean the repository {}.", targetRepository);
×
257
      }
258
    }
259
  }
1✔
260

261
  @Override
262
  public String retrieveGitUrl(Path repository) {
263

264
    this.processContext.directory(repository);
×
265
    ProcessResult result;
266
    result = this.processContext.addArgs("-C", repository, "remote", "-v").run(ProcessMode.DEFAULT_CAPTURE);
×
267
    for (String line : result.getOut()) {
×
268
      if (line.contains("(fetch)")) {
×
269
        return line.split("\\s+")[1]; // Extract the URL from the line
×
270
      }
271
    }
×
272

273
    this.context.error("Failed to retrieve git URL for repository: {}", repository);
×
274
    return null;
×
275
  }
276
}
277

278

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