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

devonfw / IDEasy / 15847147716

24 Jun 2025 09:49AM UTC coverage: 67.779% (-0.01%) from 67.793%
15847147716

Pull #1390

github

web-flow
Merge d49664b7c into f0c645132
Pull Request #1390: Add debug logging for connection errors

3176 of 5088 branches covered (62.42%)

Branch coverage included in aggregate %.

8137 of 11603 relevant lines covered (70.13%)

3.08 hits per line

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

61.42
cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
1
package com.devonfw.tools.ide.context;
2

3
import static com.devonfw.tools.ide.variable.IdeVariables.IDE_MIN_VERSION;
4

5
import java.io.BufferedReader;
6
import java.io.InputStreamReader;
7
import java.net.URL;
8
import java.net.URLConnection;
9
import java.nio.file.Files;
10
import java.nio.file.Path;
11
import java.time.LocalDateTime;
12
import java.util.ArrayList;
13
import java.util.HashMap;
14
import java.util.Iterator;
15
import java.util.List;
16
import java.util.Locale;
17
import java.util.Map;
18
import java.util.Objects;
19

20
import com.devonfw.tools.ide.cli.CliAbortException;
21
import com.devonfw.tools.ide.cli.CliArgument;
22
import com.devonfw.tools.ide.cli.CliArguments;
23
import com.devonfw.tools.ide.cli.CliException;
24
import com.devonfw.tools.ide.commandlet.Commandlet;
25
import com.devonfw.tools.ide.commandlet.CommandletManager;
26
import com.devonfw.tools.ide.commandlet.CommandletManagerImpl;
27
import com.devonfw.tools.ide.commandlet.ContextCommandlet;
28
import com.devonfw.tools.ide.commandlet.EnvironmentCommandlet;
29
import com.devonfw.tools.ide.commandlet.HelpCommandlet;
30
import com.devonfw.tools.ide.common.SystemPath;
31
import com.devonfw.tools.ide.completion.CompletionCandidate;
32
import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
33
import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault;
34
import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables;
35
import com.devonfw.tools.ide.environment.EnvironmentVariables;
36
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
37
import com.devonfw.tools.ide.environment.IdeSystem;
38
import com.devonfw.tools.ide.environment.IdeSystemImpl;
39
import com.devonfw.tools.ide.git.GitContext;
40
import com.devonfw.tools.ide.git.GitContextImpl;
41
import com.devonfw.tools.ide.git.GitUrl;
42
import com.devonfw.tools.ide.io.FileAccess;
43
import com.devonfw.tools.ide.io.FileAccessImpl;
44
import com.devonfw.tools.ide.log.IdeLogLevel;
45
import com.devonfw.tools.ide.log.IdeLogger;
46
import com.devonfw.tools.ide.log.IdeSubLogger;
47
import com.devonfw.tools.ide.merge.DirectoryMerger;
48
import com.devonfw.tools.ide.migration.IdeMigrator;
49
import com.devonfw.tools.ide.network.NetworkProxy;
50
import com.devonfw.tools.ide.os.SystemInfo;
51
import com.devonfw.tools.ide.os.SystemInfoImpl;
52
import com.devonfw.tools.ide.os.WindowsHelper;
53
import com.devonfw.tools.ide.os.WindowsHelperImpl;
54
import com.devonfw.tools.ide.os.WindowsPathSyntax;
55
import com.devonfw.tools.ide.process.ProcessContext;
56
import com.devonfw.tools.ide.process.ProcessContextImpl;
57
import com.devonfw.tools.ide.process.ProcessResult;
58
import com.devonfw.tools.ide.property.Property;
59
import com.devonfw.tools.ide.step.Step;
60
import com.devonfw.tools.ide.step.StepImpl;
61
import com.devonfw.tools.ide.tool.repository.CustomToolRepository;
62
import com.devonfw.tools.ide.tool.repository.CustomToolRepositoryImpl;
63
import com.devonfw.tools.ide.tool.repository.DefaultToolRepository;
64
import com.devonfw.tools.ide.tool.repository.MavenRepository;
65
import com.devonfw.tools.ide.tool.repository.ToolRepository;
66
import com.devonfw.tools.ide.url.model.UrlMetadata;
67
import com.devonfw.tools.ide.util.DateTimeUtil;
68
import com.devonfw.tools.ide.validation.ValidationResult;
69
import com.devonfw.tools.ide.validation.ValidationResultValid;
70
import com.devonfw.tools.ide.validation.ValidationState;
71
import com.devonfw.tools.ide.variable.IdeVariables;
72
import com.devonfw.tools.ide.version.IdeVersion;
73
import com.devonfw.tools.ide.version.VersionIdentifier;
74

75
/**
76
 * Abstract base implementation of {@link IdeContext}.
77
 */
