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

devonfw / IDEasy / 22283847745

22 Feb 2026 07:34PM UTC coverage: 70.75% (+0.3%) from 70.474%
22283847745

Pull #1714

github

web-flow
Merge 8263a112b into 379acdc9d
Pull Request #1714: #404: #1713: advanced logging

4064 of 6348 branches covered (64.02%)

Branch coverage included in aggregate %.

10635 of 14428 relevant lines covered (73.71%)

3.1 hits per line

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

91.57
cli/src/main/java/com/devonfw/tools/ide/git/GitOperation.java
1
package com.devonfw.tools.ide.git;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.time.Duration;
6

7
import org.slf4j.Logger;
8
import org.slf4j.LoggerFactory;
9

10
import com.devonfw.tools.ide.context.IdeContext;
11

12
/**
13
 * An {@link Enum} for specific Git operations where we add caching support.
14
 *
15
 * @see GitContextImpl
16
 */
17
public enum GitOperation {
2✔
18

19
  /** {@link GitOperation} for {@link GitContext#fetch(Path, String, String)}. */
20
  FETCH("fetch", GitContext.FILE_FETCH_HEAD, Duration.ofMinutes(5)) {
18✔
21
    @Override
22
    protected boolean execute(IdeContext context, GitUrl gitUrl, Path targetRepository, String remote) {
23

24
      context.getGitContext().fetch(targetRepository, remote, gitUrl.branch());
7✔
25
      // TODO: see JavaDoc, implementation incorrect. fetch needs to return boolean if changes have been fetched
26
      // and then this result must be returned - or JavaDoc needs to changed
27
      return true;
2✔
28
    }
29
  },
30

31
  /** {@link GitOperation} for {@link GitContext#clone(GitUrl, Path)}. */
32
  PULL_OR_CLONE("pull/clone", GitContext.FILE_HEAD, Duration.ofMinutes(30)) {
18✔
33
    @Override
34
    protected boolean execute(IdeContext context, GitUrl gitUrl, Path targetRepository, String remote) {
35

36
      context.getGitContext().pullOrClone(gitUrl, targetRepository);
5✔
37
      return true;
2✔
38
    }
39
  };
40

41
  private static final Logger LOG = LoggerFactory.getLogger(GitOperation.class);
4✔
42

43
  private final String name;
44

45
  private final String timestampFilename;
46

47
  private final Duration cacheDuration;
48

49
  private GitOperation(String name, String timestampFilename, Duration cacheDuration) {
4✔
50

51
    this.name = name;
3✔
52
    this.timestampFilename = timestampFilename;
3✔
53
    this.cacheDuration = cacheDuration;
3✔
54
  }
1✔
55

56
  /**
57
   * @return the human readable name of this {@link GitOperation}.
58
   */
59
  public String getName() {
60

61
    return this.name;
×
62
  }
63

64
  /**
65
   * @return the name of the file inside the ".git" folder to get the timestamp (modification time) from in order to determine how long the last
66
   *     {@link GitOperation} was ago.
67
   */
68
  public String getTimestampFilename() {
69

70
    return this.timestampFilename;
3✔
71
  }
72

73
  /**
74
   * @return the {@link Duration} how long this {@link GitOperation} will be skipped.
75
   */
76
  public Duration getCacheDuration() {
77

78
    return cacheDuration;
×
79
  }
80

81
  /**
82
   * @return {@code true} if this operation requires the ".git" folder to be present, {@code false} otherwise.
83
   */
84
  public boolean isRequireGitFolder() {
85

86
    return this == FETCH;
6!
87
  }
88

89
  /**
90
   * @return {@code true} if after this operation the {@link #getTimestampFilename() timestamp file} should be updated, {@code false} otherwise.
91
   */
92
  public boolean isForceUpdateTimestampFile() {
93

94
    return this == PULL_OR_CLONE;
7✔
95
  }
96

97
  /**
98
   * @return {@code true} if after this operation is always {@link #isNeeded(Path, IdeContext) needed} of the ".git" folder not is present, {@code false}
99
   *     otherwise.
100
   */
101
  public boolean isNeededIfGitFolderNotPresent() {
102

103
    return this == PULL_OR_CLONE;
7✔
104
  }
105

106
  /**
107
   * Executes this {@link GitOperation} physically.
108
   *
109
   * @param context the {@link IdeContext}.
110
   * @param gitUrl the git repository URL. Maybe {@code null} if not required by the operation.
111
   * @param targetRepository the {@link Path} to the git repository.
112
   * @param remote the git remote (e.g. "origin"). Maybe {@code null} if not required by the operation.
113
   * @return {@code true} if changes were received from git, {@code false} otherwise.
114
   */
115
  protected abstract boolean execute(IdeContext context, GitUrl gitUrl, Path targetRepository, String remote);
116

117
  /**
118
   * Executes this {@link GitOperation} if {@link #isNeeded(Path, IdeContext) needed}.
119
   *
120
   * @param context the {@link IdeContext}.
121
   * @param gitUrl the git repository URL. Maybe {@code null} if not required by the operation.
122
   * @param targetRepository the {@link Path} to the git repository.
123
   * @param remote the git remote (e.g. "origin"). Maybe {@code null} if not required by the operation.
124
   * @return {@code true} if changes were received from git, {@code false} otherwise (e.g. no git operation was invoked at all).
125
   */
126
  boolean executeIfNeeded(IdeContext context, GitUrl gitUrl, Path targetRepository, String remote) {
127

128
    if (isNeeded(targetRepository, context)) {
5✔
129
      boolean result = execute(context, gitUrl, targetRepository, remote);
7✔
130
      if (isForceUpdateTimestampFile()) {
3✔
131
        Path timestampPath = targetRepository.resolve(GitContext.GIT_FOLDER).resolve(this.timestampFilename);
7✔
132
        try {
133
          context.getFileAccess().touch(timestampPath);
4✔
134
        } catch (IllegalStateException e) {
1✔
135
          LOG.warn(e.getMessage());
4✔
136
        }
1✔
137
      }
138
      return result;
2✔
139
    } else {
140
      LOG.trace("Skipped git {}.", this.name);
5✔
141
      return false;
2✔
142
    }
143
  }
144

145
  private boolean isNeeded(Path targetRepository, IdeContext context) {
146

147
    Path gitDirectory = targetRepository.resolve(".git");
4✔
148
    boolean hasGitDirectory = Files.isDirectory(gitDirectory);
5✔
149
    if (isNeededIfGitFolderNotPresent() && !hasGitDirectory) {
5✔
150
      logEnforceGitOperationBecauseGitFolderNotPresent(targetRepository, context);
4✔
151
      return true;
2✔
152
    }
153
    if (context.isOffline()) {
3✔
154
      LOG.info("Skipping git {} on {} because we are offline.", this.name, targetRepository);
6✔
155
      return false;
2✔
156
    } else if (context.isForceMode() || context.isForcePull()) {
6!
157
      LOG.debug("Enforcing git {} on {} because force mode is active.", this.name, targetRepository);
6✔
158
      return true;
2✔
159
    }
160
    if (!hasGitDirectory) {
2✔
161
      if (isRequireGitFolder()) {
3!
162
        if (context.getSettingsGitRepository() == null) {
3!
163
          LOG.warn("Missing .git folder in {}.", targetRepository);
5✔
164
        }
165
      } else {
166
        logEnforceGitOperationBecauseGitFolderNotPresent(targetRepository, context);
×
167
      }
168
      return true; // technically this is an error that will be triggered by fetch method
2✔
169
    }
170
    Path timestampFilePath = gitDirectory.resolve(this.timestampFilename);
5✔
171
    if (context.getFileAccess().isFileAgeRecent(timestampFilePath, this.cacheDuration)) {
7✔
172
      LOG.debug("Skipping git {} on {} because last fetch was just recently to avoid overhead.", this.name,
6✔
173
          targetRepository);
174
      return false;
2✔
175
    } else {
176
      LOG.debug("Will need to do git {} on {} because last fetch was some time ago.", this.name, targetRepository);
6✔
177
      return true;
2✔
178
    }
179
  }
180

181
  private void logEnforceGitOperationBecauseGitFolderNotPresent(Path targetRepository, IdeContext context) {
182
    LOG.debug("Enforcing git {} on {} because .git folder is not present.", this.name, targetRepository);
6✔
183
  }
1✔
184

185
}
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