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

devonfw / IDEasy / 15636444931

13 Jun 2025 02:03PM UTC coverage: 68.064% (+0.3%) from 67.746%
15636444931

Pull #1370

github

web-flow
Merge ff8133b6f into 7be183ce9
Pull Request #1370: #1369: Omit username from StatusCommandlet

3186 of 5084 branches covered (62.67%)

Branch coverage included in aggregate %.

8163 of 11590 relevant lines covered (70.43%)

3.09 hits per line

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

62.27
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.util.PrivacyUtil;
69
import com.devonfw.tools.ide.validation.ValidationResult;
70
import com.devonfw.tools.ide.validation.ValidationResultValid;
71
import com.devonfw.tools.ide.validation.ValidationState;
72
import com.devonfw.tools.ide.variable.IdeVariables;
73
import com.devonfw.tools.ide.version.IdeVersion;
74
import com.devonfw.tools.ide.version.VersionIdentifier;
75

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

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

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

85
  private final IdeStartContextImpl startContext;
86

87
  private Path ideHome;
88

89
  private final Path ideRoot;
90

91
  private Path confPath;
92

93
  protected Path settingsPath;
94

95
  private Path settingsCommitIdPath;
96

97
  protected Path pluginsPath;
98

99
  private Path workspacePath;
100

101
  private String workspaceName;
102

103
  private Path cwd;
104

105
  private Path downloadPath;
106

107
  protected Path userHome;
108

109
  private Path userHomeIde;
110

111
  private SystemPath path;
112

113
  private WindowsPathSyntax pathSyntax;
114

115
  private final SystemInfo systemInfo;
116

117
  private EnvironmentVariables variables;
118

119
  private final FileAccess fileAccess;
120

121
  protected CommandletManager commandletManager;
122

123
  protected ToolRepository defaultToolRepository;
124

125
  private CustomToolRepository customToolRepository;
126

127
  private MavenRepository mavenRepository;
128

129
  private DirectoryMerger workspaceMerger;
130

131
  protected UrlMetadata urlMetadata;
132

133
  protected Path defaultExecutionDirectory;
134

135
  private StepImpl currentStep;
136

137
  protected Boolean online;
138

139
  protected IdeSystem system;
140

141
  private NetworkProxy networkProxy;
142

143
  private WindowsHelper windowsHelper;
144

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

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

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

193
    setCwd(workingDirectory, workspace, currentDir);
5✔
194

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

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

208
  private Path findIdeRoot(Path ideHomePath) {
209

210
    Path ideRootPath = null;
2✔
211
    if (ideHomePath != null) {
2✔
212
      ideRootPath = ideHomePath.getParent();
4✔
213
    } else if (!isTest()) {
3!
214
      ideRootPath = getIdeRootPathFromEnv();
×
215
    }
216
    return ideRootPath;
2✔
217
  }
218

219
  /**
220
   * @return the {@link #getIdeRoot() IDE_ROOT} from the system environment.
221
   */
222
  protected Path getIdeRootPathFromEnv() {
223

224
    String root = getSystem().getEnv(IdeVariables.IDE_ROOT.getName());
×
225
    if (root != null) {
×
226
      Path rootPath = Path.of(root);
×
227
      if (Files.isDirectory(rootPath)) {
×
228
        return rootPath;
×
229
      }
230
    }
231
    return null;
×
232
  }
233

234
  @Override
235
  public void setCwd(Path userDir, String workspace, Path ideHome) {
236

237
    this.cwd = userDir;
3✔
238
    this.workspaceName = workspace;
3✔
239
    this.ideHome = ideHome;
3✔
240
    if (ideHome == null) {
2✔
241
      this.workspacePath = null;
3✔
242
      this.confPath = null;
3✔
243
      this.settingsPath = null;
3✔
244
      this.pluginsPath = null;
4✔
245
    } else {
246
      this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName);
9✔
247
      this.confPath = this.ideHome.resolve(FOLDER_CONF);
6✔
248
      this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS);
6✔
249
      this.settingsCommitIdPath = this.ideHome.resolve(IdeContext.SETTINGS_COMMIT_ID);
6✔
250
      this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS);
6✔
251
    }
