mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 16:05:44 +01:00
Initial migration servlet
This commit is contained in:
@@ -161,10 +161,17 @@ public class DefaultCipherHandler implements CipherHandler {
|
|||||||
* @return decrypted value
|
* @return decrypted value
|
||||||
*/
|
*/
|
||||||
public String decode(char[] plainKey, String value) {
|
public String decode(char[] plainKey, String value) {
|
||||||
String result = null;
|
Base64.Decoder decoder = Base64.getUrlDecoder();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] encodedInput = Base64.getUrlDecoder().decode(value);
|
return decode(plainKey, value, decoder);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return decode(plainKey, value, Base64.getDecoder());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decode(char[] plainKey, String value, Base64.Decoder decoder) {
|
||||||
|
try {
|
||||||
|
byte[] encodedInput = decoder.decode(value);
|
||||||
byte[] salt = new byte[SALT_LENGTH];
|
byte[] salt = new byte[SALT_LENGTH];
|
||||||
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
|
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
|
||||||
|
|
||||||
@@ -180,12 +187,10 @@ public class DefaultCipherHandler implements CipherHandler {
|
|||||||
|
|
||||||
byte[] decoded = cipher.doFinal(encoded);
|
byte[] decoded = cipher.doFinal(encoded);
|
||||||
|
|
||||||
result = new String(decoded, ENCODING);
|
return new String(decoded, ENCODING);
|
||||||
} catch (IOException | GeneralSecurityException ex) {
|
} catch (IOException | GeneralSecurityException ex) {
|
||||||
throw new CipherException("could not decode string", ex);
|
throw new CipherException("could not decode string", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import sonia.scm.ScmEventBusModule;
|
|||||||
import sonia.scm.ScmInitializerModule;
|
import sonia.scm.ScmInitializerModule;
|
||||||
import sonia.scm.Stage;
|
import sonia.scm.Stage;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.migration.UpdateException;
|
||||||
import sonia.scm.plugin.DefaultPluginLoader;
|
import sonia.scm.plugin.DefaultPluginLoader;
|
||||||
import sonia.scm.plugin.Plugin;
|
import sonia.scm.plugin.Plugin;
|
||||||
import sonia.scm.plugin.PluginException;
|
import sonia.scm.plugin.PluginException;
|
||||||
@@ -54,6 +55,7 @@ import sonia.scm.plugin.PluginLoader;
|
|||||||
import sonia.scm.plugin.PluginWrapper;
|
import sonia.scm.plugin.PluginWrapper;
|
||||||
import sonia.scm.plugin.PluginsInternal;
|
import sonia.scm.plugin.PluginsInternal;
|
||||||
import sonia.scm.plugin.SmpArchive;
|
import sonia.scm.plugin.SmpArchive;
|
||||||
|
import sonia.scm.update.MigrationWizardContextListener;
|
||||||
import sonia.scm.update.UpdateEngine;
|
import sonia.scm.update.UpdateEngine;
|
||||||
import sonia.scm.util.ClassLoaders;
|
import sonia.scm.util.ClassLoaders;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
@@ -110,14 +112,16 @@ public class BootstrapContextListener implements ServletContextListener {
|
|||||||
public void contextDestroyed(ServletContextEvent sce) {
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
contextListener.contextDestroyed(sce);
|
contextListener.contextDestroyed(sce);
|
||||||
|
|
||||||
for (PluginWrapper plugin : contextListener.getPlugins()) {
|
if (contextListener instanceof ScmContextListener) {
|
||||||
ClassLoader pcl = plugin.getClassLoader();
|
for (PluginWrapper plugin : ((ScmContextListener) contextListener).getPlugins()) {
|
||||||
|
ClassLoader pcl = plugin.getClassLoader();
|
||||||
|
|
||||||
if (pcl instanceof Closeable) {
|
if (pcl instanceof Closeable) {
|
||||||
try {
|
try {
|
||||||
((Closeable) pcl).close();
|
((Closeable) pcl).close();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
logger.warn("could not close plugin classloader", ex);
|
logger.warn("could not close plugin classloader", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +155,9 @@ public class BootstrapContextListener implements ServletContextListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createContextListener(File pluginDirectory) {
|
private void createContextListener(File pluginDirectory) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
renameOldPluginsFolder(pluginDirectory);
|
||||||
if (!isCorePluginExtractionDisabled()) {
|
if (!isCorePluginExtractionDisabled()) {
|
||||||
extractCorePlugins(context, pluginDirectory);
|
extractCorePlugins(context, pluginDirectory);
|
||||||
} else {
|
} else {
|
||||||
@@ -166,14 +172,36 @@ public class BootstrapContextListener implements ServletContextListener {
|
|||||||
|
|
||||||
Injector bootstrapInjector = createBootstrapInjector(pluginLoader);
|
Injector bootstrapInjector = createBootstrapInjector(pluginLoader);
|
||||||
|
|
||||||
processUpdates(pluginLoader, bootstrapInjector);
|
MigrationWizardContextListener wizardContextListener = prepareWizardIfNeeded(bootstrapInjector);
|
||||||
|
|
||||||
contextListener = bootstrapInjector.getInstance(ScmContextListener.Factory.class).create(cl, plugins);
|
if (wizardContextListener.wizardNecessary()) {
|
||||||
|
contextListener = wizardContextListener;
|
||||||
|
} else {
|
||||||
|
processUpdates(pluginLoader, bootstrapInjector);
|
||||||
|
|
||||||
|
contextListener = bootstrapInjector.getInstance(ScmContextListener.Factory.class).create(cl, plugins);
|
||||||
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new PluginLoadException("could not load plugins", ex);
|
throw new PluginLoadException("could not load plugins", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renameOldPluginsFolder(File pluginDirectory) {
|
||||||
|
if (new File(pluginDirectory, "classpath.xml").exists()) {
|
||||||
|
File backupDirectory = new File(pluginDirectory.getParentFile(), "plugins.v1");
|
||||||
|
boolean renamed = pluginDirectory.renameTo(backupDirectory);
|
||||||
|
if (renamed) {
|
||||||
|
logger.warn("moved old plugins directory to {}", backupDirectory);
|
||||||
|
} else {
|
||||||
|
throw new UpdateException("could not rename existing v1 plugin directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MigrationWizardContextListener prepareWizardIfNeeded(Injector bootstrapInjector) {
|
||||||
|
return new MigrationWizardContextListener(bootstrapInjector);
|
||||||
|
}
|
||||||
|
|
||||||
private Injector createBootstrapInjector(PluginLoader pluginLoader) {
|
private Injector createBootstrapInjector(PluginLoader pluginLoader) {
|
||||||
Module scmContextListenerModule = new ScmContextListenerModule();
|
Module scmContextListenerModule = new ScmContextListenerModule();
|
||||||
BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader);
|
BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader);
|
||||||
@@ -402,7 +430,7 @@ public class BootstrapContextListener implements ServletContextListener {
|
|||||||
private ServletContext context;
|
private ServletContext context;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private ScmContextListener contextListener;
|
private ServletContextListener contextListener;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private boolean registered = false;
|
private boolean registered = false;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package sonia.scm.update;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.servlet.GuiceServletContextListener;
|
||||||
|
|
||||||
|
public class MigrationWizardContextListener extends GuiceServletContextListener {
|
||||||
|
|
||||||
|
private final Injector injector;
|
||||||
|
|
||||||
|
public MigrationWizardContextListener(Injector bootstrapInjector) {
|
||||||
|
this.injector = bootstrapInjector.createChildInjector(new MigrationWizardModule());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wizardNecessary() {
|
||||||
|
return injector.getInstance(MigrationWizardServlet.class).wizardNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Injector getInjector() {
|
||||||
|
return injector;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package sonia.scm.update;
|
||||||
|
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class MigrationWizardModule extends ServletModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureServlets() {
|
||||||
|
serve("/*").with(MigrationWizardServlet.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package sonia.scm.update;
|
||||||
|
|
||||||
|
import com.github.mustachejava.DefaultMustacheFactory;
|
||||||
|
import com.github.mustachejava.Mustache;
|
||||||
|
import com.github.mustachejava.MustacheFactory;
|
||||||
|
import sonia.scm.boot.RestartEvent;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.update.repository.MigrationStrategy;
|
||||||
|
import sonia.scm.update.repository.MigrationStrategyDao;
|
||||||
|
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class MigrationWizardServlet extends HttpServlet {
|
||||||
|
|
||||||
|
private final XmlRepositoryV1UpdateStep repositoryV1UpdateStep;
|
||||||
|
private final MigrationStrategyDao migrationStrategyDao;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MigrationWizardServlet(XmlRepositoryV1UpdateStep repositoryV1UpdateStep, MigrationStrategyDao migrationStrategyDao) {
|
||||||
|
this.repositoryV1UpdateStep = repositoryV1UpdateStep;
|
||||||
|
this.migrationStrategyDao = migrationStrategyDao;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wizardNecessary() {
|
||||||
|
return !repositoryV1UpdateStep.missingMigrationStrategies().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||||
|
List<XmlRepositoryV1UpdateStep.V1Repository> missingMigrationStrategies = repositoryV1UpdateStep.missingMigrationStrategies();
|
||||||
|
|
||||||
|
resp.setStatus(200);
|
||||||
|
|
||||||
|
HashMap<String, Object> model = new HashMap<>();
|
||||||
|
|
||||||
|
model.put("submitUrl", req.getRequestURI());
|
||||||
|
model.put("repositories", missingMigrationStrategies);
|
||||||
|
model.put("strategies", getMigrationStrategies());
|
||||||
|
|
||||||
|
MustacheFactory mf = new DefaultMustacheFactory();
|
||||||
|
Mustache mustache = mf.compile("templates/repository-migration.mustache");
|
||||||
|
mustache.execute(resp.getWriter(), model).flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getMigrationStrategies() {
|
||||||
|
return stream(MigrationStrategy.values()).map(Enum::name).collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||||
|
resp.setStatus(200);
|
||||||
|
|
||||||
|
req.getParameterMap().forEach(
|
||||||
|
(name, strategy) -> migrationStrategyDao.set(name, MigrationStrategy.valueOf(strategy[0]))
|
||||||
|
);
|
||||||
|
|
||||||
|
MustacheFactory mf = new DefaultMustacheFactory();
|
||||||
|
Mustache mustache = mf.compile("templates/repository-migration-restart.mustache");
|
||||||
|
mustache.execute(resp.getWriter(), new Object()).flush();
|
||||||
|
|
||||||
|
ScmEventBus.getInstance().post(new RestartEvent(MigrationWizardServlet.class, "wrote migration data"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import com.google.inject.Injector;
|
|||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
enum MigrationStrategy {
|
public enum MigrationStrategy {
|
||||||
|
|
||||||
COPY(CopyMigrationStrategy.class),
|
COPY(CopyMigrationStrategy.class),
|
||||||
MOVE(MoveMigrationStrategy.class),
|
MOVE(MoveMigrationStrategy.class),
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import sonia.scm.store.ConfigurationStore;
|
|||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public class MigrationStrategyDao {
|
public class MigrationStrategyDao {
|
||||||
|
|
||||||
private final RepositoryMigrationPlan plan;
|
private final RepositoryMigrationPlan plan;
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Optional.empty;
|
import static java.util.Optional.empty;
|
||||||
import static java.util.Optional.of;
|
import static java.util.Optional.of;
|
||||||
import static sonia.scm.version.Version.parse;
|
import static sonia.scm.version.Version.parse;
|
||||||
@@ -109,6 +111,23 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<V1Repository> missingMigrationStrategies() {
|
||||||
|
if (!resolveV1File().exists()) {
|
||||||
|
LOG.info("no v1 repositories database file found");
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JAXBContext jaxbContext = JAXBContext.newInstance(XmlRepositoryV1UpdateStep.V1RepositoryDatabase.class);
|
||||||
|
return readV1Database(jaxbContext)
|
||||||
|
.map(v1Database -> v1Database.repositoryList.repositories.stream())
|
||||||
|
.orElse(Stream.empty())
|
||||||
|
.filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
throw new UpdateException("could not read v1 repository database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void backupOldRepositoriesFile() {
|
private void backupOldRepositoriesFile() {
|
||||||
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
||||||
Path oldRepositoriesFile = configDir.resolve("repositories.xml");
|
Path oldRepositoriesFile = configDir.resolve("repositories.xml");
|
||||||
@@ -126,8 +145,8 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
Repository repository = new Repository(
|
Repository repository = new Repository(
|
||||||
v1Repository.id,
|
v1Repository.id,
|
||||||
v1Repository.type,
|
v1Repository.type,
|
||||||
getNamespace(v1Repository),
|
v1Repository.getNewNamespace(),
|
||||||
getName(v1Repository),
|
v1Repository.getNewName(),
|
||||||
v1Repository.contact,
|
v1Repository.contact,
|
||||||
v1Repository.description,
|
v1Repository.description,
|
||||||
createPermissions(v1Repository));
|
createPermissions(v1Repository));
|
||||||
@@ -142,10 +161,14 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private MigrationStrategy readMigrationStrategy(V1Repository v1Repository) {
|
private MigrationStrategy readMigrationStrategy(V1Repository v1Repository) {
|
||||||
return migrationStrategyDao.get(v1Repository.id)
|
return findMigrationStrategy(v1Repository)
|
||||||
.orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
|
.orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<MigrationStrategy> findMigrationStrategy(V1Repository v1Repository) {
|
||||||
|
return migrationStrategyDao.get(v1Repository.id);
|
||||||
|
}
|
||||||
|
|
||||||
private RepositoryPermission[] createPermissions(V1Repository v1Repository) {
|
private RepositoryPermission[] createPermissions(V1Repository v1Repository) {
|
||||||
if (v1Repository.permissions == null) {
|
if (v1Repository.permissions == null) {
|
||||||
return new RepositoryPermission[0];
|
return new RepositoryPermission[0];
|
||||||
@@ -161,24 +184,6 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
return new RepositoryPermission(v1Permission.name, v1Permission.type, v1Permission.groupPermission);
|
return new RepositoryPermission(v1Permission.name, v1Permission.type, v1Permission.groupPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getNamespace(V1Repository v1Repository) {
|
|
||||||
String[] nameParts = getNameParts(v1Repository.name);
|
|
||||||
return nameParts.length > 1 ? nameParts[0] : v1Repository.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getName(V1Repository v1Repository) {
|
|
||||||
String[] nameParts = getNameParts(v1Repository.name);
|
|
||||||
return nameParts.length == 1 ? nameParts[0] : concatPathElements(nameParts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String concatPathElements(String[] nameParts) {
|
|
||||||
return Arrays.stream(nameParts).skip(1).collect(Collectors.joining("_"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] getNameParts(String v1Name) {
|
|
||||||
return v1Name.split("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<V1RepositoryDatabase> readV1Database(JAXBContext jaxbContext) throws JAXBException {
|
private Optional<V1RepositoryDatabase> readV1Database(JAXBContext jaxbContext) throws JAXBException {
|
||||||
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File());
|
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File());
|
||||||
if (unmarshal instanceof V1RepositoryDatabase) {
|
if (unmarshal instanceof V1RepositoryDatabase) {
|
||||||
@@ -205,7 +210,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@XmlRootElement(name = "repositories")
|
@XmlRootElement(name = "repositories")
|
||||||
private static class V1Repository {
|
public static class V1Repository {
|
||||||
private String contact;
|
private String contact;
|
||||||
private long creationDate;
|
private long creationDate;
|
||||||
private Long lastModified;
|
private Long lastModified;
|
||||||
@@ -218,6 +223,32 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
private List<V1Permission> permissions;
|
private List<V1Permission> permissions;
|
||||||
private V1Properties properties;
|
private V1Properties properties;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewNamespace() {
|
||||||
|
String[] nameParts = getNameParts(name);
|
||||||
|
return nameParts.length > 1 ? nameParts[0] : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewName() {
|
||||||
|
String[] nameParts = getNameParts(name);
|
||||||
|
return nameParts.length == 1 ? nameParts[0] : concatPathElements(nameParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getNameParts(String v1Name) {
|
||||||
|
return v1Name.split("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String concatPathElements(String[] nameParts) {
|
||||||
|
return Arrays.stream(nameParts).skip(1).collect(Collectors.joining("_"));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "V1Repository{" +
|
return "V1Repository{" +
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>SCM-Manager Restart</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
SCM-Manager will restart to migrate the data.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>SCM-Manager Migration</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>SCM-Manager Migration</h1>
|
||||||
|
You have migrated from SCM-Manager v1 to SCM-Manager v2.
|
||||||
|
<form action="{{submitUrl}}" method="post">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>original name</th>
|
||||||
|
<th>new namespace/name</th>
|
||||||
|
<th>Strategy</th>
|
||||||
|
</tr>
|
||||||
|
{{#repositories}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{name}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{newNamespace}}/{{newName}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select name="{{id}}">
|
||||||
|
{{#strategies}}
|
||||||
|
<option>{{.}}</option>
|
||||||
|
{{/strategies}}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/repositories}}
|
||||||
|
</table>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user