This commit is contained in:
Eduard Heimbuch
2019-07-05 08:03:55 +02:00
14 changed files with 161 additions and 52 deletions

View File

@@ -1,5 +1,7 @@
package sonia.scm.repository; package sonia.scm.repository;
import java.util.function.BiConsumer;
public abstract class RepositoryLocationResolver { public abstract class RepositoryLocationResolver {
public abstract boolean supportsLocationType(Class<?> type); public abstract boolean supportsLocationType(Class<?> type);
@@ -35,5 +37,12 @@ public abstract class RepositoryLocationResolver {
* @throws IllegalStateException when there already is a location for the given repository registered. * @throws IllegalStateException when there already is a location for the given repository registered.
*/ */
void setLocation(String repositoryId, T location); void setLocation(String repositoryId, T location);
/**
* Iterates all repository locations known to this resolver instance and calls the consumer giving the repository id
* and its location for each repository.
* @param consumer This callback will be called for each repository with the repository id and its location.
*/
void forAllLocations(BiConsumer<String, T> consumer);
} }
} }

View File

@@ -0,0 +1,11 @@
package sonia.scm.update;
import sonia.scm.repository.Repository;
/**
* Use this in {@link sonia.scm.migration.UpdateStep}s only to read repository objects directly from locations given by
* {@link sonia.scm.repository.RepositoryLocationResolver}.
*/
public interface UpdateStepRepositoryMetadataAccess<T> {
Repository read(T location);
}

View File

@@ -5,19 +5,21 @@ import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry; import sonia.scm.ContextEntry;
import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.store.StoreConstants;
import sonia.scm.update.UpdateStepRepositoryMetadataAccess;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller; import javax.xml.bind.Marshaller;
import java.nio.file.Path; import java.nio.file.Path;
class MetadataStore { public class MetadataStore implements UpdateStepRepositoryMetadataAccess<Path> {
private static final Logger LOG = LoggerFactory.getLogger(MetadataStore.class); private static final Logger LOG = LoggerFactory.getLogger(MetadataStore.class);
private final JAXBContext jaxbContext; private final JAXBContext jaxbContext;
MetadataStore() { public MetadataStore() {
try { try {
jaxbContext = JAXBContext.newInstance(Repository.class); jaxbContext = JAXBContext.newInstance(Repository.class);
} catch (JAXBException ex) { } catch (JAXBException ex) {
@@ -25,10 +27,10 @@ class MetadataStore {
} }
} }
Repository read(Path path) { public Repository read(Path path) {
LOG.trace("read repository metadata from {}", path); LOG.trace("read repository metadata from {}", path);
try { try {
return (Repository) jaxbContext.createUnmarshaller().unmarshal(path.toFile()); return (Repository) jaxbContext.createUnmarshaller().unmarshal(resolveDataPath(path).toFile());
} catch (JAXBException ex) { } catch (JAXBException ex) {
throw new InternalRepositoryException( throw new InternalRepositoryException(
ContextEntry.ContextBuilder.entity(Path.class, path.toString()).build(), "failed read repository metadata", ex ContextEntry.ContextBuilder.entity(Path.class, path.toString()).build(), "failed read repository metadata", ex
@@ -41,10 +43,13 @@ class MetadataStore {
try { try {
Marshaller marshaller = jaxbContext.createMarshaller(); Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(repository, path.toFile()); marshaller.marshal(repository, resolveDataPath(path).toFile());
} catch (JAXBException ex) { } catch (JAXBException ex) {
throw new InternalRepositoryException(repository, "failed write repository metadata", ex); throw new InternalRepositoryException(repository, "failed write repository metadata", ex);
} }
} }
private Path resolveDataPath(Path repositoryPath) {
return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION));
}
} }

View File

@@ -94,6 +94,11 @@ public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocation
PathBasedRepositoryLocationResolver.this.setLocation(repositoryId, ((Path) location).toAbsolutePath()); PathBasedRepositoryLocationResolver.this.setLocation(repositoryId, ((Path) location).toAbsolutePath());
} }
} }
@Override
public void forAllLocations(BiConsumer<String, T> consumer) {
pathById.forEach((id, path) -> consumer.accept(id, (T) contextProvider.resolve(path)));
}
}; };
} }
@@ -115,10 +120,6 @@ public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocation
return contextProvider.resolve(removedPath); return contextProvider.resolve(removedPath);
} }
void forAllPaths(BiConsumer<String, Path> consumer) {
pathById.forEach((id, path) -> consumer.accept(id, contextProvider.resolve(path)));
}
void updateModificationDate() { void updateModificationDate() {
this.writePathDatabase(); this.writePathDatabase();
} }

View File