252
    if (isTest()) {
3!
253
      // only for testing...
254
      if (this.ideHome == null) {
3✔
255
        this.userHome = Path.of("/non-existing-user-home-for-testing");
7✔
256
      } else {
257
        this.userHome = this.ideHome.resolve("home");
7✔
258
      }
259
    } else {
260
      this.userHome = Path.of(getSystem().getProperty("user.home"));
×
261
    }
262
    this.userHomeIde = this.userHome.resolve(FOLDER_DOT_IDE);
6✔
263
    this.downloadPath = this.userHome.resolve("Downloads/ide");
6✔
264

265
    this.path = computeSystemPath();
4✔
266
  }
1✔
267

268
  private String getMessageIdeHomeFound() {
269

270
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
7✔
271
  }
272

273
  private String getMessageNotInsideIdeProject() {
274

275
    return "You are not inside an IDE project: " + this.cwd;
5✔
276
  }
277

278
  private String getMessageIdeRootNotFound() {
279

280
    String root = getSystem().getEnv("IDE_ROOT");
5✔
281
    if (root == null) {
2!
282
      return "The environment variable IDE_ROOT is undefined. Please reinstall IDEasy or manually repair IDE_ROOT variable.";
2✔
283
    } else {
284
      return "The environment variable IDE_ROOT is pointing to an invalid path " + root + ". Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
285
    }
286
  }
287

288
  /**
289
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
290
   */
291
  public boolean isTest() {
292

293
    return false;
×
294
  }
295

296
  protected SystemPath computeSystemPath() {
297

298
    return new SystemPath(this);
×
299
  }
300

301
  private boolean isIdeHome(Path dir) {
302

303
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
304
      return false;
2✔
305
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
306
      return false;
×
307
    }
308
    return true;
2✔
309
  }
310

311
  private EnvironmentVariables createVariables() {
312

313
    AbstractEnvironmentVariables system = createSystemVariables();
3✔
314
    AbstractEnvironmentVariables user = system.extend(this.userHomeIde, EnvironmentVariablesType.USER);
6✔
315
    AbstractEnvironmentVariables settings = user.extend(this.settingsPath, EnvironmentVariablesType.SETTINGS);
6✔
316
    AbstractEnvironmentVariables workspace = settings.extend(this.workspacePath, EnvironmentVariablesType.WORKSPACE);
6✔
317
    AbstractEnvironmentVariables conf = workspace.extend(this.confPath, EnvironmentVariablesType.CONF);
6✔
318
    return conf.resolved();
3✔
319
  }
320

321
  protected AbstractEnvironmentVariables createSystemVariables() {
322

323
    return EnvironmentVariables.ofSystem(this);
3✔
324
  }
325

326
  @Override
327
  public SystemInfo getSystemInfo() {
328

329
    return this.systemInfo;
3✔
330
  }
331

332
  @Override
333
  public FileAccess getFileAccess() {
334

335
    // currently FileAccess contains download method and requires network proxy to be configured. Maybe download should be moved to its own interface/class
336
    configureNetworkProxy();
2✔
337
    return this.fileAccess;
3✔
338
  }
339

340
  @Override
341
  public CommandletManager getCommandletManager() {
342

343
    return this.commandletManager;
3✔
344
  }
345

346
  @Override
347
  public ToolRepository getDefaultToolRepository() {
348

349
    return this.defaultToolRepository;
3✔
350
  }
351

352
  @Override
353
  public MavenRepository getMavenToolRepository() {
354

355
    return this.mavenRepository;
3✔
356
  }
357

358
  @Override
359
  public CustomToolRepository getCustomToolRepository() {
360

361
    if (this.customToolRepository == null) {
3!
362
      this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
363
    }
364
    return this.customToolRepository;
3✔
365
  }
366

367
  @Override
368
  public Path getIdeHome() {
369

370
    return this.ideHome;
3✔
371
  }
372

373
  @Override
374
  public String getProjectName() {
375

376
    if (this.ideHome != null) {
3!
377
      return this.ideHome.getFileName().toString();
5✔
378
    }
379
    return "";
×
380
  }
381

382
  @Override