78
public abstract class AbstractIdeContext implements IdeContext {
79

80
  private static final GitUrl IDE_URLS_GIT = new GitUrl("https://github.com/devonfw/ide-urls.git", null);
7✔
81

82
  private static final String LICENSE_URL = "https://github.com/devonfw/IDEasy/blob/main/documentation/LICENSE.adoc";
83

84
  private final IdeStartContextImpl startContext;
85

86
  private Path ideHome;
87

88
  private final Path ideRoot;
89

90
  private Path confPath;
91

92
  protected Path settingsPath;
93

94
  private Path settingsCommitIdPath;
95

96
  protected Path pluginsPath;
97

98
  private Path workspacePath;
99

100
  private String workspaceName;
101

102
  private Path cwd;
103

104
  private Path downloadPath;
105

106
  protected Path userHome;
107

108
  private Path userHomeIde;
109

110
  private SystemPath path;
111

112
  private WindowsPathSyntax pathSyntax;
113

114
  private final SystemInfo systemInfo;
115

116
  private EnvironmentVariables variables;
117

118
  private final FileAccess fileAccess;
119

120
  protected CommandletManager commandletManager;
121

122
  protected ToolRepository defaultToolRepository;
123

124
  private CustomToolRepository customToolRepository;
125

126
  private MavenRepository mavenRepository;
127

128
  private DirectoryMerger workspaceMerger;
129

130
  protected UrlMetadata urlMetadata;
131

132
  protected Path defaultExecutionDirectory;
133

134
  private StepImpl currentStep;
135

136
  protected Boolean online;
137

138
  protected IdeSystem system;
139

140
  private NetworkProxy networkProxy;
141

142
  private WindowsHelper windowsHelper;
143

144
  /**
145
   * The constructor.
146
   *
147
   * @param startContext the {@link IdeLogger}.
148
   * @param workingDirectory the optional {@link Path} to current working directory.
149
   */
150
  public AbstractIdeContext(IdeStartContextImpl startContext, Path workingDirectory) {
151

152
    super();
2✔
153
    this.startContext = startContext;
3✔
154
    this.systemInfo = SystemInfoImpl.INSTANCE;
3✔
155
    this.commandletManager = new CommandletManagerImpl(this);
6✔
156
    this.fileAccess = new FileAccessImpl(this);
6✔
157
    String workspace = WORKSPACE_MAIN;
2✔
158
    if (workingDirectory == null) {
2!
159
      workingDirectory = Path.of(System.getProperty("user.dir"));
×
160
    }
161
    workingDirectory = workingDirectory.toAbsolutePath();
3✔
162
    if (Files.isDirectory(workingDirectory)) {
5✔
163
      workingDirectory = this.fileAccess.toCanonicalPath(workingDirectory);
6✔
164
    } else {
165
      warning("Current working directory does not exist: {}", workingDirectory);
9✔
166
    }
167
    this.cwd = workingDirectory;
3✔
168
    // detect IDE_HOME and WORKSPACE
169
    Path currentDir = workingDirectory;
2✔
170
    String name1 = "";
2✔
171
    String name2 = "";
2✔
172
    Path ideRootPath = getIdeRootPathFromEnv(false);
4✔
173
    while (currentDir != null) {
2✔
174
      trace("Looking for IDE_HOME in {}", currentDir);
9✔
175
      if (isIdeHome(currentDir)) {
4✔
176
        if (FOLDER_WORKSPACES.equals(name1) && !name2.isEmpty()) {
7✔
177
          workspace = name2;
3✔
178
        }
179
        break;
180
      }
181
      name2 = name1;
2✔
182
      int nameCount = currentDir.getNameCount();
3✔
183
      if (nameCount >= 1) {
3✔
184
        name1 = currentDir.getName(nameCount - 1).toString();
7✔
185
      }
186
      currentDir = currentDir.getParent();
3✔
187
      if ((ideRootPath != null) && (ideRootPath.equals(currentDir))) {
2!
188
        // prevent that during tests we traverse to the real IDE project of IDEasy developer
189
        currentDir = null;
×
190
      }
191
    }
1✔
192

193
    // detection completed, initializing variables
194
    this.ideRoot = findIdeRoot(currentDir);
5✔
195

196
    setCwd(workingDirectory, workspace, currentDir);
5✔
197

198
    if (this.ideRoot != null) {
3✔
199
      Path tempDownloadPath = getTempDownloadPath();
3✔
200
      if (Files.isDirectory(tempDownloadPath)) {
6✔
201
        // TODO delete all files older than 1 day here...
202
      } else {
203
        this.fileAccess.mkdirs(tempDownloadPath);
4✔
204
      }
205
    }
206

207
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
208
    this.mavenRepository = new MavenRepository(this);
6✔
209
  }
1✔
210

211
  private Path findIdeRoot(Path ideHomePath) {
212

213
    Path ideRootPath = null;
2✔
214
    if (ideHomePath != null) {
2✔
215
      Path ideRootPathFromEnv = getIdeRootPathFromEnv(true);
4✔
216
      ideRootPath = ideHomePath.getParent();
3✔
217
      if ((ideRootPathFromEnv != null) && !ideRootPath.toString().equals(ideRootPathFromEnv.toString())) {
8!
218
        warning(
12✔
219
            "Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.\n"
220
                + "Please check your 'user.dir' or working directory setting and make sure that it matches your IDE_ROOT variable.",
221
            ideRootPathFromEnv,
222
            ideHomePath.getFileName(), ideRootPath);
6✔
223
      }
224

225
    } else if (!isTest()) {
4!
226
      ideRootPath = getIdeRootPathFromEnv(true);
×
227
    }
228
    return ideRootPath;
2✔
229
  }
230

231
  /**
232
   * @return the {@link #getIdeRoot() IDE_ROOT} from the system environment.
233
   */
234
  protected Path getIdeRootPathFromEnv(boolean withSanityCheck) {
235

236
    String root = getSystem().getEnv(IdeVariables.IDE_ROOT.getName());
×
237
    if (root != null) {
×
238
      Path rootPath = Path.of(root);
×
239
      if (Files.isDirectory(rootPath)) {
×
240
        Path absoluteRootPath = getFileAccess().toCanonicalPath(rootPath);
×
241
        if (withSanityCheck) {
×
242
          int nameCount = rootPath.getNameCount();
×
243
          int absoluteNameCount = absoluteRootPath.getNameCount();
×
244
          int delta = absoluteNameCount - nameCount;
×
245
          if (delta >= 0) {
×
246
            for (int nameIndex = 0; nameIndex < nameCount; nameIndex++) {
×
247
              String rootName = rootPath.getName(nameIndex).toString();
×
248
              String absoluteRootName = absoluteRootPath.getName(nameIndex + delta).toString();
×
249
              if (!rootName.equals(absoluteRootName)) {
×
250
                warning("IDE_ROOT is set to {} but was expanded to absolute path {} and does not match for segment {} and {} - fix your IDEasy installation!",
×
251
                    rootPath, absoluteRootPath, rootName, absoluteRootName);
252
                break;
×
253
              }
254
            }
255
          } else {
256
            warning("IDE_ROOT is set to {} but was expanded to a shorter absolute path {}", rootPath, absoluteRootPath);
×
257
          }
258
        }
259
        return absoluteRootPath;
×
260
      } else if (withSanityCheck) {
×
261
        warning("IDE_ROOT is set to {} that is not an existing directory - fix your IDEasy installation!", rootPath);
×
262
      }
263
    }
264
    return null;
×
265
  }
266

267
  @Override
268
  public void setCwd(Path userDir, String workspace, Path ideHome) {
269

270
    this.cwd = userDir;
3✔
271
    this.workspaceName = workspace;
3✔
272
    this.ideHome = ideHome;
3✔
273
    if (ideHome == null) {
2✔
274
      this.workspacePath = null;
3✔
275
      this.confPath = null;
3✔
276
      this.settingsPath = null;
3✔
277
      this.pluginsPath = null;
4✔
278
    } else {
279
      this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName);
9✔
280
      this.confPath = this.ideHome.resolve(FOLDER_CONF);
6✔
281
      this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS);
6✔
282
      this.settingsCommitIdPath = this.ideHome.resolve(IdeContext.SETTINGS_COMMIT_ID);
6✔
283
      this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS);
