Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/main/java/jenkins/plugins/git/DisableHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ class DisableHooks implements RepositoryCallback<Object> {
public Object invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException {
final String VAL = Functions.isWindows() ? DISABLED_WIN : DISABLED_NIX;
final StoredConfig repoConfig = repo.getConfig();
repoConfig.setString("core", null, "hooksPath", VAL);
repoConfig.save();
// Skip the rewrite, and its config.lock, when hooksPath is already disabled. JENKINS-71349
if (!VAL.equals(repoConfig.getString("core", null, "hooksPath"))) {
repoConfig.setString("core", null, "hooksPath", VAL);
repoConfig.save();
}
return null;
}
}
5 changes: 4 additions & 1 deletion src/main/java/jenkins/plugins/git/UnsetHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
public Object invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException {
final StoredConfig repoConfig = repo.getConfig();
final String val = repoConfig.getString("core", null, "hooksPath");
if (val != null && !val.isEmpty() && !DisableHooks.DISABLED_NIX.equals(val) && !DisableHooks.DISABLED_WIN.equals(val)) {
if (val == null) {
return null; // nothing to unset; avoid a redundant config.lock write. JENKINS-71349
}
if (!val.isEmpty() && !DisableHooks.DISABLED_NIX.equals(val) && !DisableHooks.DISABLED_WIN.equals(val)) {

Check warning on line 27 in src/main/java/jenkins/plugins/git/UnsetHooks.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 27 is only partially covered, 2 branches are missing
LOGGER.warning(() -> "core.hooksPath explicitly set to %s and will be left intact on %s.".formatted(val, repo.getDirectory()));
} else {
repoConfig.unset("core", null, "hooksPath");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import hudson.EnvVars;
import hudson.model.TaskListener;

import java.io.File;
import java.nio.file.Files;
import java.util.Random;
import org.eclipse.jgit.lib.StoredConfig;
import org.jenkinsci.plugins.gitclient.Git;
Expand Down Expand Up @@ -203,4 +205,74 @@ void testConfigure_3argsFalse() throws Exception {
String hooksPath = getCoreHooksPath();
assertThat(hooksPath, is(NULL_HOOKS_PATH));
}

private File createConfigLock() throws Exception {
return client.withRepository((repo, channel) -> {
File lock = new File(repo.getDirectory(), "config.lock");
Files.deleteIfExists(lock.toPath());
assertTrue(lock.createNewFile());
return lock;
});
}

// JENKINS-71349: re-disabling an already-disabled repo must not save(). A planted stale
// config.lock would otherwise make save() throw LockFailedException.
@Test
void testConfigureDisabledIsNoOpWhenAlreadyDisabled() throws Exception {
GitHooksConfiguration.configure(client, false);
assertThat(getCoreHooksPath(), is(NULL_HOOKS_PATH));

File configLock = createConfigLock();
try {
GitHooksConfiguration.configure(client, false);
assertThat(getCoreHooksPath(), is(NULL_HOOKS_PATH));
assertTrue(configLock.exists(), "DisableHooks must not touch an unrelated stale lock");
} finally {
Files.deleteIfExists(configLock.toPath());
}
}

// The guard must skip only the disabled sentinel, never a different value (SECURITY-2754).
@Test
void testConfigureDisabledOverwritesNonDisabledValue() throws Exception {
setCoreHooksPath(ALTERNATE_HOOKS_PATH);
assertThat(getCoreHooksPath(), is(ALTERNATE_HOOKS_PATH));

GitHooksConfiguration.configure(client, false);

assertThat(getCoreHooksPath(), is(NULL_HOOKS_PATH));
}

@Test
void testConfigureDisabledRepeatedlyIsNoOp() throws Exception {
GitHooksConfiguration.configure(client, false);
assertThat(getCoreHooksPath(), is(NULL_HOOKS_PATH));

File configLock = createConfigLock();
try {
for (int i = 0; i < 5; i++) {
GitHooksConfiguration.configure(client, false);
}
assertThat(getCoreHooksPath(), is(NULL_HOOKS_PATH));
assertTrue(configLock.exists(), "repeated disable must not touch the stale lock");
} finally {
Files.deleteIfExists(configLock.toPath());
}
}

// JENKINS-71349, allow-hooks path: UnsetHooks must not save() when hooksPath is already absent.
@Test
void testConfigureAllowedIsNoOpWhenHooksPathAbsent() throws Exception {
GitHooksConfiguration.configure(client, true);
assertThat(getCoreHooksPath(), is(nullValue()));

File configLock = createConfigLock();
try {
GitHooksConfiguration.configure(client, true);
assertThat(getCoreHooksPath(), is(nullValue()));
assertTrue(configLock.exists(), "UnsetHooks must not touch the stale lock when hooksPath is absent");
} finally {
Files.deleteIfExists(configLock.toPath());
}
}
}
Loading