383
  public VersionIdentifier getProjectVersion() {
384

385
    if (this.ideHome != null) {
3!
386
      Path versionFile = this.ideHome.resolve(IdeContext.FILE_SOFTWARE_VERSION);
5✔
387
      if (Files.exists(versionFile)) {
5✔
388
        String version = this.fileAccess.readFileContent(versionFile).trim();
6✔
389
        return VersionIdentifier.of(version);
3✔
390
      }
391
    }
392
    return IdeMigrator.START_VERSION;
2✔
393
  }
394

395
  @Override
396
  public void setProjectVersion(VersionIdentifier version) {
397

398
    if (this.ideHome == null) {
3!
399
      throw new IllegalStateException("IDE_HOME not available!");
×
400
    }
401
    Objects.requireNonNull(version);
3✔
402
    Path versionFile = this.ideHome.resolve(IdeContext.FILE_SOFTWARE_VERSION);
5✔
403
    this.fileAccess.writeFileContent(version.toString(), versionFile);
6✔
404
  }
1✔
405

406
  @Override
407
  public Path getIdeRoot() {
408

409
    return this.ideRoot;
3✔
410
  }
411

412
  @Override
413
  public Path getIdePath() {
414

415
    Path myIdeRoot = getIdeRoot();
3✔
416
    if (myIdeRoot == null) {
2!
417
      return null;
×
418
    }
419
    return myIdeRoot.resolve(FOLDER_UNDERSCORE_IDE);
4✔
420
  }
421

422
  @Override
423
  public Path getCwd() {
424

425
    return this.cwd;
3✔
426
  }
427

428
  @Override
429
  public Path getTempPath() {
430

431
    Path idePath = getIdePath();
3✔
432
    if (idePath == null) {
2!
433
      return null;
×
434
    }
435
    return idePath.resolve("tmp");
4✔
436
  }
437

438
  @Override
439
  public Path getTempDownloadPath() {
440

441
    Path tmp = getTempPath();
3✔
442
    if (tmp == null) {
2!
443
      return null;
×
444
    }
445
    return tmp.resolve(FOLDER_DOWNLOADS);
4✔
446
  }
447

448
  @Override
449
  public Path getUserHome() {
450

451
    return this.userHome;
3✔
452
  }
453

454
  @Override
455
  public Path getUserHomeIde() {
456

457
    return this.userHomeIde;
3✔
458
  }
459

460
  @Override
461
  public Path getSettingsPath() {
462

463
    return this.settingsPath;
3✔
464
  }
465

466
  @Override
467
  public Path getSettingsGitRepository() {
468

469
    Path settingsPath = getSettingsPath();
3✔
470
    // check whether the settings path has a .git folder only if its not a symbolic link or junction
471
    if ((settingsPath != null) && !Files.exists(settingsPath.resolve(".git")) && !isSettingsRepositorySymlinkOrJunction()) {
12!
472
      error("Settings repository exists but is not a git repository.");
3✔
473
      return null;
2✔
474
    }
475
    return settingsPath;
2✔
476
  }
477

478
  @Override
479
  public boolean isSettingsRepositorySymlinkOrJunction() {
480

481
    Path settingsPath = getSettingsPath();
3✔
482
    if (settingsPath == null) {
2!
483
      return false;
×
484
    }
485
    return Files.isSymbolicLink(settingsPath) || getFileAccess().isJunction(settingsPath);
10!
486
  }
487

488
  @Override
489
  public Path getSettingsCommitIdPath() {
490

491
    return this.settingsCommitIdPath;
3✔
492
  }
493

494
  @Override
495
  public Path getConfPath() {
496

497
    return this.confPath;
3✔
498
  }
499

500
  @Override
501
  public Path getSoftwarePath() {
502

503
    if (this.ideHome == null) {
3✔
504
      return null;
2✔
505
    }
506
    return this.ideHome.resolve(FOLDER_SOFTWARE);
5✔
507
  }
508

509
  @Override
510
  public Path getSoftwareExtraPath() {
511

512
    Path softwarePath = getSoftwarePath();
3✔
513
    if (softwarePath == null) {
2!
514
      return null;
×
515
    }
516
    return softwarePath.resolve(FOLDER_EXTRA);
4✔
517
  }
518

519
  @Override
