mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-05-07 15:47:00 +02:00
LRU semantic for workdir cache (#1735)
Introduces a maximum size for the simple workdir cache. On cache overflow workdirs are evicted using an LRU strategy. Furthermore parallel requests for the same repository will now block until the workdir is released.
This commit is contained in:
@@ -46,10 +46,10 @@ public class GitRepositoryConfigStoreProvider {
|
||||
}
|
||||
|
||||
public GitRepositoryConfig getGitRepositoryConfig(String repositoryId) {
|
||||
return getFronStore(createStore(repositoryId));
|
||||
return getFromStore(createStore(repositoryId));
|
||||
}
|
||||
|
||||
private static GitRepositoryConfig getFronStore(ConfigurationStore<GitRepositoryConfig> store) {
|
||||
private static GitRepositoryConfig getFromStore(ConfigurationStore<GitRepositoryConfig> store) {
|
||||
return store.getOptional().orElse(new GitRepositoryConfig());
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class GitRepositoryConfigStoreProvider {
|
||||
|
||||
@Override
|
||||
public GitRepositoryConfig get() {
|
||||
return getFronStore(delegate);
|
||||
return getFromStore(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,21 +44,22 @@ class GitWorkingCopyReclaimer {
|
||||
|
||||
private final GitContext context;
|
||||
|
||||
public GitWorkingCopyReclaimer(GitContext context) {
|
||||
GitWorkingCopyReclaimer(GitContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ParentAndClone<Repository, Repository> reclaim(File target, String initialBranch) throws SimpleWorkingCopyFactory.ReclaimFailedException {
|
||||
LOG.trace("reclaim repository {}", context.getRepository());
|
||||
String branchToCheckout = determineBranch(initialBranch);
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
Repository repo = openTarget(target);
|
||||
try (Git git = Git.open(target)) {
|
||||
git.reset().setMode(ResetCommand.ResetType.HARD).call();
|
||||
git.clean().setForce(true).setCleanDirectories(true).call();
|
||||
git.fetch().call();
|
||||
git.checkout().setForced(true).setName("origin/" + initialBranch).call();
|
||||
git.branchDelete().setBranchNames(initialBranch).setForce(true).call();
|
||||
git.checkout().setName(initialBranch).setCreateBranch(true).call();
|
||||
git.checkout().setForced(true).setName("origin/" + branchToCheckout).call();
|
||||
git.branchDelete().setBranchNames(branchToCheckout).setForce(true).call();
|
||||
git.checkout().setName(branchToCheckout).setCreateBranch(true).call();
|
||||
return new ParentAndClone<>(null, repo, target);
|
||||
} catch (GitAPIException | IOException e) {
|
||||
throw new SimpleWorkingCopyFactory.ReclaimFailedException(e);
|
||||
@@ -67,6 +68,16 @@ class GitWorkingCopyReclaimer {
|
||||
}
|
||||
}
|
||||
|
||||
private String determineBranch(String initialBranch) {
|
||||
if (initialBranch != null) {
|
||||
return initialBranch;
|
||||
}
|
||||
if (context.getConfig().getDefaultBranch() != null) {
|
||||
return context.getConfig().getDefaultBranch();
|
||||
}
|
||||
return context.getGlobalConfig().getDefaultBranch();
|
||||
}
|
||||
|
||||
private Repository openTarget(File target) throws SimpleWorkingCopyFactory.ReclaimFailedException {
|
||||
try {
|
||||
return GitUtil.open(target);
|
||||
|
||||
@@ -70,7 +70,7 @@ public class SimpleGitWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposi
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeWorkingCopy(Repository workingCopy) throws Exception {
|
||||
protected void closeWorkingCopy(Repository workingCopy) {
|
||||
if (workingCopy != null) {
|
||||
workingCopy.close();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.GitTestHelper;
|
||||
import sonia.scm.repository.PreProcessorUtil;
|
||||
@@ -149,6 +150,32 @@ public class SimpleGitWorkingCopyFactoryTest extends AbstractGitCommandTestBase
|
||||
assertBranchCheckedOutAndClean(workdir, "master");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimCleanDirectoryConfiguredDefaultBranch() throws Exception {
|
||||
SimpleGitWorkingCopyFactory factory = new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
GitContext context = createContext();
|
||||
GitRepositoryConfig config = context.getConfig();
|
||||
config.setDefaultBranch("master");
|
||||
context.setConfig(config);
|
||||
factory.reclaim(context, workdir, null);
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "master");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimCleanDirectoryGloballyConfiguredDefaultBranch() throws Exception {
|
||||
SimpleGitWorkingCopyFactory factory = new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
GitContext context = createContext();
|
||||
context.getGlobalConfig().setDefaultBranch("master");
|
||||
factory.reclaim(context, workdir, null);
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "master");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimCleanDirectoryWithOtherBranch() throws Exception {
|
||||
SimpleGitWorkingCopyFactory factory = new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
@@ -192,6 +219,7 @@ public class SimpleGitWorkingCopyFactoryTest extends AbstractGitCommandTestBase
|
||||
factory.reclaim(createContext(), workdir, "master");
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "master");
|
||||
assertThat(newDirectory).doesNotExist();
|
||||
}
|
||||
|
||||
public File createExistingClone(SimpleGitWorkingCopyFactory factory) throws Exception {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.aragost.javahg.RepositoryConfiguration;
|
||||
import com.aragost.javahg.ext.purge.PurgeExtension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.hooks.HookEnvironment;
|
||||
@@ -69,6 +70,7 @@ public class HgRepositoryFactory {
|
||||
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
||||
repoConfiguration.getEnvironment().putAll(environment);
|
||||
repoConfiguration.addExtension(HgFileviewExtension.class);
|
||||
repoConfiguration.addExtension(PurgeExtension.class);
|
||||
|
||||
boolean pending = hookEnvironment.isPending();
|
||||
repoConfiguration.setEnablePendingChangesets(pending);
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.aragost.javahg.commands.PullCommand;
|
||||
import com.aragost.javahg.commands.StatusCommand;
|
||||
import com.aragost.javahg.commands.UpdateCommand;
|
||||
import com.aragost.javahg.commands.flags.CloneCommandFlags;
|
||||
import com.aragost.javahg.ext.purge.PurgeCommand;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import sonia.scm.repository.HgExtensions;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
@@ -78,7 +79,9 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
|
||||
for (String unknown : StatusCommand.on(clone).execute().getUnknown()) {
|
||||
delete(clone.getDirectory(), unknown);
|
||||
}
|
||||
UpdateCommand.on(clone).rev(initialBranch).clean().execute();
|
||||
String branchToCheckOut = initialBranch == null ? "default" : initialBranch;
|
||||
UpdateCommand.on(clone).rev(branchToCheckOut).clean().execute();
|
||||
PurgeCommand.on(clone).execute();
|
||||
return new ParentAndClone<>(centralRepository, clone, target);
|
||||
} catch (ExecutionException | IOException e) {
|
||||
throw new ReclaimFailedException(e);
|
||||
|
||||
@@ -24,12 +24,19 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.aragost.javahg.BaseRepository;
|
||||
import com.aragost.javahg.Repository;
|
||||
import com.aragost.javahg.commands.BranchCommand;
|
||||
import com.aragost.javahg.commands.RemoveCommand;
|
||||
import com.aragost.javahg.commands.StatusCommand;
|
||||
import com.aragost.javahg.commands.results.StatusResult;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
||||
import sonia.scm.repository.work.SimpleCachingWorkingCopyPool;
|
||||
import sonia.scm.repository.work.WorkdirProvider;
|
||||
import sonia.scm.repository.work.WorkingCopy;
|
||||
@@ -38,6 +45,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -46,6 +54,7 @@ public class SimpleHgWorkingCopyFactoryTest extends AbstractHgCommandTestBase {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
private MeterRegistry meterRegistry = new SimpleMeterRegistry();
|
||||
|
||||
private WorkdirProvider workdirProvider;
|
||||
|
||||
@@ -54,7 +63,7 @@ public class SimpleHgWorkingCopyFactoryTest extends AbstractHgCommandTestBase {
|
||||
@Before
|
||||
public void bindScmProtocol() throws IOException {
|
||||
workdirProvider = new WorkdirProvider(temporaryFolder.newFolder(), repositoryLocationResolver, false);
|
||||
workingCopyFactory = new SimpleHgWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry()) {
|
||||
workingCopyFactory = new SimpleHgWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider, meterRegistry), new SimpleMeterRegistry()) {
|
||||
@Override
|
||||
public void configure(com.aragost.javahg.commands.PullCommand pullCommand) {
|
||||
// we do not want to configure http hooks in this unit test
|
||||
@@ -134,6 +143,95 @@ public class SimpleHgWorkingCopyFactoryTest extends AbstractHgCommandTestBase {
|
||||
|
||||
WorkingCopy<Repository, Repository> cachedWorkingCopy = workingCopyFactory.createWorkingCopy(cmdContext, "default");
|
||||
assertThat(cachedWorkingCopy.getDirectory()).isEqualTo(initialDirectory);
|
||||
assertThat(cachedWorkingCopy.getDirectory().toPath().resolve("newDir")).isEmptyDirectory();
|
||||
assertThat(cachedWorkingCopy.getDirectory().toPath().resolve("newDir")).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimCleanDirectoryWithSameBranch() throws Exception {
|
||||
SimpleHgWorkingCopyFactory factory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
factory.reclaim(cmdContext, workdir, "default");
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimCleanDirectoryWithDefaultBranch() throws Exception {
|
||||
SimpleHgWorkingCopyFactory factory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
factory.reclaim(cmdContext, workdir, null);
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimCleanDirectoryWithOtherBranch() throws Exception {
|
||||
SimpleHgWorkingCopyFactory factory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
factory.reclaim(cmdContext, workdir, "test-branch");
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "test-branch");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimDirectoryWithDeletedFileInIndex() throws Exception {
|
||||
SimpleHgWorkingCopyFactory factory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
RemoveCommand.on(Repository.open(workdir)).execute("a.txt");
|
||||
|
||||
factory.reclaim(cmdContext, workdir, "default");
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimDirectoryWithDeletedFileInDirectory() throws Exception {
|
||||
SimpleHgWorkingCopyFactory factory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
RemoveCommand.on(Repository.open(workdir)).execute("c");
|
||||
|
||||
factory.reclaim(cmdContext, workdir, "default");
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReclaimDirectoryWithAdditionalFileInDirectory() throws Exception {
|
||||
SimpleHgWorkingCopyFactory factory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
Path newDirectory = workdir.toPath().resolve("new");
|
||||
Files.createDirectories(newDirectory);
|
||||
Files.createFile(newDirectory.resolve("newFile"));
|
||||
|
||||
factory.reclaim(cmdContext, workdir, "default");
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "default");
|
||||
assertThat(newDirectory).doesNotExist();
|
||||
}
|
||||
|
||||
private void assertBranchCheckedOutAndClean(File workdir, String expectedBranch) {
|
||||
BaseRepository repository = Repository.open(workdir);
|
||||
StatusResult statusResult = StatusCommand.on(repository).execute();
|
||||
assertThat(statusResult.getAdded()).isEmpty();
|
||||
assertThat(statusResult.getCopied()).isEmpty();
|
||||
assertThat(statusResult.getIgnored()).isEmpty();
|
||||
assertThat(statusResult.getMissing()).isEmpty();
|
||||
assertThat(statusResult.getModified()).isEmpty();
|
||||
assertThat(statusResult.getRemoved()).isEmpty();
|
||||
assertThat(statusResult.getUnknown()).isEmpty();
|
||||
assertThat(BranchCommand.on(repository).get()).isEqualTo(expectedBranch);
|
||||
}
|
||||
|
||||
public File createExistingClone(SimpleHgWorkingCopyFactory factory) throws Exception {
|
||||
File workdir = temporaryFolder.newFolder();
|
||||
extract(workdir, "sonia/scm/repository/spi/scm-hg-spi-workdir-test.zip");
|
||||
Files.write(workdir.toPath().resolve(".hg").resolve("hgrc"), Arrays.asList("[paths]", "default = " + repositoryDirectory.getAbsolutePath()));
|
||||
return workdir;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -24,6 +24,7 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -44,17 +45,19 @@ public class SimpleSvnWorkingCopyFactoryTest extends AbstractSvnCommandTestBase
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
private MeterRegistry meterRegistry = new SimpleMeterRegistry();
|
||||
|
||||
// keep this so that it will not be garbage collected (Transport keeps this in a week reference)
|
||||
private WorkdirProvider workdirProvider;
|
||||
|
||||
|
||||
@Before
|
||||
public void initWorkDirProvider() throws IOException {
|
||||
workdirProvider = new WorkdirProvider(temporaryFolder.newFolder(), repositoryLocationResolver, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCheckoutLatestRevision() throws SVNException, IOException {
|
||||
public void shouldCheckoutLatestRevision() {
|
||||
SimpleSvnWorkingCopyFactory factory = new SimpleSvnWorkingCopyFactory(new NoneCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
|
||||
try (WorkingCopy<File, File> workingCopy = factory.createWorkingCopy(createContext(), null)) {
|
||||
@@ -96,7 +99,7 @@ public class SimpleSvnWorkingCopyFactoryTest extends AbstractSvnCommandTestBase
|
||||
|
||||
@Test
|
||||
public void shouldDeleteUntrackedFileOnReclaim() throws IOException {
|
||||
SimpleSvnWorkingCopyFactory factory = new SimpleSvnWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
SimpleSvnWorkingCopyFactory factory = new SimpleSvnWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider, meterRegistry), new SimpleMeterRegistry());
|
||||
|
||||
WorkingCopy<File, File> workingCopy = factory.createWorkingCopy(createContext(), null);
|
||||
File directory = workingCopy.getWorkingRepository();
|
||||
@@ -113,8 +116,8 @@ public class SimpleSvnWorkingCopyFactoryTest extends AbstractSvnCommandTestBase
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRestoreDeletedFileOnReclaim() throws IOException {
|
||||
SimpleSvnWorkingCopyFactory factory = new SimpleSvnWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider), new SimpleMeterRegistry());
|
||||
public void shouldRestoreDeletedFileOnReclaim() {
|
||||
SimpleSvnWorkingCopyFactory factory = new SimpleSvnWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider, meterRegistry), new SimpleMeterRegistry());
|
||||
|
||||
WorkingCopy<File, File> workingCopy = factory.createWorkingCopy(createContext(), null);
|
||||
File directory = workingCopy.getWorkingRepository();
|
||||
|
||||
Reference in New Issue
Block a user