[git] Fixed caching git-trusted failure (IJPL-254472)

(cherry picked from commit a11ab71c33694546ac840e02f6de59bf48a78d92)


(cherry picked from commit 5cef50f116adffe45a73a92c0cc8ca6c6928b418)

IJ-MR-222169

GitOrigin-RevId: d111f26b7b4d53b3de78db64949c8b76a6b5d9b0
This commit is contained in:
natalia.ponomareva
2026-09-08 11:23:27 +00:00
committed by intellij-monorepo-bot
parent 21d6cd5ebf
commit 57933ab7d4
5 changed files with 87 additions and 4 deletions
+3
View File
@@ -97,6 +97,9 @@ git4idea.commands.Git
- a:runCommand(com.intellij.openapi.util.Computable):git4idea.commands.GitCommandResult
- a:runCommand(git4idea.commands.GitLineHandler):git4idea.commands.GitCommandResult
- a:runCommandWithoutCollectingOutput(git4idea.commands.GitLineHandler):git4idea.commands.GitCommandResult
f:git4idea.commands.GitCommandNotTrustedException
- java.lang.IllegalStateException
- <init>(java.lang.String):V
c:git4idea.commands.GitCommandResult
- s:error(java.lang.String):git4idea.commands.GitCommandResult
- getOutputOrThrow(I[]):java.lang.String
@@ -0,0 +1,17 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package git4idea.commands;
import org.jetbrains.annotations.NotNull;
/**
* Thrown when the project is not trusted.
* <p>
* This reflects the trust state of the caller, not a property of the git executable. A cache
* keyed by the executable, such as {@code git4idea.config.GitExecutableFileTester}, must not
* store this failure: the same executable can succeed for a later, trusted caller.
*/
public final class GitCommandNotTrustedException extends IllegalStateException {
public GitCommandNotTrustedException(@NotNull String message) {
super(message);
}
}
@@ -465,12 +465,12 @@ public abstract class GitHandler {
private void start() throws IOException {
if (myProject == null && !TrustedProjects.isProjectTrusted(Objects.requireNonNull(getWorkingDirectory()))) {
throw new IllegalStateException("Shouldn't be possible to run a Git command in potentially untrusted project. " +
"Pass Project to GitHandler constructor if applicable.");
throw new GitCommandNotTrustedException("Shouldn't be possible to run a Git command in potentially untrusted project. " +
"Pass Project to GitHandler constructor if applicable.");
}
if (myProject != null && !myProject.isDefault() && !TrustedProjects.isProjectTrusted(myProject)) {
throw new IllegalStateException("Shouldn't be possible to run a Git command in the safe mode");
throw new GitCommandNotTrustedException("Shouldn't be possible to run a Git command in the safe mode");
}
if (isStarted()) {
@@ -18,6 +18,7 @@ import com.intellij.platform.ide.impl.wsl.WslEelDescriptor;
import com.intellij.util.concurrency.AppJavaExecutorUtil;
import git4idea.commands.Git;
import git4idea.commands.GitCommand;
import git4idea.commands.GitCommandNotTrustedException;
import git4idea.commands.GitCommandResult;
import git4idea.commands.GitLineHandler;
import git4idea.i18n.GitBundle;
@@ -60,13 +61,27 @@ class GitExecutableFileTester {
LOG.warn(e);
result = new TestResult(e, currentLastModificationDate);
myTestMap.put(executable, result);
if (!isProjectTrustFailure(e)) {
myTestMap.put(executable, result);
}
}
return result;
});
}
/**
* A {@link GitCommandNotTrustedException} reflects the trust state of the caller, not a
* property of {@code executable}, so it must not be cached: the same executable can be
* retested for a different, trusted caller and succeed.
*/
private static boolean isProjectTrustFailure(@NotNull Throwable e) {
for (Throwable t = e; t != null; t = t.getCause()) {
if (t instanceof GitCommandNotTrustedException) return true;
}
return false;
}
private static @NotNull GitVersion testOrAbort(@Nullable Project project, @NotNull GitExecutable executable) throws Exception {
int maxAttempts = 1;
@@ -0,0 +1,48 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package git4idea.config
import com.intellij.ide.trustedProjects.TrustedProjects
import com.intellij.testFramework.junit5.SystemProperty
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.fixture.projectFixture
import com.intellij.vcs.test.vcsPlatformFixture
import git4idea.commands.GitCommandNotTrustedException
import git4idea.config.GitSaveChangesPolicy
import git4idea.test.GitPlatformTestContext
import git4idea.test.gitPlatformFixture
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@TestApplication
@SystemProperty("idea.trust.headless.disabled", "false")
internal class GitExecutableManagerTrustCacheTest {
private val contextFixture = projectFixture(openAfterCreation = true).let { projectFixture ->
projectFixture
.vcsPlatformFixture()
.gitPlatformFixture(projectFixture, defaultSaveChangesPolicy = GitSaveChangesPolicy.SHELVE, hasRemoteGitOperation = false)
}
private val context: GitPlatformTestContext get() = contextFixture.get()
@Test
fun `an untrusted-project failure is not cached and does not poison a later trusted call`(): Unit = with(context) {
val manager = GitExecutableManager.getInstance()
val executable = manager.getExecutable(project)
// The fixture's own setup already tested this executable and cached a success for it;
// drop that entry so the call below genuinely re-tests under the trust state set here.
manager.dropVersionCache(executable)
TrustedProjects.setProjectTrusted(project, false)
val failure = assertThrows(GitVersionIdentificationException::class.java) {
manager.identifyVersion(project, executable)
}
assertTrue(failure.cause is GitCommandNotTrustedException)
// Without the fix, the poisoned cache entry (same GitExecutable, unchanged mtime)
// would be replayed here even though the project is now trusted.
TrustedProjects.setProjectTrusted(project, true)
val version = manager.identifyVersion(project, executable)
assertTrue(version.isSupported)
}
}