6✔
284
    }
285
    if (isTest()) {
3!
286
      // only for testing...
287
      if (this.ideHome == null) {
3✔
288
        this.userHome = Path.of("/non-existing-user-home-for-testing");
7✔
289
      } else {
290
        this.userHome = this.ideHome.resolve("home");
7✔
291
      }
292
    } else {
293
      this.userHome = Path.of(getSystem().getProperty("user.home"));
×
294
    }
295
    this.userHomeIde = this.userHome.resolve(FOLDER_DOT_IDE);
6✔
296
    this.downloadPath = this.userHome.resolve("Downloads/ide");
6✔
297

298
    this.path = computeSystemPath();
4✔
299
  }
1✔
300

301
  private String getMessageIdeHomeFound() {
302

303
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
7✔
304
  }
305

306
  private String getMessageNotInsideIdeProject() {
307

308
    return "You are not inside an IDE project: " + this.cwd;
5✔
309
  }
310

311
  private String getMessageIdeRootNotFound() {
312

313
    String root = getSystem().getEnv("IDE_ROOT");
5✔
314
    if (root == null) {
2!
315
      return "The environment variable IDE_ROOT is undefined. Please reinstall IDEasy or manually repair IDE_ROOT variable.";
2✔
316
    } else {
317
      return "The environment variable IDE_ROOT is pointing to an invalid path " + root + ". Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
318
    }
319
  }
320

321
  /**
322
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
323
   */
324
  public boolean isTest() {
325

326
    return false;
×
327
  }
328

329
  protected SystemPath computeSystemPath() {
330

331
    return new SystemPath(this);
×
332
  }
333

334
  private boolean isIdeHome(Path dir) {
335

336
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
337
      return false;
2✔
338
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
339
      return false;
×
340
    }
341
    return true;
2✔
342
  }
343

344
  private EnvironmentVariables createVariables() {
345

346
    AbstractEnvironmentVariables system = createSystemVariables();
3✔
347
    AbstractEnvironmentVariables user = system.extend(this.userHomeIde, EnvironmentVariablesType.USER);
6✔
348
    AbstractEnvironmentVariables settings = user.extend(this.settingsPath, EnvironmentVariablesType.SETTINGS);
6✔
349
    AbstractEnvironmentVariables workspace = settings.extend(this.workspacePath, EnvironmentVariablesType.WORKSPACE);
6✔
350
    AbstractEnvironmentVariables conf = workspace.extend(this.confPath, EnvironmentVariablesType.CONF);
6✔
351
    return conf.resolved();
3✔
352
  }
353

354
  protected AbstractEnvironmentVariables createSystemVariables() {
355

356
    return EnvironmentVariables.ofSystem(this);
3✔
357
  }
358

359
  @Override
360
  public SystemInfo getSystemInfo() {
361

362
    return this.systemInfo;
3✔
363
  }
364

365
  @Override
366
  public FileAccess getFileAccess() {
367

368
    // currently FileAccess contains download method and requires network proxy to be configured. Maybe download should be moved to its own interface/class
369
    configureNetworkProxy();
2✔
370
    return this.fileAccess;
3✔
371
  }
372

373
  @Override
374
  public CommandletManager getCommandletManager() {
375

376
    return this.commandletManager;
3✔
377
  }
378

379
  @Override
380
  public ToolRepository getDefaultToolRepository() {
381

382
    return this.defaultToolRepository;
3✔
383
  }
384

385
  @Override
386
  public MavenRepository getMavenToolRepository() {
387

388
    return this.mavenRepository;
3✔
389
  }
390

391
  @Override
392
  public CustomToolRepository getCustomToolRepository() {
393

394
    if (this.customToolRepository == null) {
3!
395
      this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
396
    }
397
    return this.customToolRepository;
3✔
398
  }
399

400
  @Override
401
  public Path getIdeHome() {
402

403
    return this.ideHome;
3✔
404
  }
405

406
  @Override
407
  public String getProjectName() {
408

409
    if (this.ideHome != null) {
3!
410
      return this.ideHome.getFileName().toString();
5✔
411
    }
412
    return "";
×
413
  }
414

415
  @Override
416
  public VersionIdentifier getProjectVersion() {
417

418
    if (this.ideHome != null) {
3!
419
      Path versionFile = this.ideHome.resolve(IdeContext.FILE_SOFTWARE_VERSION);
5✔
420
      if (Files.exists(versionFile)) {
5✔
421
        String version = this.fileAccess.readFileContent(versionFile).trim();
6✔
422
        return VersionIdentifier.of(version);
3✔
423
      }
424
    }
425
    return IdeMigrator.START_VERSION;
2✔
426
  }
427

428
  @Override
429
  public void setProjectVersion(VersionIdentifier version) {
430

431
    if (this.ideHome == null) {
3!
432
      throw new IllegalStateException("IDE_HOME not available!");
×
433
    }
434
    Objects.requireNonNull(version);
3✔
435
    Path versionFile = this.ideHome.resolve(IdeContext.FILE_SOFTWARE_VERSION);
5✔
436
    this.fileAccess.writeFileContent(version.toString(), versionFile);
6✔
437
  }
1✔
438

439
  @Override
440
  public Path getIdeRoot() {
441

442
    return this.ideRoot;
3✔
443
  }
444

445
  @Override
446
  public Path getIdePath() {
447

448
    Path myIdeRoot = getIdeRoot();
3✔
449
    if (myIdeRoot == null) {
2!
450
      return null;
×
451
    }
452
    return myIdeRoot.resolve(FOLDER_UNDERSCORE_IDE);
4✔
453
  }
454

455
  @Override
456
  public Path getCwd() {
457

458
    return this.cwd;
3✔
459
  }
460

461
  @Override