520
  public Path getSoftwareRepositoryPath() {
521

522
    Path idePath = getIdePath();
3✔
523
    if (idePath == null) {
2!
524
      return null;
×
525
    }
526
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
527
  }
528

529
  @Override
530
  public Path getPluginsPath() {
531

532
    return this.pluginsPath;
3✔
533
  }
534

535
  @Override
536
  public String getWorkspaceName() {
537

538
    return this.workspaceName;
3✔
539
  }
540

541
  @Override
542
  public Path getWorkspacePath() {
543

544
    return this.workspacePath;
3✔
545
  }
546

547
  @Override
548
  public Path getDownloadPath() {
549

550
    return this.downloadPath;
3✔
551
  }
552

553
  @Override
554
  public Path getUrlsPath() {
555

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

563
  @Override
564
  public Path getToolRepositoryPath() {
565

566
    Path idePath = getIdePath();
3✔
567
    if (idePath == null) {
2!
568
      return null;
×
569
    }
570
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
571
  }
572

573
  @Override
574
  public SystemPath getPath() {
575

576
    return this.path;
3✔
577
  }
578

579
  @Override
580
  public EnvironmentVariables getVariables() {
581

582
    if (this.variables == null) {
3✔
583
      this.variables = createVariables();
4✔
584
    }
585
    return this.variables;
3✔
586
  }
587

588
  @Override
589
  public UrlMetadata getUrls() {
590

591
    if (this.urlMetadata == null) {
3✔
592
      if (!isTest()) {
3!
593
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, getUrlsPath(), null);
×
594
      }
595
      this.urlMetadata = new UrlMetadata(this);
6✔
596
    }
597
    return this.urlMetadata;
3✔
598
  }
599

600
  @Override
601
  public boolean isQuietMode() {
602

603
    return this.startContext.isQuietMode();
4✔
604
  }
605

606
  @Override
607
  public boolean isBatchMode() {
608

609
    return this.startContext.isBatchMode();
4✔
610
  }
611

612
  @Override
613
  public boolean isForceMode() {
614

615
    return this.startContext.isForceMode();
4✔
616
  }
617

618
  @Override
619
  public boolean isForcePull() {
620

621
    return this.startContext.isForcePull();
×
622
  }
623

624
  @Override
625
  public boolean isForcePlugins() {
626

627
    return this.startContext.isForcePlugins();
×
628
  }
629

630
  @Override
631
  public boolean isForceRepositories() {
632

633
    return this.startContext.isForceRepositories();
×
634
  }
635

636
  @Override
637
  public boolean isOfflineMode() {
638

639
    return this.startContext.isOfflineMode();
4✔
640
  }
641

642
  @Override
643
  public boolean isPrivacyMode() {
644
    return this.startContext.isPrivacyMode();
×
645
  }
646

647
  @Override
648
  public boolean isSkipUpdatesMode() {
649

650
    return this.startContext.isSkipUpdatesMode();
4✔
651
  }
652

653
  @Override
654
  public boolean isOnline() {
655

656
    if (this.online == null) {
3✔
657
      configureNetworkProxy();
2✔
658
      // we currently assume we have only a CLI process that runs shortly
659
      // therefore we run this check only once to save resources when this method is called many times
660
      try {
661
        int timeout = 1000;
2✔
662
        //open a connection to github.com and try to retrieve data
663
        //getContent fails if there is no connection
664
        URLConnection connection = new URL("https://www.github.com").openConnection();
6✔
665
        connection.setConnectTimeout(timeout);
3✔
666
        connection.getContent();
3✔
667
        this.online = Boolean.TRUE;
3✔
668
      } catch (Exception ignored) {
×
669
        this.online = Boolean.FALSE;
×
670
      }
1✔
671
    }
672
    return this.online.booleanValue();
4✔
673
  }
674

675
  private void configureNetworkProxy() {
676

677
    if (this.networkProxy == null) {
3✔
678
      this.networkProxy = new NetworkProxy(this);
6✔
679
      this.networkProxy.configure();
3✔
680
    }
681
  }
1✔
682

683
  @Override
