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

devonfw / IDEasy / 15438718610

04 Jun 2025 09:27AM UTC coverage: 67.693% (-0.05%) from 67.742%
15438718610

Pull #1350

github

web-flow
Merge eceb95195 into ad516c95b
Pull Request #1350: #1340: proper fix if IDE_ROOT is not sane

3165 of 5080 branches covered (62.3%)

Branch coverage included in aggregate %.

8116 of 11585 relevant lines covered (70.06%)

3.07 hits per line

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

61.23
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
    this.cwd = workingDirectory;
3✔
163
    // detect IDE_HOME and WORKSPACE
164
    Path currentDir = workingDirectory;
2✔
165
    String name1 = "";
2✔
166
    String name2 = "";
2✔
167
    Path ideRootPath = getIdeRootPathFromEnv(false);
4✔
168
    while (currentDir != null) {
2✔
169
      trace("Looking for IDE_HOME in {}", currentDir);
9✔
170
      if (isIdeHome(currentDir)) {
4✔
171
        if (FOLDER_WORKSPACES.equals(name1) && !name2.isEmpty()) {
7✔
172
          workspace = name2;
3✔
173
        }
174
        break;
175
      }
176
      name2 = name1;
2✔
177
      int nameCount = currentDir.getNameCount();
3✔
178
      if (nameCount >= 1) {
3✔
179
        name1 = currentDir.getName(nameCount - 1).toString();
7✔
180
      }
181
      currentDir = currentDir.getParent();
3✔
182
      if ((ideRootPath != null) && (ideRootPath.equals(currentDir))) {
2!
183
        // prevent that during tests we traverse to the real IDE project of IDEasy developer
184
        currentDir = null;
×
185
      }
186
    }
1✔
187

188
    // detection completed, initializing variables
189
    this.ideRoot = findIdeRoot(currentDir);
5✔
190

191
    setCwd(workingDirectory, workspace, currentDir);
5✔
192

193
    if (this.ideRoot != null) {
3✔
194
      Path tempDownloadPath = getTempDownloadPath();
3✔
195
      if (Files.isDirectory(tempDownloadPath)) {
6✔
196
        // TODO delete all files older than 1 day here...
197
      } else {
198
        this.fileAccess.mkdirs(tempDownloadPath);
4✔
199
      }
200
    }
201

202
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
203
    this.mavenRepository = new MavenRepository(this);
6✔
204
  }
1✔
205

206
  private Path findIdeRoot(Path ideHomePath) {
207

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

220
    } else if (!isTest()) {
4!
221
      ideRootPath = getIdeRootPathFromEnv(true);
×
222
    }
223
    return ideRootPath;
2✔
224
  }
225

226
  /**
227
   * @return the {@link #getIdeRoot() IDE_ROOT} from the system environment.
228
   */