462
  public Path getTempPath() {
463

464
    Path idePath = getIdePath();
3✔
465
    if (idePath == null) {
2!
466
      return null;
×
467
    }
468
    return idePath.resolve("tmp");
4✔
469
  }
470

471
  @Override
472
  public Path getTempDownloadPath() {
473

474
    Path tmp = getTempPath();
3✔
475
    if (tmp == null) {
2!
476
      return null;
×
477
    }
478
    return tmp.resolve(FOLDER_DOWNLOADS);
4✔
479
  }
480

481
  @Override
482
  public Path getUserHome() {
483

484
    return this.userHome;
3✔
485
  }
486

487
  @Override
488
  public Path getUserHomeIde() {
489

490
    return this.userHomeIde;
3✔
491
  }
492

493
  @Override
494
  public Path getSettingsPath() {
495

496
    return this.settingsPath;
3✔
497
  }
498

499
  @Override
500
  public Path getSettingsGitRepository() {
501

502
    Path settingsPath = getSettingsPath();
3✔
503
    // check whether the settings path has a .git folder only if its not a symbolic link or junction
504
    if ((settingsPath != null) && !Files.exists(settingsPath.resolve(".git")) && !isSettingsRepositorySymlinkOrJunction()) {
12!
505
      error("Settings repository exists but is not a git repository.");
3✔
506
      return null;
2✔
507
    }
508
    return settingsPath;
2✔
509
  }
510

511
  @Override
512
  public boolean isSettingsRepositorySymlinkOrJunction() {
513

514
    Path settingsPath = getSettingsPath();
3✔
515
    if (settingsPath == null) {
2!
516
      return false;
×
517
    }
518
    return Files.isSymbolicLink(settingsPath) || getFileAccess().isJunction(settingsPath);
10!
519
  }
520

521
  @Override
522
  public Path getSettingsCommitIdPath() {
523

524
    return this.settingsCommitIdPath;
3✔
525
  }
526

527
  @Override
528
  public Path getConfPath() {
529

530
    return this.confPath;
3✔
531
  }
532

533
  @Override
534
  public Path getSoftwarePath() {
535

536
    if (this.ideHome == null) {
3✔
537
      return null;
2✔
538
    }
539
    return this.ideHome.resolve(FOLDER_SOFTWARE);
5✔
540
  }
541

542
  @Override
543
  public Path getSoftwareExtraPath() {
544

545
    Path softwarePath = getSoftwarePath();
3✔
546
    if (softwarePath == null) {
2!
547
      return null;
×
548
    }
549
    return softwarePath.resolve(FOLDER_EXTRA);
4✔
550
  }
551

552
  @Override
553
  public Path getSoftwareRepositoryPath() {
554

555
    Path idePath = getIdePath();
3✔
556
    if (idePath == null) {
2!
557
      return null;
×
558
    }
559
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
560
  }
561

562
  @Override
563
  public Path getPluginsPath() {
564

565
    return this.pluginsPath;
3✔
566
  }
567

568
  @Override
569
  public String getWorkspaceName() {
570

571
    return this.workspaceName;
3✔
572
  }
573

574
  @Override
575
  public Path getWorkspacePath() {
576

577
    return this.workspacePath;
3✔
578
  }
579

580
  @Override
581
  public Path getDownloadPath() {
582

583
    return this.downloadPath;
3✔
584
  }
585

586
  @Override
587
  public Path getUrlsPath() {
588

589
    Path idePath = getIdePath();
3✔
590
    if (idePath == null) {
2!
591
      return null;
×
592
    }
593
    return idePath.resolve(FOLDER_URLS);
4✔
594
  }
595

596
  @Override
597
  public Path getToolRepositoryPath() {
598

599
    Path idePath = getIdePath();
3✔
600
    if (idePath == null) {
2!
601
      return null;
×
602
    }
603
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
604
  }
605

606
  @Override
607
  public SystemPath getPath() {
608

609
    return this.path;
3✔
610
  }
611

612
  @Override
613
  public EnvironmentVariables getVariables() {
614

615
    if (this.variables == null) {
3✔
616
      this.variables = createVariables();
4✔
617
    }
618
    return this.variables;
3✔
619
  }
620

621
  @Override
622
  public UrlMetadata getUrls() {
623

624
    if (this.urlMetadata == null) {
3✔
625
      if (!isTest()) {
3!
626
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, getUrlsPath(), null);
×
627
      }
628
      this.urlMetadata = new UrlMetadata(this);
6✔
629
    }
630
    return this.urlMetadata;
3✔
631
  }
632

633
  @Override
634
  public boolean isQuietMode() {
635

636
    return this.startContext.isQuietMode();
4✔
637
  }
638

639
  @Override
640
  public boolean isBatchMode() {
641

642
    return this.startContext.isBatchMode();
4✔
643
  }
644

645
  @Override
646
  public boolean isForceMode() {
647

648
    return this.startContext.isForceMode();
4✔
649
  }
650

651
  @Override
652
  public boolean isForcePull() {
653

654
    return this.startContext.isForcePull();
×
655
  }
656

657
  @Override
658
  public boolean isForcePlugins() {
659

660
    return this.startContext.isForcePlugins();
×
661
  }
662

663
  @Override
664
  public boolean isForceRepositories() {
665

666
    return this.startContext.isForceRepositories();
×
667
  }
668

669
  @Override
670
  public boolean isOfflineMode() {
671

672
    return this.startContext.isOfflineMode();
4✔
673
  }
674

675
  @Override
676
  public boolean isSkipUpdatesMode() {
677

678
    return this.startContext.isSkipUpdatesMode();
4✔
679
  }
680

681
  @Override
682
  public boolean isOnline() {
683

684
    if (this.online == null) {
3✔
685
      configureNetworkProxy();
2✔
686
      // we currently assume we have only a CLI process that runs shortly
687
      // therefore we run this check only once to save resources when this method is called many times
688
      String url = "https://www.github.com";
2✔
689
      try {
690
        int timeout = 1000;
2✔
691
        //open a connection to github.com and try to retrieve data
692
        //getContent fails if there is no connection
693
        URLConnection connection = new URL(url).openConnection();
6✔
694
        connection.setConnectTimeout(timeout);
3✔
695
        connection.getContent();
3✔
696
        this.online = Boolean.TRUE;
3✔
697
      } catch (Exception e) {
×
698
        this.startContext.debug("Error when trying to connect to {}: {}", url, e);
×
699
        this.online = Boolean.FALSE;
×
700
      }
1✔
701
    }
702
    return this.online.booleanValue();
4✔
703
  }