684
  public Locale getLocale() {
685

686
    Locale locale = this.startContext.getLocale();
4✔
687
    if (locale == null) {
2✔
688
      locale = Locale.getDefault();
2✔
689
    }
690
    return locale;
2✔
691
  }
692

693
  @Override
694
  public DirectoryMerger getWorkspaceMerger() {
695

696
    if (this.workspaceMerger == null) {
3✔
697
      this.workspaceMerger = new DirectoryMerger(this);
6✔
698
    }
699
    return this.workspaceMerger;
3✔
700
  }
701

702
  /**
703
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
704
   */
705
  @Override
706
  public Path getDefaultExecutionDirectory() {
707

708
    return this.defaultExecutionDirectory;
×
709
  }
710

711
  /**
712
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
713
   */
714
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
715

716
    if (defaultExecutionDirectory != null) {
×
717
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
718
    }
719
  }
×
720

721
  @Override
722
  public GitContext getGitContext() {
723

724
    return new GitContextImpl(this);
×
725
  }
726

727
  @Override
728
  public ProcessContext newProcess() {
729

730
    ProcessContext processContext = createProcessContext();
3✔
731
    if (this.defaultExecutionDirectory != null) {
3!
732
      processContext.directory(this.defaultExecutionDirectory);
×
733
    }
734
    return processContext;
2✔
735
  }
736

737
  @Override
738
  public IdeSystem getSystem() {
739

740
    if (this.system == null) {
×
741
      this.system = new IdeSystemImpl(this);
×
742
    }
743
    return this.system;
×
744
  }
745

746
  /**
747
   * @return a new instance of {@link ProcessContext}.
748
   * @see #newProcess()
749
   */
750
  protected ProcessContext createProcessContext() {
751

752
    return new ProcessContextImpl(this);
5✔
753
  }
754

755
  @Override
756
  public IdeSubLogger level(IdeLogLevel level) {
757

758
    return this.startContext.level(level);
5✔
759
  }
760

761
  @Override
762
  public void logIdeHomeAndRootStatus() {
763
    if (this.ideRoot != null) {
3!
764
      success("IDE_ROOT is set to {}", formatLocationPathForDisplay(this.ideRoot));
×
765
    }
766
    if (this.ideHome == null) {
3✔
767
      warning(getMessageNotInsideIdeProject());
5✔
768
    } else {
769
      success("IDE_HOME is set to {}", formatLocationPathForDisplay(this.ideHome));
12✔
770
    }
771
  }
1✔
772

773
  /**
774
   * Formats a path for GDPR compliance based on the privacy mode.
775
   *
776
   * @param location Path to format.
777
   * @return the formatted path string.
778
   */
779
  public String formatLocationPathForDisplay(Path location) {
780
    String locationString = location.toString();
3✔
781
    if (this.startContext.isPrivacyMode()) {
4!
782
      locationString = PrivacyUtil.applyPrivacyToPath(location);
3✔
783
    }
784
    return locationString;
2✔
785
  }
786

787
  @Override
788
  public String askForInput(String message, String defaultValue) {
789

790
    if (!message.isBlank()) {
×
791
      info(message);
×
792
    }
793
    if (isBatchMode()) {
×
794
      if (isForceMode() || isForcePull()) {
×
795
        return defaultValue;
×
796
      } else {
797
        throw new CliAbortException();
×
798
      }
799
    }
800
    String input = readLine().trim();
×
801
    return input.isEmpty() ? defaultValue : input;
×
802
  }
803

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

807
    String input;
808
    do {
809
      info(message);
3✔
810
      input = readLine().trim();
4✔
811
    } while (input.isEmpty());
3!
812

813
    return input;
2✔
814
  }
815

816
  @SuppressWarnings("unchecked")
817
  @Override
818
  public <O> O question(String question, O... options) {
819

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

854
  /**
855
   * @return the input from the end-user (e.g. read from the console).
856
   */
857
  protected abstract String readLine();
858

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

861
    O duplicate = mapping.put(key, option);
×
862
    if (duplicate != null) {
×
863
      throw new IllegalArgumentException("Duplicated option " + key);
×
864
    }
865
  }
×
866

867
  @Override
868
  public Step getCurrentStep() {
869

870
    return this.currentStep;
×
871
  }
872

873
  @Override