229
  protected Path getIdeRootPathFromEnv(boolean withSanityCheck) {
230

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

262
  @Override
263
  public void setCwd(Path userDir, String workspace, Path ideHome) {
264

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

293
    this.path = computeSystemPath();
4✔
294
  }
1✔
295

296
  private String getMessageIdeHomeFound() {
297

298
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
7✔
299
  }
300

301
  private String getMessageNotInsideIdeProject() {
302

303
    return "You are not inside an IDE project: " + this.cwd;
5✔
304
  }
305

306
  private String getMessageIdeRootNotFound() {
307

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

316
  /**
317
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
318
   */
319
  public boolean isTest() {
320

321
    return false;
×
322
  }
323

324
  protected SystemPath computeSystemPath() {
325

326
    return new SystemPath(this);
×
327
  }
328

329
  private boolean isIdeHome(Path dir) {
330

331
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
332
      return false;
2✔
333
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
334
      return false;
×
335
    }
336
    return true;
2✔
337
  }
338

339
  private EnvironmentVariables createVariables() {
340

341
    AbstractEnvironmentVariables system = createSystemVariables();
3✔
342
    AbstractEnvironmentVariables user = system.extend(this.userHomeIde, EnvironmentVariablesType.USER);
6✔
343
    AbstractEnvironmentVariables settings = user.extend(this.settingsPath, EnvironmentVariablesType.SETTINGS);
6✔
344
    AbstractEnvironmentVariables workspace = settings.extend(this.workspacePath, EnvironmentVariablesType.WORKSPACE);
6✔
345
    AbstractEnvironmentVariables conf = workspace.extend(this.confPath, EnvironmentVariablesType.CONF);
6✔
346
    return conf.resolved();
3✔
347
  }
348

349
  protected AbstractEnvironmentVariables createSystemVariables() {
350

351
    return EnvironmentVariables.ofSystem(this);
3✔
352
  }
353

354
  @Override
355
  public SystemInfo getSystemInfo() {
356

357
    return this.systemInfo;
3✔
358
  }
359

360
  @Override
361
  public FileAccess getFileAccess() {
362

363
    // currently FileAccess contains download method and requires network proxy to be configured. Maybe download should be moved to its own interface/class
364
    configureNetworkProxy();
2✔
365
    return this.fileAccess;
3✔
366
  }
367

368
  @Override
369
  public CommandletManager getCommandletManager() {
370

371
    return this.commandletManager;
3✔
372
  }
373

374
  @Override
375
  public ToolRepository getDefaultToolRepository() {
376

377
    return this.defaultToolRepository;
3✔
378
  }
379

380
  @Override
381
  public MavenRepository getMavenToolRepository() {
382

383
    return this.mavenRepository;
3✔
384
  }
385

386
  @Override
387
  public CustomToolRepository getCustomToolRepository() {
388

389
    if (this.customToolRepository == null) {
3!
390
      this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
391
    }
392
    return this.customToolRepository;
3✔
393
  }
394

395
  @Override
396
  public Path getIdeHome() {
397

398
    return this.ideHome;
3✔
399
  }
400

401
  @Override
402
  public String getProjectName() {
403

404
    if (this.ideHome != null) {
3!
405
      return this.ideHome.getFileName().toString();
5✔
406
    }
407
    return "";
×
408
  }
409

410
  @Override
411
  public VersionIdentifier getProjectVersion() {
412

413
    if (this.ideHome != null) {
3!
414
      Path versionFile = this.ideHome.resolve(IdeContext.FILE_SOFTWARE_VERSION);
5✔
415
      if (Files.exists(versionFile)) {
5✔
416
        String version = this.fileAccess.readFileContent(versionFile).trim();
6✔
417
        return VersionIdentifier.of(version);
3✔
418
      }
419
    }
420
    return IdeMigrator.START_VERSION;
2✔
421
  }
422

423
  @Override
424
  public void setProjectVersion(VersionIdentifier version) {
425

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

434
  @Override
435
  public Path getIdeRoot() {
436

437
    return this.ideRoot;
3✔
438
  }
439

440
  @Override
441
  public Path getIdePath() {
442

443
    Path myIdeRoot = getIdeRoot();
3✔
444
    if (myIdeRoot == null) {
2!
445
      return null;
×
446
    }
447
    return myIdeRoot.resolve(FOLDER_UNDERSCORE_IDE);
4✔
448
  }
449

450
  @Override
451
  public Path getCwd() {
452

453
    return this.cwd;
3✔
454
  }
455

456
  @Override
457
  public Path getTempPath() {
458

459
    Path idePath = getIdePath();
3✔
460
    if (idePath == null) {
2!
461
      return null;
×
462
    }
463
    return idePath.resolve("tmp");
4✔
464
  }
465

466
  @Override
467
  public Path getTempDownloadPath() {
468

469
    Path tmp = getTempPath();
3✔
470
    if (tmp == null) {
2!
471
      return null;
×
472
    }
473
    return tmp.resolve(FOLDER_DOWNLOADS);
4✔
474
  }
475

476
  @Override
477
  public Path getUserHome() {
478

479
    return this.userHome;
3✔
480
  }
481

482
  @Override
483
  public Path getUserHomeIde() {
484

485
    return this.userHomeIde;
3✔
486
  }
487

488
  @Override
489
  public Path getSettingsPath() {
490

491
    return this.settingsPath;
3✔
492
  }
493

494
  @Override
495
  public Path getSettingsGitRepository() {
496

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

506
  @Override
507
  public boolean isSettingsRepositorySymlinkOrJunction() {
508

509
    Path settingsPath = getSettingsPath();
3✔
510
    if (settingsPath == null) {
2!
511
      return false;
×
512
    }
513
    return Files.isSymbolicLink(settingsPath) || getFileAccess().isJunction(settingsPath);
10!
514
  }
515

516
  @Override
517
  public Path getSettingsCommitIdPath() {
518

519
    return this.settingsCommitIdPath;
3✔
520
  }
521

522
  @Override
523
  public Path getConfPath() {
524

525
    return this.confPath;
3✔
526
  }
527

528
  @Override
529
  public Path getSoftwarePath() {
530

531
    if (this.ideHome == null) {
3✔
532
      return null;
2✔
533
    }
534
    return this.ideHome.resolve(FOLDER_SOFTWARE);
5✔
535
  }
536

537
  @Override
538
  public Path getSoftwareExtraPath() {
539

540
    Path softwarePath = getSoftwarePath();
3✔
541
    if (softwarePath == null) {
2!
542
      return null;
×
543
    }
544
    return softwarePath.resolve(FOLDER_EXTRA);
4✔
545
  }
546

547
  @Override
548
  public Path getSoftwareRepositoryPath() {
549

550
    Path idePath = getIdePath();
3✔
551
    if (idePath == null) {
2!
552
      return null;
×
553
    }
554
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
555
  }
556

557
  @Override
558
  public Path getPluginsPath() {
559

560
    return this.pluginsPath;
3✔
561
  }
562

563
  @Override
564
  public String getWorkspaceName() {
565

566
    return this.workspaceName;
3✔
567
  }
568

569
  @Override
570
  public Path getWorkspacePath() {
571

572
    return this.workspacePath;
3✔
573
  }
574

575
  @Override
576
  public Path getDownloadPath() {
577

578
    return this.downloadPath;
3✔
579
  }
580

581
  @Override
582
  public Path getUrlsPath() {
583

584
    Path idePath = getIdePath();
3✔
585
    if (idePath == null) {
2!
586
      return null;
×
587
    }
588
    return idePath.resolve(FOLDER_URLS);
4✔
589
  }
590

591
  @Override
592
  public Path getToolRepositoryPath() {
593

594
    Path idePath = getIdePath();
3✔
595
    if (idePath == null) {
2!
596
      return null;
×
597
    }
598
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
599
  }
600

601
  @Override
602
  public SystemPath getPath() {
603

604
    return this.path;
3✔
605
  }
606

607
  @Override
608
  public EnvironmentVariables getVariables() {
609

610
    if (this.variables == null) {
3✔
611
      this.variables = createVariables();
4✔
612
    }
613
    return this.variables;
3✔
614
  }
615

616
  @Override
617
  public UrlMetadata getUrls() {
618

619
    if (this.urlMetadata == null) {
3✔
620
      if (!isTest()) {
3!
621
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, getUrlsPath(), null);
×
622
      }
623
      this.urlMetadata = new UrlMetadata(this);
6✔
624
    }
625
    return this.urlMetadata;
3✔
626
  }
627

628
  @Override
629
  public boolean isQuietMode() {
630

631
    return this.startContext.isQuietMode();
4✔
632
  }
633

634
  @Override
635
  public boolean isBatchMode() {
636

637
    return this.startContext.isBatchMode();
4✔
638
  }
639

640
  @Override
641
  public boolean isForceMode() {
642

643
    return this.startContext.isForceMode();
4✔
644
  }
645

646
  @Override
647
  public boolean isForcePull() {
648

649
    return this.startContext.isForcePull();
×
650
  }
651

652
  @Override
653
  public boolean isForcePlugins() {
654

655
    return this.startContext.isForcePlugins();
×
656
  }
657

658
  @Override
659
  public boolean isForceRepositories() {
660

661
    return this.startContext.isForceRepositories();
×
662
  }
663

664
  @Override
665
  public boolean isOfflineMode() {
666

667
    return this.startContext.isOfflineMode();
4✔
668
  }
669

670
  @Override
671
  public boolean isSkipUpdatesMode() {
672

673
    return this.startContext.isSkipUpdatesMode();
4✔
674
  }
675

676
  @Override
677
  public boolean isOnline() {
678

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

698
  private void configureNetworkProxy() {
699

700
    if (this.networkProxy == null) {
3✔
701
      this.networkProxy = new NetworkProxy(this);
6✔
702
      this.networkProxy.configure();
3✔
703
    }
704
  }
1✔
705

706
  @Override
707
  public Locale getLocale() {
708

709
    Locale locale = this.startContext.getLocale();
4✔
710
    if (locale == null) {
2✔
711
      locale = Locale.getDefault();
2✔
712
    }
713
    return locale;
2✔
714
  }
715

716
  @Override
717
  public DirectoryMerger getWorkspaceMerger() {
718

719
    if (this.workspaceMerger == null) {
3✔
720
      this.workspaceMerger = new DirectoryMerger(this);
6✔
721
    }
722
    return this.workspaceMerger;
3✔
723
  }
724

725
  /**
726
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
727
   */
728
  @Override
729
  public Path getDefaultExecutionDirectory() {
730

731
    return this.defaultExecutionDirectory;
×
732
  }
733

734
  /**
735
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
736
   */
737
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
738

739
    if (defaultExecutionDirectory != null) {
×
740
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
741
    }
742
  }
×
743

744
  @Override
745
  public GitContext getGitContext() {
746

747
    return new GitContextImpl(this);
×
748
  }
749

750
  @Override
751
  public ProcessContext newProcess() {
752

753
    ProcessContext processContext = createProcessContext();
3✔
754
    if (this.defaultExecutionDirectory != null) {
3!
755
      processContext.directory(this.defaultExecutionDirectory);
×
756
    }
757
    return processContext;
2✔
758
  }
759

760
  @Override
761
  public IdeSystem getSystem() {
762

763
    if (this.system == null) {
×
764
      this.system = new IdeSystemImpl(this);
×
765
    }
766
    return this.system;
×
767
  }
768

769
  /**
770
   * @return a new instance of {@link ProcessContext}.
771
   * @see #newProcess()
772
   */
773
  protected ProcessContext createProcessContext() {
774

775
    return new ProcessContextImpl(this);
5✔
776
  }
777

778
  @Override
779
  public IdeSubLogger level(IdeLogLevel level) {
780

781
    return this.startContext.level(level);
5✔
782
  }
783

784
  @Override
785
  public void logIdeHomeAndRootStatus() {
786

787
    if (this.ideRoot != null) {
3!
788
      success("IDE_ROOT is set to {}", this.ideRoot);
×
789
    }
790
    if (this.ideHome == null) {
3!
791
      warning(getMessageNotInsideIdeProject());
5✔
792
    } else {
793
      success("IDE_HOME is set to {}", this.ideHome);
×
794
    }
795
  }
1✔
796

797
  @Override
798
  public String askForInput(String message, String defaultValue) {
799

800
    if (!message.isBlank()) {
×
801
      info(message);
×
802
    }
803
    if (isBatchMode()) {
×
804
      if (isForceMode() || isForcePull()) {
×
805
        return defaultValue;
×
806
      } else {
807
        throw new CliAbortException();
×
808
      }
809
    }
810
    String input = readLine().trim();
×
811
    return input.isEmpty() ? defaultValue : input;
×
812
  }
813

814
  @Override
815
  public String askForInput(String message) {
816

817
    String input;
818
    do {
819
      info(message);
3✔
820
      input = readLine().trim();
4✔
821
    } while (input.isEmpty());
3!
822

823
    return input;
2✔
824
  }
825

826
  @SuppressWarnings("unchecked")
827
  @Override
828
  public <O> O question(String question, O... options) {
829

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

864
  /**
865
   * @return the input from the end-user (e.g. read from the console).
866
   */
867
  protected abstract String readLine();
868

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

871
    O duplicate = mapping.put(key, option);
×
872
    if (duplicate != null) {
×
873
      throw new IllegalArgumentException("Duplicated option " + key);
×
874
    }
875
  }
×
876

877
  @Override
878
  public Step getCurrentStep() {
879

880
    return this.currentStep;
×
881
  }
882

883
  @Override
884
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
885

886
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
887
    return this.currentStep;
3✔
888
  }
889

890
  /**
891
   * Internal method to end the running {@link Step}.
892
   *
893
   * @param step the current {@link Step} to end.
894
   */
895
  public void endStep(StepImpl step) {
896

897
    if (step == this.currentStep) {
4!
898
      this.currentStep = this.currentStep.getParent();
6✔
899
    } else {
900
      String currentStepName = "null";
×
901
      if (this.currentStep != null) {
×
902
        currentStepName = this.currentStep.getName();
×
903
      }
904
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
905
    }
906
  }
1✔
907

908
  /**
909
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
910
   *
911
   * @param arguments the {@link CliArgument}.
912
   * @return the return code of the execution.
913
   */
914
  public int run(CliArguments arguments) {
915

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

956
  @Override
957
  public void runWithoutLogging(Runnable lambda, IdeLogLevel threshold) {
958

959
    this.startContext.deactivateLogging(threshold);
4✔
960
    lambda.run();
2✔
961
    this.startContext.activateLogging();
3✔
962
  }
1✔
963

964
  /**
965
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet) apply} and {@link Commandlet#run() run}.
966
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
967
   *     {@link Commandlet} did not match and we have to try a different candidate).
968
   */
969
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
970

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

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

1028
  private boolean ensureLicenseAgreement(Commandlet cmd) {
1029

1030
    if (isTest()) {
3!
1031
      return true; // ignore for tests
2✔
1032
    }
1033
    getFileAccess().mkdirs(this.userHomeIde);
×
1034
    Path licenseAgreement = this.userHomeIde.resolve(FILE_LICENSE_AGREEMENT);
×
1035
    if (Files.isRegularFile(licenseAgreement)) {
×
1036
      return true; // success, license already accepted
×
1037
    }
1038
    if (cmd instanceof EnvironmentCommandlet) {
×
1039
      // 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
1040
      // 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
1041
      // printing anything anymore in such case.
1042
      return false;
×
1043
    }
1044
    boolean logLevelInfoDisabled = !this.startContext.info().isEnabled();
×
1045
    if (logLevelInfoDisabled) {
×
1046
      this.startContext.setLogLevel(IdeLogLevel.INFO, true);
×
1047
    }
1048
    boolean logLevelInteractionDisabled = !this.startContext.interaction().isEnabled();
×
1049
    if (logLevelInteractionDisabled) {
×
1050
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, true);
×
1051
    }
1052
    StringBuilder sb = new StringBuilder(1180);
×
1053
    sb.append(LOGO).append("""
×
1054
        Welcome to IDEasy!
1055
        This product (with its included 3rd party components) is open-source software and can be used free (also commercially).
1056
        It supports automatic download and installation of arbitrary 3rd party tools.
1057
        By default only open-source 3rd party tools are used (downloaded, installed, executed).
1058
        But if explicitly configured, also commercial software that requires an additional license may be used.
1059
        This happens e.g. if you configure "ultimate" edition of IntelliJ or "docker" edition of Docker (Docker Desktop).
1060
        You are solely responsible for all risks implied by using this software.
1061
        Before using IDEasy you need to read and accept the license agreement with all involved licenses.
1062
        You will be able to find it online under the following URL:
1063
        """).append(LICENSE_URL);
×
1064
    if (this.ideRoot != null) {
×
1065
      sb.append("\n\nAlso it is included in the documentation that you can find here:\n").
×
1066
          append(getIdePath().resolve("IDEasy.pdf").toString()).append("\n");
×
1067
    }
1068
    info(sb.toString());
×
1069
    askToContinue("Do you accept these terms of use and all license agreements?");
×
1070

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

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

1108
  /**
1109
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
1110
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
1111
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
1112
   */
1113
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
1114

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

1141
  private void completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
1142

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

1199
  /**
1200
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
1201
   *     {@link CliArguments#copy() copy} as needed.
1202
   * @param cmd the potential {@link Commandlet} to match.
1203
   * @return the {@link ValidationResult} telling if the {@link CliArguments} can be applied successfully or if validation errors ocurred.
1204
   */
1205
  public ValidationResult apply(CliArguments arguments, Commandlet cmd) {
1206

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

1253
  @Override
1254
  public String findBash() {
1255

1256
    String bash = "bash";
2✔
1257
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1258
      bash = findBashOnWindows();
×
1259
    }
1260

1261
    return bash;
2✔
1262
  }
1263

1264
  private String findBashOnWindows() {
1265

1266
    // Check if Git Bash exists in the default location
1267
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1268
    if (Files.exists(defaultPath)) {
×
1269
      return defaultPath.toString();
×
1270
    }
1271

1272
    // If not found in the default location, try the registry query
1273
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1274
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1275
    String regQueryResult;
1276
    for (String bashVariant : bashVariants) {
×
1277
      for (String registryKey : registryKeys) {
×
1278
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1279
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1280

1281
        try {
1282
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1283
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1284
            StringBuilder output = new StringBuilder();
×
1285
            String line;
1286

1287
            while ((line = reader.readLine()) != null) {
×
1288
              output.append(line);
×
1289
            }
1290

1291
            int exitCode = process.waitFor();
×
1292
            if (exitCode != 0) {
×
1293
              return null;
×
1294
            }
1295

1296
            regQueryResult = output.toString();
×
1297
            if (regQueryResult != null) {
×
1298
              int index = regQueryResult.indexOf("REG_SZ");
×
1299
              if (index != -1) {
×
1300
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1301
                return path + "\\bin\\bash.exe";
×
1302
              }
1303
            }
1304

1305
          }
×
1306
        } catch (Exception e) {
×
1307
          return null;
×
1308
        }
×
1309
      }
1310
    }
1311
    // no bash found
1312
    return null;
×
1313
  }
1314

1315
  @Override
1316
  public WindowsPathSyntax getPathSyntax() {
1317

1318
    return this.pathSyntax;
3✔
1319
  }
1320

1321
  /**
1322
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1323
   */
1324
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1325

1326
    this.pathSyntax = pathSyntax;
3✔
1327
  }