704

705
  private void configureNetworkProxy() {
706

707
    if (this.networkProxy == null) {
3✔
708
      this.networkProxy = new NetworkProxy(this);
6✔
709
      this.networkProxy.configure();
3✔
710
    }
711
  }
1✔
712

713
  @Override
714
  public Locale getLocale() {
715

716
    Locale locale = this.startContext.getLocale();
4✔
717
    if (locale == null) {
2✔
718
      locale = Locale.getDefault();
2✔
719
    }
720
    return locale;
2✔
721
  }
722

723
  @Override
724
  public DirectoryMerger getWorkspaceMerger() {
725

726
    if (this.workspaceMerger == null) {
3✔
727
      this.workspaceMerger = new DirectoryMerger(this);
6✔
728
    }
729
    return this.workspaceMerger;
3✔
730
  }
731

732
  /**
733
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
734
   */
735
  @Override
736
  public Path getDefaultExecutionDirectory() {
737

738
    return this.defaultExecutionDirectory;
×
739
  }
740

741
  /**
742
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
743
   */
744
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
745

746
    if (defaultExecutionDirectory != null) {
×
747
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
748
    }
749
  }
×
750

751
  @Override
752
  public GitContext getGitContext() {
753

754
    return new GitContextImpl(this);
×
755
  }
756

757
  @Override
758
  public ProcessContext newProcess() {
759

760
    ProcessContext processContext = createProcessContext();
3✔
761
    if (this.defaultExecutionDirectory != null) {
3!
762
      processContext.directory(this.defaultExecutionDirectory);
×
763
    }
764
    return processContext;
2✔
765
  }
766

767
  @Override
768
  public IdeSystem getSystem() {
769

770
    if (this.system == null) {
×
771
      this.system = new IdeSystemImpl(this);
×
772
    }
773
    return this.system;
×
774
  }
775

776
  /**
777
   * @return a new instance of {@link ProcessContext}.
778
   * @see #newProcess()
779
   */
780
  protected ProcessContext createProcessContext() {
781

782
    return new ProcessContextImpl(this);
5✔
783
  }
784

785
  @Override
786
  public IdeSubLogger level(IdeLogLevel level) {
787

788
    return this.startContext.level(level);
5✔
789
  }
790

791
  @Override
792
  public void logIdeHomeAndRootStatus() {
793

794
    if (this.ideRoot != null) {
3!
795
      success("IDE_ROOT is set to {}", this.ideRoot);
×
796
    }
797
    if (this.ideHome == null) {
3!
798
      warning(getMessageNotInsideIdeProject());
5✔
799
    } else {
800
      success("IDE_HOME is set to {}", this.ideHome);
×
801
    }
802
  }
1✔
803

804
  @Override
805
  public String askForInput(String message, String defaultValue) {
806

807
    if (!message.isBlank()) {
×
808
      info(message);
×
809
    }
810
    if (isBatchMode()) {
×
811
      if (isForceMode() || isForcePull()) {
×
812
        return defaultValue;
×
813
      } else {
814
        throw new CliAbortException();
×
815
      }
816
    }
817
    String input = readLine().trim();
×
818
    return input.isEmpty() ? defaultValue : input;
×
819
  }
820

821
  @Override
822
  public String askForInput(String message) {
823

824
    String input;
825
    do {
826
      info(message);
3✔
827
      input = readLine().trim();
4✔
828
    } while (input.isEmpty());
3!
829

830
    return input;
2✔
831
  }
832

833
  @SuppressWarnings("unchecked")
834
  @Override
835
  public <O> O question(String question, O... options) {
836

837
    assert (options.length >= 2);
×
838
    interaction(question);
×
839
    Map<String, O> mapping = new HashMap<>(options.length);
×
840
    int i = 0;
×
841
    for (O option : options) {
×
842
      i++;
×
843
      String key = "" + option;
×
844
      addMapping(mapping, key, option);
×
845
      String numericKey = Integer.toString(i);
×
846
      if (numericKey.equals(key)) {
×
847
        trace("Options should not be numeric: " + key);
×
848
      } else {
849
        addMapping(mapping, numericKey, option);
×
850
      }
851
      interaction("Option " + numericKey + ": " + key);
×
852
    }
853
    O option = null;
×
854
    if (isBatchMode()) {
×
855
      if (isForceMode() || isForcePull()) {
×
856
        option = options[0];
×
857
        interaction("" + option);
×
858
      }
859
    } else {
860
      while (option == null) {
×
861
        String answer = readLine();
×
862
        option = mapping.get(answer);
×
863
        if (option == null) {
×
864
          warning("Invalid answer: '" + answer + "' - please try again.");
×
865
        }
866
      }
×
867
    }
868
    return option;
×
869
  }
870

871
  /**
872
   * @return the input from the end-user (e.g. read from the console).
873
   */
874
  protected abstract String readLine();
875

876
  private static <O> void addMapping(Map<String, O> mapping, String key, O option) {
877

878
    O duplicate = mapping.put(key, option);
×
879
    if (duplicate != null) {
×
880
      throw new IllegalArgumentException("Duplicated option " + key);
×
881
    }
882
  }
×
883

884
  @Override
885
  public Step getCurrentStep() {
886

887
    return this.currentStep;
×
888
  }
889

890
  @Override
891
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
892

893
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
894
    return this.currentStep;
3✔
895
  }
896

897
  /**
898
   * Internal method to end the running {@link Step}.
899
   *
900
   * @param step the current {@link Step} to end.
901
   */
902
  public void endStep(StepImpl step) {
903

904
    if (step == this.currentStep) {
4!
905
      this.currentStep = this.currentStep.getParent();
6✔
906
    } else {
907
      String currentStepName = "null";
×
908
      if (this.currentStep != null) {
×
909
        currentStepName = this.currentStep.getName();
×
910
      }
911
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
912
    }
913
  }
1✔
914

915
  /**
916
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
917
   *
918
   * @param arguments the {@link CliArgument}.
919
   * @return the return code of the execution.
920
   */