874
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
875

876
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
877
    return this.currentStep;
3✔
878
  }
879

880
  /**
881
   * Internal method to end the running {@link Step}.
882
   *
883
   * @param step the current {@link Step} to end.
884
   */
885
  public void endStep(StepImpl step) {
886

887
    if (step == this.currentStep) {
4!
888
      this.currentStep = this.currentStep.getParent();
6✔
889
    } else {
890
      String currentStepName = "null";
×
891
      if (this.currentStep != null) {
×
892
        currentStepName = this.currentStep.getName();
×
893
      }
894
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
895
    }
896
  }
1✔
897

898
  /**
899
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
900
   *
901
   * @param arguments the {@link CliArgument}.
902
   * @return the return code of the execution.
903
   */
904
  public int run(CliArguments arguments) {
905

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

946
  @Override
947
  public void runWithoutLogging(Runnable lambda, IdeLogLevel threshold) {
948

949
    this.startContext.deactivateLogging(threshold);
4✔
950
    lambda.run();
2✔
951
    this.startContext.activateLogging();
3✔
952
  }
1✔
953

954
  /**
955
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet) apply} and {@link Commandlet#run() run}.
956
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
957
   *     {@link Commandlet} did not match and we have to try a different candidate).
958
   */
959
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
960

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

996
              } else {
997
                interaction(
×
998
                    "Updates are available for the settings repository. If you want to apply the latest changes, call \"ide update\"");
999
              }
1000
            }
1001
          }
1002
        }
1003
        boolean success = ensureLicenseAgreement(cmd);
4✔
1004
        if (!success) {
2!
1005
          return ValidationResultValid.get();
×
1006
        }
1007
        cmd.run();
2✔
1008
      } finally {
1009
        if (previousLogLevel != null) {
2!
1010
          this.startContext.setLogLevel(previousLogLevel);
×
1011
        }
1012
      }
1✔
1013
    } else {
1014
      trace("Commandlet did not match");
×
1015
    }
1016
    return result;
2✔
1017
  }
1018

1019
  private boolean ensureLicenseAgreement(Commandlet cmd) {
1020

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

1062
    sb.setLength(0);
×
1063
    LocalDateTime now = LocalDateTime.now();
×
1064
    sb.append("On ").append(DateTimeUtil.formatDate(now, false)).append(" at ").append(DateTimeUtil.formatTime(now))
×
1065
        .append(" you accepted the IDEasy license.\n").append(LICENSE_URL);
×
1066
    try {
1067
      Files.writeString(licenseAgreement, sb);
×
1068
    } catch (Exception e) {
×
1069
      throw new RuntimeException("Failed to save license agreement!", e);
×
1070
    }
×
1071
    if (logLevelInfoDisabled) {
×
1072
      this.startContext.setLogLevel(IdeLogLevel.INFO, false);
×
1073
    }
1074
    if (logLevelInteractionDisabled) {
×
1075
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, false);
×
1076
    }
1077
    return true;
×
1078
  }
1079

1080
  private void verifyIdeRoot() {
1081

1082
    if (!isTest()) {
3!
1083
      if (this.ideRoot == null) {
×
1084
        warning("Variable IDE_ROOT is undefined. Please check your installation or run setup script again.");
×
1085
      } else if (this.ideHome != null) {
×
1086
        Path ideRootPath = getIdeRootPathFromEnv();
×
1087
        if (!this.ideRoot.equals(ideRootPath)) {
×
1088
          warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", ideRootPath,
×
1089
              this.ideHome.getFileName(), formatLocationPathForDisplay(this.ideRoot));
×
1090
        }
1091
      }
1092
    }
1093
  }
1✔
1094

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

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

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

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

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

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

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

1259
  @Override
1260
  public String findBash() {
1261

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

1267
    return bash;
2✔
1268
  }
1269

1270
  private String findBashOnWindows() {
1271

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

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

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

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

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

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

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

1321
  @Override
1322
  public WindowsPathSyntax getPathSyntax() {
1323

1324
    return this.pathSyntax;
3✔
1325
  }
1326

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

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

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

1340
    return startContext;
3✔
1341
  }
1342

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

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

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

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

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

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

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

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

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