1✔
1328

1329
  /**
1330
   * @return the {@link IdeStartContextImpl}.
1331
   */
1332
  public IdeStartContextImpl getStartContext() {
1333

1334
    return startContext;
3✔
1335
  }
1336

1337
  /**
1338
   * @return the {@link WindowsHelper}.
1339
   */
1340
  public final WindowsHelper getWindowsHelper() {
1341

1342
    if (this.windowsHelper == null) {
3✔
1343
      this.windowsHelper = createWindowsHelper();
4✔
1344
    }
1345
    return this.windowsHelper;
3✔
1346
  }
1347

1348
  /**
1349
   * @return the new {@link WindowsHelper} instance.
1350
   */
1351
  protected WindowsHelper createWindowsHelper() {
1352

1353
    return new WindowsHelperImpl(this);
×
1354
  }
1355

1356
  /**
1357
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1358
   */
1359
  public void reload() {
1360

1361
    this.variables = null;
3✔
1362
    this.customToolRepository = null;
3✔
1363
  }
1✔
1364

1365
  @Override
1366
  public void writeVersionFile(VersionIdentifier version, Path installationPath) {
1367

1368
    assert (Files.isDirectory(installationPath));
6!
1369
    Path versionFile = installationPath.resolve(FILE_SOFTWARE_VERSION);
4✔
1370
    getFileAccess().writeFileContent(version.toString(), versionFile);
6✔
1371
  }
1✔
1372

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