921
  public int run(CliArguments arguments) {
922

923
    CliArgument current = arguments.current();
3✔
924
    assert (this.currentStep == null);
4!
925
    boolean supressStepSuccess = false;
2✔
926
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
8✔
927
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, null);
6✔
928
    Commandlet cmd = null;
2✔
929
    ValidationResult result = null;
2✔
930
    try {
931
      while (commandletIterator.hasNext()) {
3✔
932
        cmd = commandletIterator.next();
4✔
933
        result = applyAndRun(arguments.copy(), cmd);
6✔
934
        if (result.isValid()) {
3!
935
          supressStepSuccess = cmd.isSuppressStepSuccess();
3✔
936
          step.success();
2✔
937
          return ProcessResult.SUCCESS;
4✔
938
        }
939
      }
940
      this.startContext.activateLogging();
3✔
941
      verifyIdeMinVersion(false);
3✔
942
      if (result != null) {
2!
943
        error(result.getErrorMessage());
×
944
      }
945
      step.error("Invalid arguments: {}", current.getArgs());
10✔
946
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
6✔
947
      if (cmd != null) {
2!
948
        help.commandlet.setValue(cmd);
×
949
      }
950
      help.run();
2✔
951
      return 1;
4✔
952
    } catch (Throwable t) {
1✔
953
      this.startContext.activateLogging();
3✔
954
      step.error(t, true);
4✔
955
      throw t;
2✔
956
    } finally {
957
      step.close();
2✔
958
      assert (this.currentStep == null);
4!
959
      step.logSummary(supressStepSuccess);
3✔
960
    }
961
  }
962

963
  @Override
964
  public void runWithoutLogging(Runnable lambda, IdeLogLevel threshold) {
965

966
    this.startContext.deactivateLogging(threshold);
4✔
967
    lambda.run();
2✔
968
    this.startContext.activateLogging();
3✔
969
  }
1✔
970

971
  /**
972
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet) apply} and {@link Commandlet#run() run}.
973
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
974
   *     {@link Commandlet} did not match and we have to try a different candidate).
975
   */
976
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
977

978
    IdeLogLevel previousLogLevel = null;
2✔
979
    cmd.reset();
2✔
980
    ValidationResult result = apply(arguments, cmd);
5✔
981
    if (result.isValid()) {
3!
982
      result = cmd.validate();
3✔
983
    }
984
    if (result.isValid()) {
3!
985
      debug("Running commandlet {}", cmd);
9✔
986
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
987
        throw new CliException(getMessageNotInsideIdeProject(), ProcessResult.NO_IDE_HOME);
×
988
      } else if (cmd.isIdeRootRequired() && (this.ideRoot == null)) {
6!
989
        throw new CliException(getMessageIdeRootNotFound(), ProcessResult.NO_IDE_ROOT);
7✔
990
      }
991
      try {
992
        if (cmd.isProcessableOutput()) {
3!
993
          if (!debug().isEnabled()) {
×
994
            // unless --debug or --trace was supplied, processable output commandlets will disable all log-levels except INFO to prevent other logs interfere
995
            previousLogLevel = this.startContext.setLogLevel(IdeLogLevel.PROCESSABLE);
×
996
          }
997
          this.startContext.activateLogging();
×
998
        } else {
999
          this.startContext.activateLogging();
3✔
1000
          if (cmd.isIdeHomeRequired()) {
3!
1001
            debug(getMessageIdeHomeFound());
4✔
1002
          }
1003
          Path settingsRepository = getSettingsGitRepository();
3✔
1004
          if (settingsRepository != null) {
2!
1005
            if (getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()) || (
×
1006
                getGitContext().fetchIfNeeded(settingsRepository) && getGitContext().isRepositoryUpdateAvailable(
×
1007
                    settingsRepository, getSettingsCommitIdPath()))) {
×
1008
              if (isSettingsRepositorySymlinkOrJunction()) {
×
1009
                interaction(
×
1010
                    "Updates are available for the settings repository. Please pull the latest changes by yourself or by calling \"ide -f update\" to apply them.");
1011

1012
              } else {
1013
                interaction(
×
1014
                    "Updates are available for the settings repository. If you want to apply the latest changes, call \"ide update\"");
1015
              }
1016
            }
1017
          }
1018
        }
1019
        boolean success = ensureLicenseAgreement(cmd);
4✔
1020
        if (!success) {
2!
1021
          return ValidationResultValid.get();
×
1022
        }
1023
        cmd.run();
2✔
1024
      } finally {
1025
        if (previousLogLevel != null) {
2!
1026
          this.startContext.setLogLevel(previousLogLevel);
×
1027
        }
1028
      }
1✔
1029
    } else {
1030
      trace("Commandlet did not match");
×
1031
    }
1032
    return result;
2✔
1033
  }
1034

1035
  private boolean ensureLicenseAgreement(Commandlet cmd) {
1036

1037
    if (isTest()) {
3!
1038
      return true; // ignore for tests
2✔
1039
    }
1040
    getFileAccess().mkdirs(this.userHomeIde);
×
1041
    Path licenseAgreement = this.userHomeIde.resolve(FILE_LICENSE_AGREEMENT);
×
1042
    if (Files.isRegularFile(licenseAgreement)) {
×
1043
      return true; // success, license already accepted
×
1044
    }
1045
    if (cmd instanceof EnvironmentCommandlet) {
×
1046
      // if the license was not accepted, "$(ideasy env --bash)" that is written into a variable prevents the user from seeing the question he is asked
1047
      // in such situation the user could not open a bash terminal anymore and gets blocked what would really annoy the user so we exit here without doing or
1048
      // printing anything anymore in such case.
1049
      return false;
×
1050
    }
1051
    boolean logLevelInfoDisabled = !this.startContext.info().isEnabled();
×
1052
    if (logLevelInfoDisabled) {
×
1053
      this.startContext.setLogLevel(IdeLogLevel.INFO, true);
×
1054
    }
1055
    boolean logLevelInteractionDisabled = !this.startContext.interaction().isEnabled();
×
1056
    if (logLevelInteractionDisabled) {
×
1057
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, true);
×
1058
    }
1059
    StringBuilder sb = new StringBuilder(1180);