@@ -1,5 +1,7 @@
package sonia.scm.repository.xml; package sonia.scm.repository.xml;
import sonia.scm.repository.RepositoryLocationResolver;
import javax.inject.Inject; import javax.inject.Inject;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -7,9 +9,9 @@ import java.util.function.BiConsumer;
public class SingleRepositoryUpdateProcessor { public class SingleRepositoryUpdateProcessor {
@Inject @Inject
private PathBasedRepositoryLocationResolver locationResolver; private RepositoryLocationResolver locationResolver;
public void doUpdate(BiConsumer<String, Path> forEachRepository) { public void doUpdate(BiConsumer<String, Path> forEachRepository) {
locationResolver.forAllPaths(forEachRepository); locationResolver.forClass(Path.class).forAllLocations(forEachRepository);
} }
} }

View File

@@ -40,7 +40,7 @@ import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryDAO;
import sonia.scm.store.StoreConstants; import sonia.scm.repository.RepositoryLocationResolver;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.IOException; import java.io.IOException;
@@ -76,18 +76,14 @@ public class XmlRepositoryDAO implements RepositoryDAO {
} }
private void init() { private void init() {
repositoryLocationResolver.forAllPaths((repositoryId, repositoryPath) -> { RepositoryLocationResolver.RepositoryLocationResolverInstance<Path> pathRepositoryLocationResolverInstance = repositoryLocationResolver.create(Path.class);
Path metadataPath = resolveDataPath(repositoryPath); pathRepositoryLocationResolverInstance.forAllLocations((repositoryId, repositoryPath) -> {
Repository repository = metadataStore.read(metadataPath); Repository repository = metadataStore.read(repositoryPath);
byNamespaceAndName.put(repository.getNamespaceAndName(), repository); byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
byId.put(repositoryId, repository); byId.put(repositoryId, repository);
}); });
} }
private Path resolveDataPath(Path repositoryPath) {
return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION));
}
@Override @Override
public String getType() { public String getType() {
return "xml"; return "xml";
@@ -108,8 +104,7 @@ public class XmlRepositoryDAO implements RepositoryDAO {
Path repositoryPath = (Path) location; Path repositoryPath = (Path) location;
try { try {
Path metadataPath = resolveDataPath(repositoryPath); metadataStore.write(repositoryPath, repository);
metadataStore.write(metadataPath, repository);
} catch (Exception e) { } catch (Exception e) {
repositoryLocationResolver.remove(repository.getId()); repositoryLocationResolver.remove(repository.getId());
throw new InternalRepositoryException(repository, "failed to create filesystem", e); throw new InternalRepositoryException(repository, "failed to create filesystem", e);
@@ -166,9 +161,8 @@ public class XmlRepositoryDAO implements RepositoryDAO {
Path repositoryPath = repositoryLocationResolver Path repositoryPath = repositoryLocationResolver
.create(Path.class) .create(Path.class)
.getLocation(repository.getId()); .getLocation(repository.getId());
Path metadataPath = resolveDataPath(repositoryPath);
repositoryLocationResolver.updateModificationDate(); repositoryLocationResolver.updateModificationDate();
metadataStore.write(metadataPath, clone); metadataStore.write(repositoryPath, clone);
} }
@Override @Override

View File

@@ -120,7 +120,7 @@ class PathBasedRepositoryLocationResolverTest {
@Test @Test
void shouldInitWithExistingData() { void shouldInitWithExistingData() {
Map<String, Path> foundRepositories = new HashMap<>(); Map<String, Path> foundRepositories = new HashMap<>();
resolverWithExistingData.forAllPaths( resolverWithExistingData.forClass(Path.class).forAllLocations(
foundRepositories::put foundRepositories::put
); );
assertThat(foundRepositories) assertThat(foundRepositories)

View File

@@ -26,15 +26,13 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -47,6 +45,7 @@ class XmlRepositoryDAOTest {
@Mock @Mock
private PathBasedRepositoryLocationResolver locationResolver; private PathBasedRepositoryLocationResolver locationResolver;
private Consumer<BiConsumer<String, Path>> triggeredOnForAllLocations = none -> {};
private FileSystem fileSystem = new DefaultFileSystem(); private FileSystem fileSystem = new DefaultFileSystem();
@@ -69,6 +68,11 @@ class XmlRepositoryDAOTest {
@Override @Override
public void setLocation(String repositoryId, Path location) { public void setLocation(String repositoryId, Path location) {
} }
@Override
public void forAllLocations(BiConsumer<String, Path> consumer) {
triggeredOnForAllLocations.accept(consumer);
}
} }
); );
when(locationResolver.create(anyString())).thenAnswer(invocation -> createMockedRepoPath(basePath, invocation)); when(locationResolver.create(anyString())).thenAnswer(invocation -> createMockedRepoPath(basePath, invocation));
@@ -332,11 +336,10 @@ class XmlRepositoryDAOTest {
@Test @Test
void shouldRefreshWithExistingRepositoriesFromPathDatabase() { void shouldRefreshWithExistingRepositoriesFromPathDatabase() {
// given // given
doNothing().when(locationResolver).forAllPaths(any());
XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem);
mockExistingPath(); mockExistingPath();
XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem);
// when // when
dao.refresh(); dao.refresh();
@@ -346,12 +349,7 @@ class XmlRepositoryDAOTest {
} }
private void mockExistingPath() { private void mockExistingPath() {
doAnswer( triggeredOnForAllLocations = consumer -> consumer.accept("existing", repositoryPath);
invocation -> {
((BiConsumer<String, Path>) invocation.getArgument(0)).accept("existing", repositoryPath);
return null;
}
).when(locationResolver).forAllPaths(any());
} }
} }

View File

@@ -0,0 +1,21 @@
package sonia.scm.repository;
import org.eclipse.jgit.lib.StoredConfig;
import java.io.IOException;
public class GitConfigHelper {
private static final String CONFIG_SECTION_SCMM = "scmm";
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
public void createScmmConfig(Repository repository, org.eclipse.jgit.lib.Repository gitRepository) throws IOException {
StoredConfig config = gitRepository.getConfig();
config.setString(CONFIG_SECTION_SCMM, null, CONFIG_KEY_REPOSITORY_ID, repository.getId());
config.save();
}
public String getRepositoryId(StoredConfig gitConfig) {
return gitConfig.getString(CONFIG_SECTION_SCMM, null, CONFIG_KEY_REPOSITORY_ID);
}
}

View File

@@ -89,8 +89,6 @@ public class GitRepositoryHandler
GitRepositoryServiceProvider.COMMANDS); GitRepositoryServiceProvider.COMMANDS);
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
private static final String CONFIG_SECTION_SCMM = "scmm";
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
private final Scheduler scheduler; private final Scheduler scheduler;
@@ -185,7 +183,7 @@ public class GitRepositoryHandler
} }
public String getRepositoryId(StoredConfig gitConfig) { public String getRepositoryId(StoredConfig gitConfig) {
return gitConfig.getString(GitRepositoryHandler.CONFIG_SECTION_SCMM, null, GitRepositoryHandler.CONFIG_KEY_REPOSITORY_ID); return new GitConfigHelper().getRepositoryId(gitConfig);
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -194,9 +192,7 @@ public class GitRepositoryHandler
protected void create(Repository repository, File directory) throws IOException { protected void create(Repository repository, File directory) throws IOException {
try (org.eclipse.jgit.lib.Repository gitRepository = build(directory)) { try (org.eclipse.jgit.lib.Repository gitRepository = build(directory)) {
gitRepository.create(true); gitRepository.create(true);
StoredConfig config = gitRepository.getConfig(); new GitConfigHelper().createScmmConfig(repository, gitRepository);
config.setString(CONFIG_SECTION_SCMM, null, CONFIG_KEY_REPOSITORY_ID, repository.getId());
config.save();
} }
} }

View File

@@ -0,0 +1,70 @@
package sonia.scm.repository.update;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import sonia.scm.migration.UpdateException;
import sonia.scm.migration.UpdateStep;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.GitConfigHelper;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.update.UpdateStepRepositoryMetadataAccess;
import sonia.scm.version.Version;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static sonia.scm.version.Version.parse;
@Extension
public class GitV1UpdateStep implements UpdateStep {
private final RepositoryLocationResolver locationResolver;
private final UpdateStepRepositoryMetadataAccess<Path> repositoryMetadataAccess;
@Inject
public GitV1UpdateStep(RepositoryLocationResolver locationResolver, UpdateStepRepositoryMetadataAccess<Path> repositoryMetadataAccess) {
this.locationResolver = locationResolver;
this.repositoryMetadataAccess = repositoryMetadataAccess;
}
@Override
public void doUpdate() {
locationResolver.forClass(Path.class).forAllLocations(
(repositoryId, path) -> {
Repository repository = repositoryMetadataAccess.read(path);
if (isGitDirectory(repository)) {
try (org.eclipse.jgit.lib.Repository gitRepository = build(path.resolve("data").toFile())) {
new GitConfigHelper().createScmmConfig(repository, gitRepository);
} catch (IOException e) {
throw new UpdateException("could not update repository with id " + repositoryId + " in path " + path, e);
}
}
}
);
}
private org.eclipse.jgit.lib.Repository build(File directory) throws IOException {
return new FileRepositoryBuilder()
.setGitDir(directory)
.readEnvironment()
.findGitDir()
.build();
}
private boolean isGitDirectory(Repository repository) {
return GitRepositoryHandler.TYPE_NAME.equals(repository.getType());
}
@Override
public Version getTargetVersion() {
return parse("2.0.0");
}
@Override
public String getAffectedDataType() {
return "sonia.scm.plugin.git";
}
}

View File

@@ -41,13 +41,11 @@ import com.google.inject.Singleton;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.ConfigurationException; import sonia.scm.ConfigurationException;
import sonia.scm.ContextEntry;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.installer.HgInstaller; import sonia.scm.installer.HgInstaller;
import sonia.scm.installer.HgInstallerFactory; import sonia.scm.installer.HgInstallerFactory;
import sonia.scm.io.ExtendedCommand; import sonia.scm.io.ExtendedCommand;
import sonia.scm.io.INIConfiguration; import sonia.scm.io.INIConfiguration;
import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter; import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection; import sonia.scm.io.INISection;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
@@ -347,14 +345,6 @@ public class HgRepositoryHandler
writer.write(hgrc, hgrcFile); writer.write(hgrc, hgrcFile);
} }
public String getRepositoryId(File directory) {
try {
return new INIConfigurationReader().read(new File(directory, PATH_HGRC)).getSection(CONFIG_SECTION_SCMM).getParameter(CONFIG_KEY_REPOSITORY_ID);
} catch (IOException e) {
throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("Directory", directory.toString()), "could not read scm configuration file", e);
}
}
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** /**

View File

@@ -4,6 +4,7 @@ import sonia.scm.repository.BasicRepositoryLocationResolver;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.function.BiConsumer;
public class TempDirRepositoryLocationResolver extends BasicRepositoryLocationResolver { public class TempDirRepositoryLocationResolver extends BasicRepositoryLocationResolver {
private final File tempDirectory; private final File tempDirectory;
@@ -30,6 +31,11 @@ public class TempDirRepositoryLocationResolver extends BasicRepositoryLocationRe
public void setLocation(String repositoryId, T location) { public void setLocation(String repositoryId, T location) {
throw new UnsupportedOperationException("not implemented for tests"); throw new UnsupportedOperationException("not implemented for tests");
} }
@Override
public void forAllLocations(BiConsumer<String, T> consumer) {
consumer.accept("id", (T) tempDirectory.toPath());
}
}; };
} }
} }

View File

@@ -1,6 +1,7 @@
package sonia.scm.lifecycle.modules; package sonia.scm.lifecycle.modules;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.throwingproviders.ThrowingProviderBinder; import com.google.inject.throwingproviders.ThrowingProviderBinder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -10,6 +11,7 @@ import sonia.scm.io.DefaultFileSystem;
import sonia.scm.io.FileSystem; import sonia.scm.io.FileSystem;
import sonia.scm.plugin.PluginLoader; import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.repository.xml.MetadataStore;
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver; import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
import sonia.scm.security.CipherHandler; import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil; import sonia.scm.security.CipherUtil;
@@ -27,9 +29,12 @@ import sonia.scm.store.JAXBDataStoreFactory;
import sonia.scm.store.JAXBPropertyFileAccess; import sonia.scm.store.JAXBPropertyFileAccess;
import sonia.scm.update.BlobDirectoryAccess; import sonia.scm.update.BlobDirectoryAccess;
import sonia.scm.update.PropertyFileAccess; import sonia.scm.update.PropertyFileAccess;
import sonia.scm.update.UpdateStepRepositoryMetadataAccess;
import sonia.scm.update.V1PropertyDAO; import sonia.scm.update.V1PropertyDAO;
import sonia.scm.update.xml.XmlV1PropertyDAO; import sonia.scm.update.xml.XmlV1PropertyDAO;
import java.nio.file.Path;
public class BootstrapModule extends AbstractModule { public class BootstrapModule extends AbstractModule {
private static final Logger LOG = LoggerFactory.getLogger(BootstrapModule.class); private static final Logger LOG = LoggerFactory.getLogger(BootstrapModule.class);
@@ -68,6 +73,7 @@ public class BootstrapModule extends AbstractModule {
bind(V1PropertyDAO.class, XmlV1PropertyDAO.class); bind(V1PropertyDAO.class, XmlV1PropertyDAO.class);
bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class); bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class);
bind(BlobDirectoryAccess.class, DefaultBlobDirectoryAccess.class); bind(BlobDirectoryAccess.class, DefaultBlobDirectoryAccess.class);
bind(new TypeLiteral<UpdateStepRepositoryMetadataAccess<Path>>() {}).to(new TypeLiteral<MetadataStore>() {});
} }
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) { private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {