diff --git a/scm-core/src/main/java/sonia/scm/update/BlobDirectoryAccess.java b/scm-core/src/main/java/sonia/scm/update/BlobDirectoryAccess.java new file mode 100644 index 0000000000..35eaa134fd --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/BlobDirectoryAccess.java @@ -0,0 +1,15 @@ +package sonia.scm.update; + +import java.io.IOException; +import java.nio.file.Path; + +public interface BlobDirectoryAccess { + + void forBlobDirectories(BlobDirectoryConsumer blobDirectoryConsumer) throws IOException; + + void moveToRepositoryBlobStore(Path blobDirectory, String newDirectoryName, String repositoryId) throws IOException; + + interface BlobDirectoryConsumer { + void accept(Path directory) throws IOException; + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/DefaultBlobDirectoryAccess.java b/scm-dao-xml/src/main/java/sonia/scm/store/DefaultBlobDirectoryAccess.java new file mode 100644 index 0000000000..d22a76c79d --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/DefaultBlobDirectoryAccess.java @@ -0,0 +1,68 @@ +package sonia.scm.store; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.SCMContextProvider; +import sonia.scm.repository.RepositoryLocationResolver; +import sonia.scm.update.BlobDirectoryAccess; +import sonia.scm.util.IOUtil; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class DefaultBlobDirectoryAccess implements BlobDirectoryAccess { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultBlobDirectoryAccess.class); + + private final SCMContextProvider contextProvider; + private final RepositoryLocationResolver locationResolver; + + @Inject + public DefaultBlobDirectoryAccess(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) { + this.contextProvider = contextProvider; + this.locationResolver = locationResolver; + } + + @Override + public void forBlobDirectories(BlobDirectoryConsumer blobDirectoryConsumer) throws IOException { + Path v1blobDir = computeV1BlobDir(); + if (Files.exists(v1blobDir) && Files.isDirectory(v1blobDir)) { + try (Stream fileStream = Files.list(v1blobDir)) { + fileStream.filter(p -> Files.isDirectory(p)).forEach(p -> { + try { + blobDirectoryConsumer.accept(p); + } catch (IOException e) { + throw new RuntimeException("could not call consumer for blob directory " + p, e); + } + }); + } + } + } + + @Override + public void moveToRepositoryBlobStore(Path blobDirectory, String newDirectoryName, String repositoryId) throws IOException { + Path repositoryLocation; + try { + repositoryLocation = locationResolver + .forClass(Path.class) + .getLocation(repositoryId); + } catch (IllegalStateException e) { + LOG.info("ignoring blob directory {} because there is no repository location for repository id {}", blobDirectory, repositoryId); + return; + } + Path target = repositoryLocation + .resolve(Store.BLOB.getRepositoryStoreDirectory()); + IOUtil.mkdirs(target.toFile()); + Path resolvedSourceDirectory = computeV1BlobDir().resolve(blobDirectory); + Path resolvedTargetDirectory = target.resolve(newDirectoryName); + LOG.trace("moving directory {} to {}", resolvedSourceDirectory, resolvedTargetDirectory); + Files.move(resolvedSourceDirectory, resolvedTargetDirectory); + } + + private Path computeV1BlobDir() { + return contextProvider.getBaseDirectory().toPath().resolve("var").resolve("blob"); + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java index b8d30bc0c1..ca4aeea468 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java @@ -17,7 +17,6 @@ public class JAXBPropertyFileAccess implements PropertyFileAccess { private static final Logger LOG = LoggerFactory.getLogger(JAXBPropertyFileAccess.class); - public static final String XML_FILENAME_SUFFIX = ".xml"; private final SCMContextProvider contextProvider; private final RepositoryLocationResolver locationResolver; @@ -31,8 +30,8 @@ public class JAXBPropertyFileAccess implements PropertyFileAccess { public Target renameGlobalConfigurationFrom(String oldName) { return newName -> { Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME); - Path oldConfigFile = configDir.resolve(oldName + XML_FILENAME_SUFFIX); - Path newConfigFile = configDir.resolve(newName + XML_FILENAME_SUFFIX); + Path oldConfigFile = configDir.resolve(oldName + StoreConstants.FILE_EXTENSION); + Path newConfigFile = configDir.resolve(newName + StoreConstants.FILE_EXTENSION); Files.move(oldConfigFile, newConfigFile); }; } @@ -45,7 +44,7 @@ public class JAXBPropertyFileAccess implements PropertyFileAccess { Path v1storeDir = computeV1StoreDir(); if (Files.exists(v1storeDir) && Files.isDirectory(v1storeDir)) { try (Stream fileStream = Files.list(v1storeDir)) { - fileStream.filter(p -> p.toString().endsWith(XML_FILENAME_SUFFIX)).forEach(p -> { + fileStream.filter(p -> p.toString().endsWith(StoreConstants.FILE_EXTENSION)).forEach(p -> { try { String storeName = extractStoreName(p); storeFileConsumer.accept(p, storeName); @@ -84,7 +83,7 @@ public class JAXBPropertyFileAccess implements PropertyFileAccess { private String extractStoreName(Path p) { String fileName = p.getFileName().toString(); - return fileName.substring(0, fileName.length() - XML_FILENAME_SUFFIX.length()); + return fileName.substring(0, fileName.length() - StoreConstants.FILE_EXTENSION.length()); } }; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsV1UpdateStep.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsV1UpdateStep.java new file mode 100644 index 0000000000..2684222c32 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsV1UpdateStep.java @@ -0,0 +1,43 @@ +package sonia.scm.web.lfs; + +import sonia.scm.migration.UpdateStep; +import sonia.scm.plugin.Extension; +import sonia.scm.update.BlobDirectoryAccess; +import sonia.scm.version.Version; + +import javax.inject.Inject; +import java.nio.file.Path; + +@Extension +public class LfsV1UpdateStep implements UpdateStep { + + private final BlobDirectoryAccess blobDirectoryAccess; + + @Inject + public LfsV1UpdateStep(BlobDirectoryAccess blobDirectoryAccess) { + this.blobDirectoryAccess = blobDirectoryAccess; + } + + @Override + public void doUpdate() throws Exception { + blobDirectoryAccess.forBlobDirectories( + f -> { + Path v1Directory = f.getFileName(); + String v1DirectoryName = v1Directory.toString(); + if (v1DirectoryName.endsWith("-git-lfs")) { + blobDirectoryAccess.moveToRepositoryBlobStore(f, v1DirectoryName, v1DirectoryName.substring(0, v1DirectoryName.length() - "-git-lfs".length())); + } + } + ); + } + + @Override + public Version getTargetVersion() { + return Version.parse("2.0.0"); + } + + @Override + public String getAffectedDataType() { + return "sonia.scm.git.lfs"; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/BootstrapModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/BootstrapModule.java index 9b6ea719d8..c0c933efd2 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/BootstrapModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/BootstrapModule.java @@ -19,11 +19,13 @@ import sonia.scm.store.BlobStoreFactory; import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.DataStoreFactory; +import sonia.scm.store.DefaultBlobDirectoryAccess; import sonia.scm.store.FileBlobStoreFactory; import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.store.JAXBDataStoreFactory; import sonia.scm.store.JAXBPropertyFileAccess; +import sonia.scm.update.BlobDirectoryAccess; import sonia.scm.update.PropertyFileAccess; import sonia.scm.update.V1PropertyDAO; import sonia.scm.update.xml.XmlV1PropertyDAO; @@ -65,6 +67,7 @@ public class BootstrapModule extends AbstractModule { bind(PluginLoader.class).toInstance(pluginLoader); bind(V1PropertyDAO.class, XmlV1PropertyDAO.class); bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class); + bind(BlobDirectoryAccess.class, DefaultBlobDirectoryAccess.class); } private void bind(Class clazz, Class defaultImplementation) {