×
1060
    sb.append(LOGO).append("""
×
1061
        Welcome to IDEasy!
1062
        This product (with its included 3rd party components) is open-source software and can be used free (also commercially).
1063
        It supports automatic download and installation of arbitrary 3rd party tools.
1064
        By default only open-source 3rd party tools are used (downloaded, installed, executed).
1065
        But if explicitly configured, also commercial software that requires an additional license may be used.
1066
        This happens e.g. if you configure "ultimate" edition of IntelliJ or "docker" edition of Docker (Docker Desktop).
1067
        You are solely responsible for all risks implied by using this software.
1068
        Before using IDEasy you need to read and accept the license agreement with all involved licenses.
1069
        You will be able to find it online under the following URL:
1070
        """).append(LICENSE_URL);
×
1071
    if (this.ideRoot != null) {
×
1072
      sb.append("\n\nAlso it is included in the documentation that you can find here:\n").
×
1073
          append(getIdePath().resolve("IDEasy.pdf").toString()).append("\n");
×
1074
    }
1075
    info(sb.toString());
×
1076
    askToContinue("Do you accept these terms of use and all license agreements?");
×
1077

1078
    sb.setLength(0);
×
1079
    LocalDateTime now = LocalDateTime.now();
×
1080
    sb.append("On ").append(DateTimeUtil.formatDate(now, false)).append(" at ").append(DateTimeUtil.formatTime(now))
×
1081
        .append(" you accepted the IDEasy license.\n").append(LICENSE_URL);
×
1082
    try {
1083
      Files.writeString(licenseAgreement, sb);
×
1084
    } catch (Exception e) {
×
1085
      throw new RuntimeException("Failed to save license agreement!", e);
×
1086
    }
×
1087
    if (logLevelInfoDisabled) {
×
1088
      this.startContext.setLogLevel(IdeLogLevel.INFO, false);
×
1089
    }
1090
    if (logLevelInteractionDisabled) {
×
1091
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, false);
×
1092
    }
1093
    return true;
×
1094
  }
1095

1096
  @Override
1097
  public void verifyIdeMinVersion(boolean throwException) {
1098
    VersionIdentifier minVersion = IDE_MIN_VERSION.get(this);
5✔
1099
    if (minVersion == null) {
2✔
1100
      return;
1✔
1101
    }
1102
    if (IdeVersion.getVersionIdentifier().compareVersion(minVersion).isLess()) {
5✔
1103
      String message = String.format("Your version of IDEasy is currently %s\n"
7✔
1104
          + "However, this is too old as your project requires at latest version %s\n"
1105
          + "Please run the following command to update to the latest version of IDEasy and fix the problem:\n"
1106
          + "ide upgrade", IdeVersion.getVersionIdentifier().toString(), minVersion.toString());
8✔
1107
      if (throwException) {
2✔
1108
        throw new CliException(message);
5✔
1109
      } else {
1110
        warning(message);
3✔
1111
      }
1112
    }
1113
  }
1✔
1114

1115
  /**
1116
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
1117
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
1118
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
1119
   */
1120
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
1121

1122
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
1123
    if (arguments.current().isStart()) {
4✔
1124
      arguments.next();
3✔
1125
    }
1126
    if (includeContextOptions) {
2✔
1127
      ContextCommandlet cc = new ContextCommandlet();
4✔
1128
      for (Property<?> property : cc.getProperties()) {
11✔
1129
        assert (property.isOption());
4!
1130
        property.apply(arguments, this, cc, collector);
7✔
1131
      }
1✔
1132
    }
1133
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, collector);
6✔
1134
    CliArgument current = arguments.current();
3✔
1135
    if (current.isCompletion() && current.isCombinedShortOption()) {
6✔
1136
      collector.add(current.get(), null, null, null);
7✔
1137
    }
1138
    arguments.next();
3✔
1139
    while (commandletIterator.hasNext()) {
3✔
1140
      Commandlet cmd = commandletIterator.next();
4✔
1141
      if (!arguments.current().isEnd()) {
4✔
1142
        completeCommandlet(arguments.copy(), cmd, collector);
6✔
1143
      }
1144
    }
1✔
1145
    return collector.getSortedCandidates();
3✔
1146
  }
1147

1148
  private void completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
1149

1150
    trace("Trying to match arguments for auto-completion for commandlet {}", cmd.getName());
10✔
1151
    Iterator<Property<?>> valueIterator = cmd.getValues().iterator();
4✔
1152
    valueIterator.next(); // skip first property since this is the keyword property that already matched to find the commandlet
3✔
1153
    List<Property<?>> properties = cmd.getProperties();
3✔
1154
    // we are creating our own list of options and remove them when matched to avoid duplicate suggestions
1155
    List<Property<?>> optionProperties = new ArrayList<>(properties.size());
6✔
1156
    for (Property<?> property : properties) {
10✔
1157
      if (property.isOption()) {
3✔
1158
        optionProperties.add(property);
4✔
1159
      }
1160
    }
1✔
1161
    CliArgument currentArgument = arguments.current();
3✔
1162
    while (!currentArgument.isEnd()) {
3✔
1163
      trace("Trying to match argument '{}'", currentArgument);
9✔
1164
      if (currentArgument.isOption() && !arguments.isEndOptions()) {
6!
1165
        if (currentArgument.isCompletion()) {
3✔
1166
          Iterator<Property<?>> optionIterator = optionProperties.iterator();
3✔
1167
          while (optionIterator.hasNext()) {
3✔
1168
            Property<?> option = optionIterator.next();
4✔
1169
            boolean success = option.apply(arguments, this, cmd, collector);
7✔
1170
            if (success) {
2✔
1171
              optionIterator.remove();
2✔
1172
              arguments.next();
3✔
1173
            }
1174
          }
1✔
1175
        } else {
1✔
1176
          Property<?> option = cmd.getOption(currentArgument.get());
5✔
1177
          if (option != null) {
2✔
1178
            arguments.next();
3✔
1179
            boolean removed = optionProperties.remove(option);
4✔
1180
            if (!removed) {
2!
1181
              option = null;
×
1182
            }
1183
          }
1184
          if (option == null) {
2✔
1185
            trace("No such option was found.");
3✔
1186
            return;
1✔
1187
          }
1188
        }
1✔
1189
      } else {
1190
        if (valueIterator.hasNext()) {
3✔
1191
          Property<?> valueProperty = valueIterator.next();
4✔
1192
          boolean success = valueProperty.apply(arguments, this, cmd, collector);
7✔
1193
          if (!success) {
2✔
1194
            trace("Completion cannot match any further.");
3✔
1195
            return;
1✔
1196
          }
1197
        } else {
1✔
1198
          trace("No value left for completion.");
3✔
1199
          return;
1✔
1200
        }
1201
      }
1202
      currentArgument = arguments.current();
4✔
1203
    }
1204
  }
1✔
1205

1206
  /**
1207
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
1208
   *     {@link CliArguments#copy() copy} as needed.
1209
   * @param cmd the potential {@link Commandlet} to match.
1210
   * @return the {@link ValidationResult} telling if the {@link CliArguments} can be applied successfully or if validation errors ocurred.
1211
   */
1212
  public ValidationResult apply(CliArguments arguments, Commandlet cmd) {
1213

1214
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
1215
    CliArgument currentArgument = arguments.current();
3✔
1216
    Iterator<Property<?>> propertyIterator = cmd.getValues().iterator();
4✔
1217
    Property<?> property = null;
2✔
1218
    if (propertyIterator.hasNext()) {
3!
1219
      property = propertyIterator.next();
4✔
1220
    }
1221
    while (!currentArgument.isEnd()) {
3✔
1222
      trace("Trying to match argument '{}'", currentArgument);
9✔
1223
      Property<?> currentProperty = property;
2✔
1224
      if (!arguments.isEndOptions()) {
3!
1225
        Property<?> option = cmd.getOption(currentArgument.getKey());
5✔
1226
        if (option != null) {
2!
1227
          currentProperty = option;
×
1228
        }
1229
      }
1230
      if (currentProperty == null) {
2!
1231
        trace("No option or next value found");
×
1232
        ValidationState state = new ValidationState(null);
×
1233
        state.addErrorMessage("No matching property found");
×
1234
        return state;
×
1235
      }
1236
      trace("Next property candidate to match argument is {}", currentProperty);
9✔
1237
      if (currentProperty == property) {
3!
1238
        if (!property.isMultiValued()) {
3✔
1239
          if (propertyIterator.hasNext()) {
3✔
1240
            property = propertyIterator.next();
5✔
1241
          } else {
1242
            property = null;
2✔
1243
          }
1244
        }
1245
        if ((property != null) && property.isValue() && property.isMultiValued()) {
8!
1246
          arguments.stopSplitShortOptions();
2✔
1247
        }
1248
      }
1249
      boolean matches = currentProperty.apply(arguments, this, cmd, null);
7✔
1250
      if (!matches) {
2!
1251
        ValidationState state = new ValidationState(null);
×
1252
        state.addErrorMessage("No matching property found");
×
1253
        return state;
×
1254
      }
1255
      currentArgument = arguments.current();
3✔
1256
    }
1✔
1257
    return ValidationResultValid.get();
2✔
1258
  }
1259

1260
  @Override
1261
  public String findBash() {
1262

1263
    String bash = "bash";
2✔
1264
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1265
      bash = findBashOnWindows();
×
1266
    }
1267

1268
    return bash;
2✔
1269
  }
1270

1271
  private String findBashOnWindows() {
1272

1273
    // Check if Git Bash exists in the default location
1274
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1275
    if (Files.exists(defaultPath)) {
×
1276
      return defaultPath.toString();
×
1277
    }
1278

1279
    // If not found in the default location, try the registry query
1280
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1281
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1282
    String regQueryResult;
1283
    for (String bashVariant : bashVariants) {
×
1284
      for (String registryKey : registryKeys) {
×
1285
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1286
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1287

1288
        try {
1289
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1290
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1291
            StringBuilder output = new StringBuilder();
×
1292
            String line;
1293

1294
            while ((line = reader.readLine()) != null) {
×
1295
              output.append(line);
×
1296
            }
1297

1298
            int exitCode = process.waitFor();
×
1299
            if (exitCode != 0) {
×
1300
              return null;
×
1301
            }
1302

1303
            regQueryResult = output.toString();
×
1304
            if (regQueryResult != null) {
×
1305
              int index = regQueryResult.indexOf("REG_SZ");
×
1306
              if (index != -1) {
×
1307
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1308
                return path + "\\bin\\bash.exe";
×
1309
              }
1310
            }
1311

1312
          }
×
1313
        } catch (Exception e) {
×
1314
          return null;
×
1315
        }
×
1316
      }
1317
    }
1318
    // no bash found
1319
    return null;
×
1320
  }
1321

1322
  @Override
1323
  public WindowsPathSyntax getPathSyntax() {
1324

1325
    return this.pathSyntax;
3✔
1326
  }
1327

1328
  /**
1329
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1330
   */
1331
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1332

1333
    this.pathSyntax = pathSyntax;
3✔
1334
  }
1✔
1335

1336
  /**
1337
   * @return the {@link IdeStartContextImpl}.
1338
   */
1339
  public IdeStartContextImpl getStartContext() {
1340

1341
    return startContext;
3✔
1342
  }
1343

1344
  /**
1345
   * @return the {@link WindowsHelper}.
1346
   */
1347
  public final WindowsHelper getWindowsHelper() {
1348

1349
    if (this.windowsHelper == null) {
3✔
1350
      this.windowsHelper = createWindowsHelper();
4✔
1351
    }
1352
    return this.windowsHelper;
3✔
1353
  }
1354

1355
  /**
1356
   * @return the new {@link WindowsHelper} instance.
1357
   */
1358
  protected WindowsHelper createWindowsHelper() {
1359

1360
    return new WindowsHelperImpl(this);
×
1361
  }
1362

1363
  /**
1364
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1365
   */
1366
  public void reload() {
1367

1368
    this.variables = null;
3✔
1369
    this.customToolRepository = null;
3✔
1370
  }
1✔
1371

1372
  @Override
1373
  public void writeVersionFile(VersionIdentifier version, Path installationPath) {
1374

1375
    assert (Files.isDirectory(installationPath));
6!
1376
    Path versionFile = installationPath.resolve(FILE_SOFTWARE_VERSION);
4✔
1377
    getFileAccess().writeFileContent(version.toString(), versionFile);
6✔
1378
  }
1✔
1379

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