merge + refactor getStoreDirectory

This commit is contained in:
Mohamed Karray
2018-11-28 15:14:49 +01:00
98 changed files with 1474 additions and 1534 deletions

View File

@@ -34,16 +34,12 @@ package sonia.scm.repository;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException;
import sonia.scm.ConfigurationException; import sonia.scm.ConfigurationException;
import sonia.scm.ContextEntry;
import sonia.scm.io.CommandResult; import sonia.scm.io.CommandResult;
import sonia.scm.io.ExtendedCommand; import sonia.scm.io.ExtendedCommand;
import sonia.scm.io.FileSystem;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import java.io.File; import java.io.File;
@@ -62,6 +58,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
public static final String DEFAULT_VERSION_INFORMATION = "unknown"; public static final String DEFAULT_VERSION_INFORMATION = "unknown";
public static final String DOT = "."; public static final String DOT = ".";
static final String REPOSITORIES_NATIVE_DIRECTORY = "data";
/** /**
* the logger for AbstractSimpleRepositoryHandler * the logger for AbstractSimpleRepositoryHandler
@@ -69,73 +66,28 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class); LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
private FileSystem fileSystem;
private final RepositoryLocationResolver repositoryLocationResolver; private final RepositoryLocationResolver repositoryLocationResolver;
public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory, public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory,
FileSystem fileSystem, RepositoryLocationResolver repositoryLocationResolver) { RepositoryLocationResolver repositoryLocationResolver) {
super(storeFactory); super(storeFactory);
this.fileSystem = fileSystem;
this.repositoryLocationResolver = repositoryLocationResolver; this.repositoryLocationResolver = repositoryLocationResolver;
} }
@Override @Override
public Repository create(Repository repository) { public Repository create(Repository repository) {
File directory = repositoryLocationResolver.getInitialNativeDirectory(repository); File nativeDirectory = resolveNativeDirectory(repository);
if (directory != null && directory.exists()) {
throw new AlreadyExistsException(repository);
}
checkPath(directory, repository);
try { try {
fileSystem.create(directory); create(repository, nativeDirectory);
create(repository, directory); postCreate(repository, nativeDirectory);
postCreate(repository, directory);
return repository;
} catch (Exception ex) {
if (directory != null && directory.exists()) {
logger.warn("delete repository directory {}, because of failed repository creation", directory);
try {
fileSystem.destroy(directory);
} catch (IOException e) { } catch (IOException e) {
logger.error("Could not destroy directory", e); throw new InternalRepositoryException(repository, "could not create native repository directory", e);
} }
} return repository;
Throwables.propagateIfPossible(ex, AlreadyExistsException.class);
// This point will never be reached
return null;
}
}
@Override
public String createResourcePath(Repository repository) {
StringBuilder path = new StringBuilder("/");
path.append(getType().getName()).append("/").append(repository.getId());
return path.toString();
} }
@Override @Override
public void delete(Repository repository) { public void delete(Repository repository) {
File directory = null;
try {
directory = repositoryLocationResolver.getRepositoryDirectory(repository);
} catch (IOException e) {
throw new InternalRepositoryException(repository, "Cannot get the repository directory");
}
try {
if (directory.exists()) {
fileSystem.destroy(directory);
} else {
logger.warn("repository {} not found", repository.getNamespaceAndName());
}
} catch (IOException e) {
throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("directory", directory.toString()).in(repository), "could not delete repository directory", e);
}
} }
@Override @Override
@@ -157,22 +109,13 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
public File getDirectory(Repository repository) { public File getDirectory(Repository repository) {
File directory; File directory;
if (isConfigured()) { if (isConfigured()) {
try { directory = resolveNativeDirectory(repository);
directory = repositoryLocationResolver.getNativeDirectory(repository);
} catch (IOException e) {
throw new ConfigurationException("Error on getting the current repository directory");
}
} else { } else {
throw new ConfigurationException("RepositoryHandler is not configured"); throw new ConfigurationException("RepositoryHandler is not configured");
} }
return directory; return directory;
} }
@Override
public File getInitialBaseDirectory() {
return repositoryLocationResolver.getInitialBaseDirectory();
}
@Override @Override
public String getVersionInformation() { public String getVersionInformation() {
return DEFAULT_VERSION_INFORMATION; return DEFAULT_VERSION_INFORMATION;
@@ -224,40 +167,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
return content; return content;
} }
/** private File resolveNativeDirectory(Repository repository) {
* Returns true if the directory is a repository. return new File(repositoryLocationResolver.getRepositoryDirectory(repository), REPOSITORIES_NATIVE_DIRECTORY);
*
* @param directory directory to check
* @return true if the directory is a repository
* @since 1.9
*/
protected boolean isRepository(File directory) {
return new File(directory, DOT.concat(getType().getName())).exists();
} }
/**
* Check path for existing repositories
*
* @param directory repository target directory
* @throws RuntimeException when the parent directory already is a repository
*/
private void checkPath(File directory, Repository repository) {
if (directory == null) {
return;
}
File repositoryDirectory = getInitialBaseDirectory();
File parent = directory.getParentFile();
while ((parent != null) && !repositoryDirectory.equals(parent)) {
logger.trace("check {} for existing repository", parent);
if (isRepository(parent)) {
throw new InternalRepositoryException(repository, "parent path" + parent + " is a repository");
}
parent = parent.getParentFile();
}
}
} }

View File

@@ -1,63 +1,53 @@
package sonia.scm.repository; package sonia.scm.repository;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.io.FileSystem;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
/** /**
*
* A Location Resolver for File based Repository Storage. * A Location Resolver for File based Repository Storage.
* * <p>
* WARNING: The Locations provided with this class may not be used from the plugins to store any plugin specific files. * <b>WARNING:</b> The Locations provided with this class may not be used from the plugins to store any plugin specific files.
* * <p>
* Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data * Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data<br>
* Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files * Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files<br>
* Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations * Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations
* *
* @author Mohamed Karray * @author Mohamed Karray
* @since 2.0.0 * @since 2.0.0
*/ */
public final class InitialRepositoryLocationResolver { public class InitialRepositoryLocationResolver {
private static final String REPOSITORIES_DIRECTORY = "repositories"; public static final String DEFAULT_REPOSITORY_PATH = "repositories";
public static final String REPOSITORIES_NATIVE_DIRECTORY = "data";
private SCMContextProvider context;
private FileSystem fileSystem;
private final SCMContextProvider context;
@Inject @Inject
public InitialRepositoryLocationResolver(SCMContextProvider context, FileSystem fileSystem) { public InitialRepositoryLocationResolver(SCMContextProvider context) {
this.context = context; this.context = context;
this.fileSystem = fileSystem;
} }
public static File getNativeDirectory(File repositoriesDirectory, String repositoryId) { public InitialRepositoryLocation getRelativeRepositoryPath(Repository repository) {
return new File(repositoriesDirectory, repositoryId String relativePath = DEFAULT_REPOSITORY_PATH + File.separator + repository.getId();
.concat(File.separator) return new InitialRepositoryLocation(new File(context.getBaseDirectory(), relativePath), relativePath);
.concat(REPOSITORIES_NATIVE_DIRECTORY));
} }
public File getBaseDirectory() { public static class InitialRepositoryLocation {
return new File(context.getBaseDirectory(), REPOSITORIES_DIRECTORY); private final File absolutePath;
private final String relativePath;
public InitialRepositoryLocation(File absolutePath, String relativePath) {
this.absolutePath = absolutePath;
this.relativePath = relativePath;
} }
File getContextBaseDirectory() { public File getAbsolutePath() {
return context.getBaseDirectory(); return absolutePath;
} }
public File createDirectory(Repository repository) throws IOException { public String getRelativePath() {
File initialRepoFolder = getDirectory(repository); return relativePath;
fileSystem.create(initialRepoFolder);
return initialRepoFolder;
} }
public File getDirectory(Repository repository) {
return new File(context.getBaseDirectory(), REPOSITORIES_DIRECTORY
.concat(File.separator)
.concat(repository.getId()));
} }
} }

View File

@@ -11,10 +11,7 @@ import java.nio.file.Path;
public interface PathBasedRepositoryDAO extends RepositoryDAO { public interface PathBasedRepositoryDAO extends RepositoryDAO {
/** /**
* get the current path of the repository * Get the current path of the repository. This works for existing repositories only, not for repositories that should be created.
*
* @param repository
* @return the current path of the repository
*/ */
Path getPath(Repository repository) throws RepositoryPathNotFoundException; Path getPath(Repository repository) ;
} }

View File

@@ -48,10 +48,4 @@ public interface RepositoryDirectoryHandler extends RepositoryHandler {
* @return the current directory of the given repository * @return the current directory of the given repository
*/ */
File getDirectory(Repository repository); File getDirectory(Repository repository);
/**
* get the initial directory of all repositories
* @return the initial directory of all repositories
*/
File getInitialBaseDirectory();
} }

View File

@@ -50,17 +50,6 @@ public interface RepositoryHandler
extends Handler<Repository> extends Handler<Repository>
{ {
/**
* Returns the resource path for the given {@link Repository}.
* The resource path is part of the {@link Repository} url.
*
*
*
* @param repository given {@link Repository}
* @return resource path of the {@link Repository}
*/
public String createResourcePath(Repository repository);
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** /**

View File

@@ -4,18 +4,14 @@ import groovy.lang.Singleton;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.io.IOException;
import static sonia.scm.repository.InitialRepositoryLocationResolver.REPOSITORIES_NATIVE_DIRECTORY;
/** /**
*
* A Location Resolver for File based Repository Storage. * A Location Resolver for File based Repository Storage.
* * <p>
* WARNING: The Locations provided with this class may not be used from the plugins to store any plugin specific files. * <b>WARNING:</b> The Locations provided with this class may not be used from the plugins to store any plugin specific files.
* * <p>
* Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data * Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data<br>
* Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files * Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files<br>
* Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations * Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations
* *
* @author Mohamed Karray * @author Mohamed Karray
@@ -24,10 +20,6 @@ import static sonia.scm.repository.InitialRepositoryLocationResolver.REPOSITORIE
@Singleton @Singleton
public class RepositoryLocationResolver { public class RepositoryLocationResolver {
private static final String REPOSITORIES_STORES_DIRECTORY = "stores";
private static final String REPOSITORIES_CONFIG_DIRECTORY = "config";
private static final String GLOBAL_STORE_BASE_DIRECTORY = "var";
private RepositoryDAO repositoryDAO; private RepositoryDAO repositoryDAO;
private InitialRepositoryLocationResolver initialRepositoryLocationResolver; private InitialRepositoryLocationResolver initialRepositoryLocationResolver;
@@ -37,59 +29,13 @@ public class RepositoryLocationResolver {
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver; this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
} }
/** public File getRepositoryDirectory(Repository repository){
* Get the current repository directory from the dao or create the initial directory if the repository does not exists
* @param repository
* @return the current repository directory from the dao or the initial directory if the repository does not exists
* @throws IOException
*/
public File getRepositoryDirectory(Repository repository) throws IOException {
if (repositoryDAO instanceof PathBasedRepositoryDAO) { if (repositoryDAO instanceof PathBasedRepositoryDAO) {
PathBasedRepositoryDAO pathBasedRepositoryDAO = (PathBasedRepositoryDAO) repositoryDAO; PathBasedRepositoryDAO pathBasedRepositoryDAO = (PathBasedRepositoryDAO) repositoryDAO;
try {
return pathBasedRepositoryDAO.getPath(repository).toFile(); return pathBasedRepositoryDAO.getPath(repository).toFile();
} catch (RepositoryPathNotFoundException e) {
return createInitialDirectory(repository);
} }
} return initialRepositoryLocationResolver.getRelativeRepositoryPath(repository).getAbsolutePath();
return createInitialDirectory(repository);
} }
public File getInitialBaseDirectory() {
return initialRepositoryLocationResolver.getBaseDirectory();
}
public File createInitialDirectory(Repository repository) throws IOException {
return initialRepositoryLocationResolver.createDirectory(repository);
}
public File getInitialDirectory(Repository repository) {
return initialRepositoryLocationResolver.getDirectory(repository);
}
public File getNativeDirectory(Repository repository) throws IOException {
return new File (getRepositoryDirectory(repository), REPOSITORIES_NATIVE_DIRECTORY);
}
public File getInitialNativeDirectory(Repository repository) {
return new File (getInitialDirectory(repository), REPOSITORIES_NATIVE_DIRECTORY);
}
/**
* Get the store directory of a specific repository
* @param repository
* @return the store directory of a specific repository
*/
public File getStoresDirectory(Repository repository) throws IOException{
return new File (getRepositoryDirectory(repository), REPOSITORIES_STORES_DIRECTORY);
}
public File getConfigDirectory(Repository repository) throws IOException {
return new File (getRepositoryDirectory(repository), REPOSITORIES_CONFIG_DIRECTORY);
}
public File getGlobalStoreDirectory() {
return new File(initialRepositoryLocationResolver.getContextBaseDirectory(),
GLOBAL_STORE_BASE_DIRECTORY.concat(File.separator));
}
} }

View File

@@ -1,119 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Preconditions;
import sonia.scm.io.DirectoryFileFilter;
import sonia.scm.util.IOUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
* @since 1.11
*/
public final class RepositoryUtil {
private RepositoryUtil() {}
public static List<File> searchRepositoryDirectories(File directory, String... names) {
List<File> repositories = new ArrayList<>();
searchRepositoryDirectories(repositories, directory, Arrays.asList(names));
return repositories;
}
@SuppressWarnings("squid:S2083") // ignore, because the path is validated at {@link #getRepositoryId(File, File)}
public static String getRepositoryId(RepositoryDirectoryHandler handler, String directoryPath) throws IOException {
return getRepositoryId(handler.getInitialBaseDirectory(), new File(directoryPath));
}
public static String getRepositoryId(RepositoryDirectoryHandler handler, File directory) throws IOException {
return getRepositoryId(handler.getInitialBaseDirectory(), directory);
}
public static String getRepositoryId(File baseDirectory, File directory) throws IOException {
String path = directory.getCanonicalPath();
String basePath = baseDirectory.getCanonicalPath();
Preconditions.checkArgument(
path.startsWith(basePath),
"repository path %s is not in the main repository path %s", path, basePath
);
String id = IOUtil.trimSeperatorChars(path.substring(basePath.length()).replace(InitialRepositoryLocationResolver.REPOSITORIES_NATIVE_DIRECTORY, ""));
Preconditions.checkArgument(
!id.contains("\\") && !id.contains("/"),
"got illegal repository directory with separators in id: %s", path
);
return id;
}
private static void searchRepositoryDirectories(List<File> repositories, File directory, List<String> names) {
boolean found = false;
for (String name : names) {
if (new File(directory, name).exists()) {
found = true;
break;
}
}
if (found) {
repositories.add(directory);
} else {
File[] directories = directory.listFiles(DirectoryFileFilter.instance);
if (directories != null) {
for (File d : directories) {
searchRepositoryDirectories(repositories, d, names);
}
}
}
}
}

View File

@@ -74,19 +74,24 @@ public final class HookEventFacade
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
public HookEventHandler handle(String id) { public HookEventHandler handle(String id) {
return handle(repositoryManagerProvider.get().get(id)); Repository repository = repositoryManagerProvider.get().get(id);
if (repository == null)
{
throw notFound(entity("repository", id));
}
return handle(repository);
} }
public HookEventHandler handle(NamespaceAndName namespaceAndName) { public HookEventHandler handle(NamespaceAndName namespaceAndName) {
return handle(repositoryManagerProvider.get().get(namespaceAndName)); Repository repository = repositoryManagerProvider.get().get(namespaceAndName);
if (repository == null)
{
throw notFound(entity(namespaceAndName));
}
return handle(repository);
} }
public HookEventHandler handle(Repository repository) { public HookEventHandler handle(Repository repository) {
if (repository == null)
{
throw notFound(entity(repository));
}
return new HookEventHandler(repositoryManagerProvider.get(), return new HookEventHandler(repositoryManagerProvider.get(),
hookContextFactory, repository); hookContextFactory, repository);
} }

View File

@@ -0,0 +1,42 @@
package sonia.scm.repository;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.SCMContextProvider;
import java.io.File;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class InitialRepositoryLocationResolverTest {
@Mock
private SCMContextProvider context;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Before
public void init() throws IOException {
when(context.getBaseDirectory()).thenReturn(temporaryFolder.newFolder());
}
@Test
public void shouldComputeInitialDirectory() {
InitialRepositoryLocationResolver resolver = new InitialRepositoryLocationResolver(context);
Repository repository = new Repository();
repository.setId("ABC");
InitialRepositoryLocationResolver.InitialRepositoryLocation directory = resolver.getRelativeRepositoryPath(repository);
assertThat(directory.getAbsolutePath()).isEqualTo(new File(context.getBaseDirectory(), "repositories/ABC"));
assertThat(directory.getRelativePath()).isEqualTo( "repositories/ABC");
}
}

View File

@@ -1,62 +0,0 @@
package sonia.scm.repository;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class RepositoryUtilTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Mock
private RepositoryDirectoryHandler repositoryHandler;
private File repositoryTypeRoot;
@Before
public void setUpMocks() throws IOException {
repositoryTypeRoot = temporaryFolder.newFolder();
when(repositoryHandler.getInitialBaseDirectory()).thenReturn(repositoryTypeRoot);
}
@Test
public void testGetRepositoryId() throws IOException {
File repository = new File(repositoryTypeRoot, "abc");
String id = RepositoryUtil.getRepositoryId(repositoryHandler, repository.getPath());
assertEquals("abc", id);
}
@Test(expected = IllegalArgumentException.class)
public void testGetRepositoryIdWithInvalidPath() throws IOException {
File repository = new File("/etc/abc");
String id = RepositoryUtil.getRepositoryId(repositoryHandler, repository.getPath());
assertEquals("abc", id);
}
@Test(expected = IllegalArgumentException.class)
public void testGetRepositoryIdWithInvalidPathButSameLength() throws IOException {
File repository = new File(temporaryFolder.newFolder(), "abc");
String id = RepositoryUtil.getRepositoryId(repositoryHandler, repository.getPath());
assertEquals("abc", id);
}
@Test(expected = IllegalArgumentException.class)
public void testGetRepositoryIdWithInvalidId() throws IOException {
File repository = new File(repositoryTypeRoot, "abc/123");
RepositoryUtil.getRepositoryId(repositoryHandler, repository.getPath());
}
}

View File

@@ -21,6 +21,9 @@ public class RepositoryPath implements ModelObject {
@XmlTransient @XmlTransient
private Repository repository; private Repository repository;
@XmlTransient
private boolean toBeSynchronized;
/** /**
* Needed from JAXB * Needed from JAXB
*/ */
@@ -87,4 +90,12 @@ public class RepositoryPath implements ModelObject {
public boolean isValid() { public boolean isValid() {
return StringUtils.isNotEmpty(path); return StringUtils.isNotEmpty(path);
} }
public boolean toBeSynchronized() {
return toBeSynchronized;
}
public void setToBeSynchronized(boolean toBeSynchronized) {
this.toBeSynchronized = toBeSynchronized;
}
} }

View File

@@ -37,19 +37,21 @@ package sonia.scm.repository.xml;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.io.FileSystem;
import sonia.scm.repository.InitialRepositoryLocationResolver; import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.InitialRepositoryLocationResolver.InitialRepositoryLocation;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.PathBasedRepositoryDAO; import sonia.scm.repository.PathBasedRepositoryDAO;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPathNotFoundException;
import sonia.scm.store.JAXBConfigurationStore; import sonia.scm.store.JAXBConfigurationStore;
import sonia.scm.store.Store;
import sonia.scm.store.StoreConstants; import sonia.scm.store.StoreConstants;
import sonia.scm.util.IOUtil;
import sonia.scm.xml.AbstractXmlDAO; import sonia.scm.xml.AbstractXmlDAO;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
@@ -61,19 +63,21 @@ public class XmlRepositoryDAO
extends AbstractXmlDAO<Repository, XmlRepositoryDatabase> extends AbstractXmlDAO<Repository, XmlRepositoryDatabase>
implements PathBasedRepositoryDAO { implements PathBasedRepositoryDAO {
/**
* Field description
*/
public static final String STORE_NAME = "repositories"; public static final String STORE_NAME = "repositories";
private InitialRepositoryLocationResolver initialRepositoryLocationResolver; private InitialRepositoryLocationResolver initialRepositoryLocationResolver;
private final FileSystem fileSystem;
private final SCMContextProvider context;
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
@Inject @Inject
public XmlRepositoryDAO(InitialRepositoryLocationResolver initialRepositoryLocationResolver, SCMContextProvider context) { public XmlRepositoryDAO(InitialRepositoryLocationResolver initialRepositoryLocationResolver, FileSystem fileSystem, SCMContextProvider context) {
super(new JAXBConfigurationStore<>(XmlRepositoryDatabase.class, new File(context.getBaseDirectory(), StoreConstants.CONFIG_DIRECTORY_NAME + File.separator + STORE_NAME + StoreConstants.FILE_EXTENSION))); super(new JAXBConfigurationStore<>(XmlRepositoryDatabase.class,
IOUtil.mkdirs(new File(context.getBaseDirectory(), StoreConstants.CONFIG_DIRECTORY_NAME + File.separator + STORE_NAME + StoreConstants.FILE_EXTENSION)); new File(context.getBaseDirectory(), Store.CONFIG.getGlobalStoreDirectory()+File.separator+ STORE_NAME + StoreConstants.FILE_EXTENSION)));
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver; this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
this.fileSystem = fileSystem;
this.context = context;
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -95,14 +99,22 @@ public class XmlRepositoryDAO
@Override @Override
public void modify(Repository repository) { public void modify(Repository repository) {
db.remove(repository.getId()); RepositoryPath repositoryPath = findExistingRepositoryPath(repository).orElseThrow(() -> new InternalRepositoryException(repository, "path object for repository not found"));
add(repository); repositoryPath.setRepository(repository);
repositoryPath.setToBeSynchronized(true);
storeDB();
} }
@Override @Override
public void add(Repository repository) { public void add(Repository repository) {
String path = initialRepositoryLocationResolver.getDirectory(repository).getAbsolutePath(); InitialRepositoryLocation initialLocation = initialRepositoryLocationResolver.getRelativeRepositoryPath(repository);
RepositoryPath repositoryPath = new RepositoryPath(path, repository.getId(), repository.clone()); try {
fileSystem.create(initialLocation.getAbsolutePath());
} catch (IOException e) {
throw new InternalRepositoryException(repository, "could not create directory for repository data: " + initialLocation.getAbsolutePath(), e);
}
RepositoryPath repositoryPath = new RepositoryPath(initialLocation.getRelativePath(), repository.getId(), repository.clone());
repositoryPath.setToBeSynchronized(true);
synchronized (store) { synchronized (store) {
db.add(repositoryPath); db.add(repositoryPath);
storeDB(); storeDB();
@@ -134,6 +146,17 @@ public class XmlRepositoryDAO
return repository.clone(); return repository.clone();
} }
@Override
public void delete(Repository repository) {
Path directory = getPath(repository);
super.delete(repository);
try {
fileSystem.destroy(directory.toFile());
} catch (IOException e) {
throw new InternalRepositoryException(repository, "could not delete repository directory", e);
}
}
/** /**
* Method description * Method description
* *
@@ -145,15 +168,19 @@ public class XmlRepositoryDAO
} }
@Override @Override
public Path getPath(Repository repository) throws RepositoryPathNotFoundException { public Path getPath(Repository repository) {
Optional<RepositoryPath> repositoryPath = db.getPaths().stream() return context
.filter(repoPath -> repoPath.getId().equals(repository.getId())) .getBaseDirectory()
.findFirst(); .toPath()
if (!repositoryPath.isPresent()) { .resolve(
throw new RepositoryPathNotFoundException(); findExistingRepositoryPath(repository)
} else { .map(RepositoryPath::getPath)
.orElseThrow(() -> new InternalRepositoryException(repository, "could not find base directory for repository")));
return Paths.get(repositoryPath.get().getPath());
} }
private Optional<RepositoryPath> findExistingRepositoryPath(Repository repository) {
return db.values().stream()
.filter(repoPath -> repoPath.getId().equals(repository.getId()))
.findAny();
} }
} }

View File

@@ -98,16 +98,6 @@ public class XmlRepositoryDatabase implements XmlDatabase<RepositoryPath> {
return get(id) != null; return get(id) != null;
} }
public boolean contains(Repository repository)
{
return repositoryPathMap.containsKey(createKey(repository));
}
public void remove(Repository repository)
{
repositoryPathMap.remove(createKey(repository));
}
@Override @Override
public RepositoryPath remove(String id) public RepositoryPath remove(String id)
{ {
@@ -129,11 +119,6 @@ public class XmlRepositoryDatabase implements XmlDatabase<RepositoryPath> {
return repositoryPathMap.values(); return repositoryPathMap.values();
} }
public Collection<RepositoryPath> getPaths() {
return repositoryPathMap.values();
}
public Repository get(NamespaceAndName namespaceAndName) { public Repository get(NamespaceAndName namespaceAndName) {
RepositoryPath repositoryPath = repositoryPathMap.get(createKey(namespaceAndName)); RepositoryPath repositoryPath = repositoryPathMap.get(createKey(namespaceAndName));
if (repositoryPath != null) { if (repositoryPath != null) {

View File

@@ -31,10 +31,12 @@
package sonia.scm.repository.xml; package sonia.scm.repository.xml;
import sonia.scm.SCMContext;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.store.StoreConstants; import sonia.scm.store.StoreConstants;
import sonia.scm.store.StoreException; import sonia.scm.store.StoreException;
import sonia.scm.util.IOUtil;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
@@ -42,6 +44,8 @@ import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.io.File; import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@@ -62,14 +66,20 @@ public class XmlRepositoryMapAdapter extends XmlAdapter<XmlRepositoryList, Map<S
// marshall the repo_path/metadata.xml files // marshall the repo_path/metadata.xml files
for (RepositoryPath repositoryPath : repositoryPaths.getRepositoryPaths()) { for (RepositoryPath repositoryPath : repositoryPaths.getRepositoryPaths()) {
File dir = new File(repositoryPath.getPath()); if (repositoryPath.toBeSynchronized()) {
if (!dir.exists()){
IOUtil.mkdirs(dir); File baseDirectory = SCMContext.getContext().getBaseDirectory();
Path dir = baseDirectory.toPath().resolve(repositoryPath.getPath());
if (!Files.isDirectory(dir)) {
throw new InternalRepositoryException(repositoryPath.getRepository(), "repository path not found");
}
marshaller.marshal(repositoryPath.getRepository(), getRepositoryMetadataFile(dir.toFile()));
repositoryPath.setToBeSynchronized(false);
} }
marshaller.marshal(repositoryPath.getRepository(), getRepositoryMetadataFile(dir));
} }
} catch (JAXBException ex) { } catch (JAXBException ex) {
throw new StoreException("failed to marshall repository database", ex); throw new StoreException("failed to marshal repository database", ex);
} }
return repositoryPaths; return repositoryPaths;
@@ -81,18 +91,21 @@ public class XmlRepositoryMapAdapter extends XmlAdapter<XmlRepositoryList, Map<S
} }
@Override @Override
public Map<String, RepositoryPath> unmarshal(XmlRepositoryList repositories) { public Map<String, RepositoryPath> unmarshal(XmlRepositoryList repositoryPaths) {
Map<String, RepositoryPath> repositoryPathMap = new LinkedHashMap<>(); Map<String, RepositoryPath> repositoryPathMap = new LinkedHashMap<>();
try { try {
JAXBContext context = JAXBContext.newInstance(Repository.class); JAXBContext context = JAXBContext.newInstance(Repository.class);
Unmarshaller unmarshaller = context.createUnmarshaller(); Unmarshaller unmarshaller = context.createUnmarshaller();
for (RepositoryPath repositoryPath : repositories) { for (RepositoryPath repositoryPath : repositoryPaths) {
Repository repository = (Repository) unmarshaller.unmarshal(getRepositoryMetadataFile(new File(repositoryPath.getPath()))); SCMContextProvider contextProvider = SCMContext.getContext();
File baseDirectory = contextProvider.getBaseDirectory();
Repository repository = (Repository) unmarshaller.unmarshal(getRepositoryMetadataFile(baseDirectory.toPath().resolve(repositoryPath.getPath()).toFile()));
repositoryPath.setRepository(repository); repositoryPath.setRepository(repository);
repositoryPathMap.put(XmlRepositoryDatabase.createKey(repository), repositoryPath); repositoryPathMap.put(XmlRepositoryDatabase.createKey(repository), repositoryPath);
} }
} catch (JAXBException ex) { } catch (JAXBException ex) {
throw new StoreException("failed to unmarshall object", ex); throw new StoreException("failed to unmarshal object", ex);
} }
return repositoryPathMap; return repositoryPathMap;
} }

View File

@@ -34,14 +34,12 @@ package sonia.scm.store;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.repository.InternalRepositoryException; import sonia.scm.SCMContextProvider;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.util.IOUtil; import sonia.scm.util.IOUtil;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -56,55 +54,54 @@ public abstract class FileBasedStoreFactory {
* the logger for FileBasedStoreFactory * the logger for FileBasedStoreFactory
*/ */
private static final Logger LOG = LoggerFactory.getLogger(FileBasedStoreFactory.class); private static final Logger LOG = LoggerFactory.getLogger(FileBasedStoreFactory.class);
private SCMContextProvider contextProvider;
private RepositoryLocationResolver repositoryLocationResolver; private RepositoryLocationResolver repositoryLocationResolver;
private Store store;
private final String dataDirectoryName; private File storeDirectory;
private File dataDirectory; protected FileBasedStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, Store store) {
this.contextProvider = contextProvider;
protected FileBasedStoreFactory(RepositoryLocationResolver repositoryLocationResolver, String dataDirectoryName) {
this.repositoryLocationResolver = repositoryLocationResolver; this.repositoryLocationResolver = repositoryLocationResolver;
this.dataDirectoryName = dataDirectoryName; this.store = store;
} }
//~--- get methods ---------------------------------------------------------- protected File getStoreLocation(StoreParameters storeParameters) {
/** return getStoreLocation(storeParameters.getName(), storeParameters.getType(), storeParameters.getRepository());
* Returns data directory for given name.
*
* @param name name of data directory
*
* @return data directory
*/
protected File getDirectory(String name) {
if (dataDirectory == null) {
dataDirectory = new File(repositoryLocationResolver.getGlobalStoreDirectory(), dataDirectoryName);
LOG.debug("get data directory {}", dataDirectory);
} }
File storeDirectory = new File(dataDirectory, name); protected File getStoreLocation(String name, Class type, Repository repository) {
if (storeDirectory == null) {
if (repository != null) {
LOG.debug("create store with type :{}, name:{} and repository {}", type, name, repository.getNamespaceAndName());
storeDirectory = this.getStoreDirectory(store, repository);
} else {
LOG.debug("create store with type :{} and name:{} ", type, name);
storeDirectory = this.getStoreDirectory(store);
}
IOUtil.mkdirs(storeDirectory); IOUtil.mkdirs(storeDirectory);
return storeDirectory; }
return new File(this.storeDirectory, name);
} }
/** /**
* Returns data directory for given name. * Get the store directory of a specific repository
* * @param store the type of the store
* @param name name of data directory * @param repository the repo
* * @return the store directory of a specific repository
* @return data directory
*/ */
protected File getDirectory(String name, Repository repository) { private File getStoreDirectory(Store store, Repository repository) {
if (dataDirectory == null) { return new File (repositoryLocationResolver.getRepositoryDirectory(repository), store.getRepositoryStoreDirectory());
try {
dataDirectory = new File(repositoryLocationResolver.getStoresDirectory(repository), dataDirectoryName);
} catch (IOException e) {
throw new InternalRepositoryException(repository, MessageFormat.format("Error on getting the store directory {0} of the repository {1}", dataDirectory.getAbsolutePath(), repository.getNamespaceAndName()), e);
} }
LOG.debug("create data directory {}", dataDirectory);
}
File storeDirectory = new File(dataDirectory, name); /**
IOUtil.mkdirs(storeDirectory); * Get the global store directory
return storeDirectory; * @param store the type of the store
* @return the global store directory
*/
private File getStoreDirectory(Store store) {
return new File(contextProvider.getBaseDirectory(), store.getGlobalStoreDirectory());
} }
} }

View File

@@ -31,14 +31,17 @@
package sonia.scm.store; package sonia.scm.store;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.util.IOUtil;
import java.io.File;
/** /**
* File based store factory. * File based store factory.
@@ -48,8 +51,6 @@ import sonia.scm.security.KeyGenerator;
@Singleton @Singleton
public class FileBlobStoreFactory extends FileBasedStoreFactory implements BlobStoreFactory { public class FileBlobStoreFactory extends FileBasedStoreFactory implements BlobStoreFactory {
private static final String DIRECTORY_NAME = "blob";
/** /**
* the logger for FileBlobStoreFactory * the logger for FileBlobStoreFactory
*/ */
@@ -60,21 +61,22 @@ public class FileBlobStoreFactory extends FileBasedStoreFactory implements BlobS
/** /**
* Constructs a new instance. * Constructs a new instance.
* *
* @param repositoryLocationResolver * @param repositoryLocationResolver location resolver
* @param keyGenerator key generator * @param keyGenerator key generator
*/ */
@Inject @Inject
public FileBlobStoreFactory(RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) { public FileBlobStoreFactory(SCMContextProvider contextProvider ,RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) {
super(repositoryLocationResolver, DIRECTORY_NAME); super(contextProvider, repositoryLocationResolver, Store.BLOB);
this.keyGenerator = keyGenerator; this.keyGenerator = keyGenerator;
} }
@Override @Override
@SuppressWarnings("unchecked")
public BlobStore getStore(StoreParameters storeParameters) { public BlobStore getStore(StoreParameters storeParameters) {
if (storeParameters.getRepository() != null) { File storeLocation = getStoreLocation(storeParameters);
return new FileBlobStore(keyGenerator, getDirectory(storeParameters.getName(), storeParameters.getRepository())); IOUtil.mkdirs(storeLocation);
} return new FileBlobStore(keyGenerator, storeLocation);
return new FileBlobStore(keyGenerator, getDirectory(storeParameters.getName()));
} }
} }

View File

@@ -1,10 +1,10 @@
/** /**
* Copyright (c) 2010, Sebastian Sdorra * Copyright (c) 2010, Sebastian Sdorra
* All rights reserved. * All rights reserved.
* * <p>
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
* * <p>
* 1. Redistributions of source code must retain the above copyright notice, * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, * 2. Redistributions in binary form must reproduce the above copyright notice,
@@ -13,7 +13,7 @@
* 3. Neither the name of SCM-Manager; nor the names of its * 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this * contributors may be used to endorse or promote products derived from this
* software without specific prior written permission. * software without specific prior written permission.
* * <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,109 +24,43 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* * <p>
* http://bitbucket.org/sdorra/scm-manager * http://bitbucket.org/sdorra/scm-manager
*
*/ */
package sonia.scm.store; package sonia.scm.store;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.io.File;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Singleton @Singleton
public class JAXBConfigurationEntryStoreFactory public class JAXBConfigurationEntryStoreFactory extends FileBasedStoreFactory
implements ConfigurationEntryStoreFactory implements ConfigurationEntryStoreFactory {
{
/**
* the logger for JAXBConfigurationEntryStoreFactory
*/
private static final Logger logger =
LoggerFactory.getLogger(JAXBConfigurationEntryStoreFactory.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param keyGenerator
* @param context
*/
@Inject
public JAXBConfigurationEntryStoreFactory(KeyGenerator keyGenerator,
SCMContextProvider context)
{
this.keyGenerator = keyGenerator;
directory = new File(context.getBaseDirectory(),
StoreConstants.CONFIG_DIRECTORY_NAME);
IOUtil.mkdirs(directory);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param type
* @param name
*
* @return
*/
private ConfigurationEntryStore getStore(Class type, String name)
{
logger.debug("create new configuration store for type {} with name {}",
type, name);
//J-
return new JAXBConfigurationEntryStore(
new File(directory,name.concat(StoreConstants.FILE_EXTENSION)),
keyGenerator,
type
);
//J+
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private File directory;
/** Field description */
private KeyGenerator keyGenerator; private KeyGenerator keyGenerator;
@Override @Inject
public ConfigurationEntryStore getStore(StoreParameters storeParameters) { public JAXBConfigurationEntryStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) {
if (storeParameters.getRepository() != null){ super(contextProvider, repositoryLocationResolver, Store.CONFIG);
return getStore(storeParameters.getType(),storeParameters.getName(),storeParameters.getRepository()); this.keyGenerator = keyGenerator;
}
return getStore(storeParameters.getType(),storeParameters.getName());
} }
private ConfigurationEntryStore getStore(Class type, String name, Repository repository) { @Override
return null; @SuppressWarnings("unchecked")
public ConfigurationEntryStore getStore(StoreParameters storeParameters) {
return new JAXBConfigurationEntryStore(getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository()), keyGenerator, storeParameters.getType());
} }
} }

View File

@@ -32,15 +32,8 @@ package sonia.scm.store;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.slf4j.Logger; import sonia.scm.SCMContextProvider;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.util.IOUtil;
import java.io.File;
import java.io.IOException;
/** /**
* JAXB implementation of {@link StoreFactory}. * JAXB implementation of {@link StoreFactory}.
@@ -48,14 +41,7 @@ import java.io.IOException;
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Singleton @Singleton
public class JAXBConfigurationStoreFactory implements ConfigurationStoreFactory { public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory implements ConfigurationStoreFactory {
/**
* the logger for JAXBConfigurationStoreFactory
*/
private static final Logger LOG = LoggerFactory.getLogger(JAXBConfigurationStoreFactory.class);
private RepositoryLocationResolver repositoryLocationResolver;
/** /**
* Constructs a new instance. * Constructs a new instance.
@@ -63,57 +49,13 @@ public class JAXBConfigurationStoreFactory implements ConfigurationStoreFactory
* @param repositoryLocationResolver Resolver to get the repository Directory * @param repositoryLocationResolver Resolver to get the repository Directory
*/ */
@Inject @Inject
public JAXBConfigurationStoreFactory(RepositoryLocationResolver repositoryLocationResolver) { public JAXBConfigurationStoreFactory(SCMContextProvider contextProvider, RepositoryLocationResolver repositoryLocationResolver) {
this.repositoryLocationResolver = repositoryLocationResolver; super(contextProvider, repositoryLocationResolver, Store.CONFIG);
}
/**
* Get or create the global config directory.
*
* @return the global config directory.
*/
private File getGlobalConfigDirectory() {
File baseDirectory = repositoryLocationResolver.getInitialBaseDirectory();
File configDirectory = new File(baseDirectory, StoreConstants.CONFIG_DIRECTORY_NAME);
return getOrCreateFile(configDirectory);
}
/**
* Get or create the repository specific config directory.
*
* @return the repository specific config directory.
*/
private File getRepositoryConfigDirectory(Repository repository) {
File baseDirectory = null;
try {
baseDirectory = repositoryLocationResolver.getConfigDirectory(repository);
} catch (IOException e) {
e.printStackTrace();
}
File configDirectory = new File(baseDirectory, StoreConstants.CONFIG_DIRECTORY_NAME);
return getOrCreateFile(configDirectory);
}
private File getOrCreateFile(File directory) {
if (!directory.exists()) {
IOUtil.mkdirs(directory);
}
return directory;
}
private JAXBConfigurationStore getStore(Class type, String name, File configDirectory) {
File configFile = new File(configDirectory, name.concat(StoreConstants.FILE_EXTENSION));
LOG.debug("create store for {} at {}", type.getName(), configFile.getPath());
return new JAXBConfigurationStore<>(type, configFile);
} }
@Override @Override
@SuppressWarnings("unchecked")
public JAXBConfigurationStore getStore(StoreParameters storeParameters) { public JAXBConfigurationStore getStore(StoreParameters storeParameters) {
try { return new JAXBConfigurationStore(storeParameters.getType(), getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository()));
return getStore(storeParameters.getType(), storeParameters.getName(),repositoryLocationResolver.getRepositoryDirectory(storeParameters.getRepository()));
} catch (IOException e) {
throw new InternalRepositoryException(storeParameters.getRepository(),"Error on getting the store of the repository"+ storeParameters.getRepository().getNamespaceAndName());
}
} }
} }

View File

@@ -40,8 +40,12 @@ import com.google.inject.Singleton;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.util.IOUtil;
import java.io.File;
/** /**
* *
@@ -49,26 +53,22 @@ import sonia.scm.security.KeyGenerator;
*/ */
@Singleton @Singleton
public class JAXBDataStoreFactory extends FileBasedStoreFactory public class JAXBDataStoreFactory extends FileBasedStoreFactory
implements DataStoreFactory implements DataStoreFactory {
{
private static final Logger logger = LoggerFactory.getLogger(JAXBDataStoreFactory.class); private static final Logger logger = LoggerFactory.getLogger(JAXBDataStoreFactory.class);
private static final String DIRECTORY_NAME = "data";
private KeyGenerator keyGenerator; private KeyGenerator keyGenerator;
@Inject @Inject
public JAXBDataStoreFactory(RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) { public JAXBDataStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) {
super(repositoryLocationResolver, DIRECTORY_NAME); super(contextProvider, repositoryLocationResolver, Store.DATA);
this.keyGenerator = keyGenerator; this.keyGenerator = keyGenerator;
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public DataStore getStore(StoreParameters storeParameters) { public DataStore getStore(StoreParameters storeParameters) {
logger.debug("create new store for type {} with name {}", storeParameters.getType(), storeParameters.getName()); File storeLocation = getStoreLocation(storeParameters);
if (storeParameters.getRepository() != null) { IOUtil.mkdirs(storeLocation);
return new JAXBDataStore(keyGenerator, storeParameters.getType(), getDirectory(storeParameters.getName(), storeParameters.getRepository())); return new JAXBDataStore(keyGenerator, storeParameters.getType(), storeLocation);
}
return new JAXBDataStore(keyGenerator, storeParameters.getType(), getDirectory(storeParameters.getName()));
} }
} }

View File

@@ -0,0 +1,49 @@
package sonia.scm.store;
import java.io.File;
public enum Store {
CONFIG("config"),
DATA("data"),
BLOB("blob");
private static final String GLOBAL_STORE_BASE_DIRECTORY = "var";
private String directory;
Store(String directory) {
this.directory = directory;
}
/**
* Get the relkative store directory path to be stored in the repository root
* <p>
* The repository store directories are:
* repo_base_dir/config/
* repo_base_dir/blob/
* repo_base_dir/data/
*
* @return the relative store directory path to be stored in the repository root
*/
public String getRepositoryStoreDirectory() {
return directory;
}
/**
* Get the relative store directory path to be stored in the global root
* <p>
* The global store directories are:
* base_dir/config/
* base_dir/var/blob/
* base_dir/var/data/
*
* @return the relative store directory path to be stored in the global root
*/
public String getGlobalStoreDirectory() {
if (this.equals(CONFIG)) {
return directory;
}
return GLOBAL_STORE_BASE_DIRECTORY + File.separator + directory;
}
}

View File

@@ -63,7 +63,7 @@ public abstract class AbstractXmlDAO<I extends ModelObject,
* the logger for XmlGroupDAO * the logger for XmlGroupDAO
*/ */
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(XmlGroupDAO.class); LoggerFactory.getLogger(AbstractXmlDAO.class);
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------

View File

@@ -0,0 +1,111 @@
package sonia.scm.repository.xml;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.SCMContextProvider;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.io.FileSystem;
import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.Repository;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.StoreParameters;
import java.io.IOException;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.codehaus.groovy.runtime.InvokerHelper.asList;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static sonia.scm.repository.xml.XmlRepositoryDAO.STORE_NAME;
@RunWith(MockitoJUnitRunner.class)
@Ignore
public class XmlRepositoryDAOTest {
@Mock
private ConfigurationStoreFactory storeFactory;
@Mock
private ConfigurationStore<XmlRepositoryDatabase> store;
@Mock
private XmlRepositoryDatabase db;
@Mock
private SCMContextProvider context;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private final FileSystem fileSystem = new DefaultFileSystem();
@Before
public void init() throws IOException {
StoreParameters storeParameters = new StoreParameters().withType(XmlRepositoryDatabase.class).withName(STORE_NAME).build();
when(storeFactory.getStore(storeParameters)).thenReturn(store);
when(store.get()).thenReturn(db);
when(context.getBaseDirectory()).thenReturn(temporaryFolder.newFolder());
}
@Test
public void addShouldCreateNewRepositoryPathWithRelativePath() {
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(context);
XmlRepositoryDAO dao = new XmlRepositoryDAO(initialRepositoryLocationResolver, fileSystem, context);
dao.add(new Repository("id", "git", "namespace", "name"));
verify(db).add(argThat(repositoryPath -> {
assertThat(repositoryPath.getId()).isEqualTo("id");
assertThat(repositoryPath.getPath()).isEqualTo(InitialRepositoryLocationResolver.DEFAULT_REPOSITORY_PATH + "/id");
return true;
}));
verify(store).set(db);
}
@Test
public void modifyShouldStoreChangedRepository() {
Repository oldRepository = new Repository("id", "old", null, null);
RepositoryPath repositoryPath = new RepositoryPath("/path", "id", oldRepository);
when(db.values()).thenReturn(asList(repositoryPath));
XmlRepositoryDAO dao = new XmlRepositoryDAO(new InitialRepositoryLocationResolver(context), fileSystem, context);
Repository newRepository = new Repository("id", "new", null, null);
dao.modify(newRepository);
assertThat(repositoryPath.getRepository()).isSameAs(newRepository);
verify(store).set(db);
}
@Test
public void shouldGetPathInBaseDirectoryForRelativePath() {
Repository existingRepository = new Repository("id", "old", null, null);
RepositoryPath repositoryPath = new RepositoryPath("path", "id", existingRepository);
when(db.values()).thenReturn(asList(repositoryPath));
XmlRepositoryDAO dao = new XmlRepositoryDAO(new InitialRepositoryLocationResolver(context), fileSystem, context);
Path path = dao.getPath(existingRepository);
assertThat(path.toString()).isEqualTo(context.getBaseDirectory().getPath() + "/path");
}
@Test
public void shouldGetPathInBaseDirectoryForAbsolutePath() {
Repository existingRepository = new Repository("id", "old", null, null);
RepositoryPath repositoryPath = new RepositoryPath("/tmp/path", "id", existingRepository);
when(db.values()).thenReturn(asList(repositoryPath));
XmlRepositoryDAO dao = new XmlRepositoryDAO(new InitialRepositoryLocationResolver(context), fileSystem, context);
Path path = dao.getPath(existingRepository);
assertThat(path.toString()).isEqualTo("/tmp/path");
}
}

View File

@@ -34,8 +34,15 @@ package sonia.scm.store;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.junit.Test;
import sonia.scm.repository.Repository;
import sonia.scm.security.UUIDKeyGenerator; import sonia.scm.security.UUIDKeyGenerator;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
@@ -52,6 +59,25 @@ public class FileBlobStoreTest extends BlobStoreTestBase
@Override @Override
protected BlobStoreFactory createBlobStoreFactory() protected BlobStoreFactory createBlobStoreFactory()
{ {
return new FileBlobStoreFactory(repositoryLocationResolver, new UUIDKeyGenerator()); return new FileBlobStoreFactory(contextProvider, repositoryLocationResolver, new UUIDKeyGenerator());
}
@Test
@SuppressWarnings("unchecked")
public void shouldStoreAndLoadInRepository() {
BlobStore store = createBlobStoreFactory().getStore(new StoreParameters()
.withType(StoreObject.class)
.withName("test")
.forRepository(new Repository("id", "git", "ns", "n"))
.build());
Blob createdBlob = store.create("abc");
List<Blob> storedBlobs = store.getAll();
assertNotNull(createdBlob);
assertThat(storedBlobs)
.isNotNull()
.hasSize(1)
.usingElementComparatorOnFields("id").containsExactly(createdBlob);
} }
} }

View File

@@ -144,6 +144,16 @@ public class JAXBConfigurationEntryStoreTest
assertEquals("repository:create", ap.getPermission()); assertEquals("repository:create", ap.getPermission());
} }
@Test
public void shouldStoreAndLoadInRepository() throws IOException
{
repoStore.put("abc", new StoreObject("abc_value"));
StoreObject storeObject = repoStore.get("abc");
assertNotNull(storeObject);
assertEquals("abc_value", storeObject.getValue());
}
/** /**
* Method description * Method description
* *
@@ -153,7 +163,7 @@ public class JAXBConfigurationEntryStoreTest
@Override @Override
protected ConfigurationEntryStoreFactory createConfigurationStoreFactory() protected ConfigurationEntryStoreFactory createConfigurationStoreFactory()
{ {
return new JAXBConfigurationEntryStoreFactory(new UUIDKeyGenerator(), contextProvider); return new JAXBConfigurationEntryStoreFactory(contextProvider, repositoryLocationResolver, new UUIDKeyGenerator());
} }
/** /**

View File

@@ -32,6 +32,14 @@
package sonia.scm.store; package sonia.scm.store;
import org.junit.Test;
import sonia.scm.repository.Repository;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/** /**
* Unit tests for {@link JAXBConfigurationStore}. * Unit tests for {@link JAXBConfigurationStore}.
* *
@@ -42,6 +50,24 @@ public class JAXBConfigurationStoreTest extends StoreTestBase {
@Override @Override
protected ConfigurationStoreFactory createStoreFactory() protected ConfigurationStoreFactory createStoreFactory()
{ {
return new JAXBConfigurationStoreFactory(repositoryLocationResolver); return new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver);
}
@Test
@SuppressWarnings("unchecked")
public void shouldStoreAndLoadInRepository() throws IOException
{
ConfigurationStore<StoreObject> store = createStoreFactory().getStore(new StoreParameters()
.withType(StoreObject.class)
.withName("test")
.forRepository(new Repository("id", "git", "ns", "n"))
.build());
store.set(new StoreObject("value"));
StoreObject storeObject = store.get();
assertNotNull(storeObject);
assertEquals("value", storeObject.getValue());
} }
} }

View File

@@ -34,9 +34,15 @@ package sonia.scm.store;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.junit.Test;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.security.UUIDKeyGenerator; import sonia.scm.security.UUIDKeyGenerator;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
@@ -52,7 +58,7 @@ public class JAXBDataStoreTest extends DataStoreTestBase {
@Override @Override
protected DataStoreFactory createDataStoreFactory() protected DataStoreFactory createDataStoreFactory()
{ {
return new JAXBDataStoreFactory(repositoryLocationResolver, new UUIDKeyGenerator()); return new JAXBDataStoreFactory(contextProvider, repositoryLocationResolver, new UUIDKeyGenerator());
} }
@Override @Override
@@ -73,4 +79,14 @@ public class JAXBDataStoreTest extends DataStoreTestBase {
.build(); .build();
return createDataStoreFactory().getStore(params); return createDataStoreFactory().getStore(params);
} }
@Test
public void shouldStoreAndLoadInRepository() throws IOException
{
repoStore.put("abc", new StoreObject("abc_value"));
StoreObject storeObject = repoStore.get("abc");
assertNotNull(storeObject);
assertEquals("abc_value", storeObject.getValue());
}
} }

View File

@@ -136,7 +136,7 @@ public class ScmTransportProtocol extends TransportProtocol
*/ */
@Override @Override
public Transport open(URIish uri, Repository local, String remoteName) public Transport open(URIish uri, Repository local, String remoteName)
throws NotSupportedException, TransportException throws TransportException
{ {
File localDirectory = local.getDirectory(); File localDirectory = local.getDirectory();
File path = local.getFS().resolve(localDirectory, uri.getPath()); File path = local.getFS().resolve(localDirectory, uri.getPath());

View File

@@ -38,6 +38,7 @@ package sonia.scm.repository;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -88,6 +89,8 @@ 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;
@@ -97,19 +100,13 @@ public class GitRepositoryHandler
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param storeFactory
* @param fileSystem
* @param scheduler
* @param repositoryLocationResolver
*/
@Inject @Inject
public GitRepositoryHandler(ConfigurationStoreFactory storeFactory, FileSystem fileSystem, Scheduler scheduler, RepositoryLocationResolver repositoryLocationResolver, GitWorkdirFactory workdirFactory) public GitRepositoryHandler(ConfigurationStoreFactory storeFactory,
Scheduler scheduler,
RepositoryLocationResolver repositoryLocationResolver,
GitWorkdirFactory workdirFactory)
{ {
super(storeFactory, fileSystem, repositoryLocationResolver); super(storeFactory, repositoryLocationResolver);
this.scheduler = scheduler; this.scheduler = scheduler;
this.workdirFactory = workdirFactory; this.workdirFactory = workdirFactory;
} }
@@ -182,12 +179,23 @@ public class GitRepositoryHandler
return getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION); return getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION);
} }
public GitWorkdirFactory getWorkdirFactory() {
return workdirFactory;
}
public String getRepositoryId(StoredConfig gitConfig) {
return gitConfig.getString(GitRepositoryHandler.CONFIG_SECTION_SCMM, null, GitRepositoryHandler.CONFIG_KEY_REPOSITORY_ID);
}
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@Override @Override
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();
config.setString(CONFIG_SECTION_SCMM, null, CONFIG_KEY_REPOSITORY_ID, repository.getId());
config.save();
} }
} }
@@ -224,22 +232,4 @@ public class GitRepositoryHandler
{ {
return GitConfig.class; return GitConfig.class;
} }
/**
* Method description
*
*
* @param directory
*
* @return
*/
@Override
protected boolean isRepository(File directory)
{
return new File(directory, DIRECTORY_REFS).exists();
}
public GitWorkdirFactory getWorkdirFactory() {
return workdirFactory;
}
} }

View File

@@ -36,6 +36,7 @@ package sonia.scm.web;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.PostReceiveHook; import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceiveCommand;
@@ -44,11 +45,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.RepositoryUtil;
import sonia.scm.repository.spi.GitHookContextProvider; import sonia.scm.repository.spi.GitHookContextProvider;
import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.HookEventFacade;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -128,14 +127,14 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook
try try
{ {
Repository repository = rpack.getRepository(); Repository repository = rpack.getRepository();
String id = resolveRepositoryId(repository); String repositoryId = resolveRepositoryId(repository);
logger.trace("resolved repository to id {}", id); logger.trace("resolved repository to {}", repositoryId);
GitHookContextProvider context = new GitHookContextProvider(rpack, GitHookContextProvider context = new GitHookContextProvider(rpack,
receiveCommands); receiveCommands);
hookEventFacade.handle(id).fireHookEvent(type, context); hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
} }
catch (Exception ex) catch (Exception ex)
@@ -187,20 +186,10 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook
* *
* @throws IOException * @throws IOException
*/ */
private String resolveRepositoryId(Repository repository) throws IOException private String resolveRepositoryId(Repository repository)
{ {
File directory; StoredConfig gitConfig = repository.getConfig();
return handler.getRepositoryId(gitConfig);
if (repository.isBare())
{
directory = repository.getDirectory();
}
else
{
directory = repository.getWorkTree();
}
return RepositoryUtil.getRepositoryId(handler, directory);
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------

View File

@@ -33,29 +33,19 @@ class GitConfigurationForm extends React.Component<Props, State> {
this.state = { ...props.initialConfiguration }; this.state = { ...props.initialConfiguration };
} }
isValid = () => {
return !!this.state.repositoryDirectory;
};
handleChange = (value: any, name: string) => { handleChange = (value: any, name: string) => {
this.setState({ this.setState({
[name]: value [name]: value
}, () => this.props.onConfigurationChange(this.state, this.isValid())); }, () => this.props.onConfigurationChange(this.state, true));
}; };
render() { render() {
const { repositoryDirectory, gcExpression, disabled } = this.state; const { gcExpression, disabled } = this.state;
const { readOnly, t } = this.props; const { readOnly, t } = this.props;
return ( return (
<> <>
<InputField name="repositoryDirectory"
label={t("scm-git-plugin.config.directory")}
helpText={t("scm-git-plugin.config.directoryHelpText")}
value={repositoryDirectory}
onChange={this.handleChange}
disabled={readOnly}
/>
<InputField name="gcExpression" <InputField name="gcExpression"
label={t("scm-git-plugin.config.gcExpression")} label={t("scm-git-plugin.config.gcExpression")}
helpText={t("scm-git-plugin.config.gcExpressionHelpText")} helpText={t("scm-git-plugin.config.gcExpressionHelpText")}

View File

@@ -1,7 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { Title, GlobalConfiguration } from "@scm-manager/ui-components"; import { Title, Configuration } from "@scm-manager/ui-components";
import GitConfigurationForm from "./GitConfigurationForm"; import GitConfigurationForm from "./GitConfigurationForm";
type Props = { type Props = {
@@ -22,7 +22,7 @@ class GitGlobalConfiguration extends React.Component<Props> {
return ( return (
<div> <div>
<Title title={t("scm-git-plugin.config.title")}/> <Title title={t("scm-git-plugin.config.title")}/>
<GlobalConfiguration link={link} render={props => <GitConfigurationForm {...props} />}/> <Configuration link={link} render={props => <GitConfigurationForm {...props} />}/>
</div> </div>
); );
} }

View File

@@ -8,8 +8,6 @@
"config": { "config": {
"link": "Git", "link": "Git",
"title": "Git Configuration", "title": "Git Configuration",
"directory": "Repository Directory",
"directoryHelpText": "Location of the Git repositories.",
"gcExpression": "GC Cron Expression", "gcExpression": "GC Cron Expression",
"gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.", "gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.",
"disabled": "Disabled", "disabled": "Disabled",

View File

@@ -37,26 +37,17 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.schedule.Scheduler; import sonia.scm.schedule.Scheduler;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.StoreFactory;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
/** /**
*
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@@ -71,8 +62,8 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock @Mock
private GitWorkdirFactory gitWorkdirFactory; private GitWorkdirFactory gitWorkdirFactory;
RepositoryLocationResolver repositoryLocationResolver ; RepositoryLocationResolver repositoryLocationResolver;
private Path repoDir;
@Override @Override
protected void checkDirectory(File directory) { protected void checkDirectory(File directory) {
@@ -95,16 +86,10 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Override @Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
File directory) throws RepositoryPathNotFoundException { File directory) {
DefaultFileSystem fileSystem = new DefaultFileSystem(); repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider));
PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(contextProvider,fileSystem);
repositoryLocationResolver = new RepositoryLocationResolver(repoDao, initialRepositoryLocationResolver);
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
fileSystem, scheduler, repositoryLocationResolver, gitWorkdirFactory); scheduler, repositoryLocationResolver, gitWorkdirFactory);
repoDir = directory.toPath();
when(repoDao.getPath(any())).thenReturn(repoDir);
repositoryHandler.init(contextProvider); repositoryHandler.init(contextProvider);
GitConfig config = new GitConfig(); GitConfig config = new GitConfig();
@@ -118,16 +103,15 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test @Test
public void getDirectory() { public void getDirectory() {
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
new DefaultFileSystem(), scheduler, repositoryLocationResolver, gitWorkdirFactory); scheduler, repositoryLocationResolver, gitWorkdirFactory);
Repository repository = new Repository("id", "git", "Space", "Name");
GitConfig config = new GitConfig(); GitConfig config = new GitConfig();
config.setDisabled(false); config.setDisabled(false);
config.setGcExpression("gc exp"); config.setGcExpression("gc exp");
repositoryHandler.setConfig(config); repositoryHandler.setConfig(config);
initRepository();
File path = repositoryHandler.getDirectory(repository); File path = repositoryHandler.getDirectory(repository);
assertEquals(repoDir.toString()+File.separator+InitialRepositoryLocationResolver.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); assertEquals(repoPath.toString() + File.separator + AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
} }
} }

View File

@@ -94,12 +94,7 @@ public class HgImportHandler extends AbstactImportHandler
INIConfiguration c = reader.read(hgrc); INIConfiguration c = reader.read(hgrc);
INISection web = c.getSection("web"); INISection web = c.getSection("web");
if (web == null) if (web != null) {
{
handler.appendWebSection(c);
}
else
{
repository.setDescription(web.getParameter("description")); repository.setDescription(web.getParameter("description"));
String contact = web.getParameter("contact"); String contact = web.getParameter("contact");
@@ -112,16 +107,7 @@ public class HgImportHandler extends AbstactImportHandler
{ {
logger.warn("contact {} is not a valid mail address", contact); logger.warn("contact {} is not a valid mail address", contact);
} }
handler.setWebParameter(web);
} }
// issue-97
handler.registerMissingHook(c, repositoryName);
INIConfigurationWriter writer = new INIConfigurationWriter();
writer.write(c, hgrc);
} }
else else
{ {

View File

@@ -41,12 +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.DirectoryFileFilter;
import sonia.scm.io.ExtendedCommand; import sonia.scm.io.ExtendedCommand;
import sonia.scm.io.FileSystem;
import sonia.scm.io.INIConfiguration; import sonia.scm.io.INIConfiguration;
import sonia.scm.io.INIConfigurationReader; import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter; import sonia.scm.io.INIConfigurationWriter;
@@ -56,7 +55,6 @@ import sonia.scm.repository.spi.HgRepositoryServiceProvider;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.util.IOUtil; import sonia.scm.util.IOUtil;
import sonia.scm.util.SystemUtil; import sonia.scm.util.SystemUtil;
import sonia.scm.util.Util;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
@@ -105,22 +103,17 @@ public class HgRepositoryHandler
/** Field description */ /** Field description */
public static final String PATH_HGRC = public static final String PATH_HGRC =
".hg".concat(File.separator).concat("hgrc"); ".hg".concat(File.separator).concat("hgrc");
private static final String CONFIG_SECTION_SCMM = "scmm";
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
* @param storeFactory
* @param fileSystem
* @param hgContextProvider
* @param repositoryLocationResolver
*/
@Inject @Inject
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory, FileSystem fileSystem, public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
Provider<HgContext> hgContextProvider, RepositoryLocationResolver repositoryLocationResolver) Provider<HgContext> hgContextProvider,
RepositoryLocationResolver repositoryLocationResolver)
{ {
super(storeFactory, fileSystem, repositoryLocationResolver); super(storeFactory, repositoryLocationResolver);
this.hgContextProvider = hgContextProvider; this.hgContextProvider = hgContextProvider;
try try
@@ -179,7 +172,6 @@ public class HgRepositoryHandler
public void init(SCMContextProvider context) public void init(SCMContextProvider context)
{ {
super.init(context); super.init(context);
registerMissingHooks();
writePythonScripts(context); writePythonScripts(context);
// fix wrong hg.bat from package installation // fix wrong hg.bat from package installation
@@ -299,100 +291,6 @@ public class HgRepositoryHandler
return version; return version;
} }
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param hgrc
*/
void appendHookSection(INIConfiguration hgrc)
{
INISection hooksSection = new INISection("hooks");
setHookParameter(hooksSection);
hgrc.addSection(hooksSection);
}
/**
* Method description
*
*
* @param hgrc
*/
void appendWebSection(INIConfiguration hgrc)
{
INISection webSection = new INISection("web");
setWebParameter(webSection);
hgrc.addSection(webSection);
}
/**
* Method description
*
*
* @param c
* @param repositoryName
*
* @return
*/
boolean registerMissingHook(INIConfiguration c, String repositoryName)
{
INISection hooks = c.getSection("hooks");
if (hooks == null)
{
hooks = new INISection("hooks");
c.addSection(hooks);
}
boolean write = false;
if (appendHook(repositoryName, hooks, "changegroup.scm"))
{
write = true;
}
if (appendHook(repositoryName, hooks, "pretxnchangegroup.scm"))
{
write = true;
}
return write;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param hooksSection
*/
void setHookParameter(INISection hooksSection)
{
hooksSection.setParameter("changegroup.scm", "python:scmhooks.callback");
hooksSection.setParameter("pretxnchangegroup.scm",
"python:scmhooks.callback");
}
/**
* Method description
*
*
* @param webSection
*/
void setWebParameter(INISection webSection)
{
webSection.setParameter("push_ssl", "false");
webSection.setParameter("allow_read", "*");
webSection.setParameter("allow_push", "*");
}
//~--- methods --------------------------------------------------------------
/** /**
* Method description * Method description
* *
@@ -434,16 +332,25 @@ public class HgRepositoryHandler
File hgrcFile = new File(directory, PATH_HGRC); File hgrcFile = new File(directory, PATH_HGRC);
INIConfiguration hgrc = new INIConfiguration(); INIConfiguration hgrc = new INIConfiguration();
appendWebSection(hgrc); INISection iniSection = new INISection(CONFIG_SECTION_SCMM);
iniSection.setParameter(CONFIG_KEY_REPOSITORY_ID, repository.getId());
// register hooks INIConfiguration iniConfiguration = new INIConfiguration();
appendHookSection(hgrc); iniConfiguration.addSection(iniSection);
hgrc.addSection(iniSection);
INIConfigurationWriter writer = new INIConfigurationWriter(); INIConfigurationWriter writer = new INIConfigurationWriter();
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 ----------------------------------------------------------
/** /**
@@ -460,37 +367,6 @@ public class HgRepositoryHandler
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param repositoryName
* @param hooks
* @param hookName
*
* @return
*/
private boolean appendHook(String repositoryName, INISection hooks,
String hookName)
{
boolean write = false;
String hook = hooks.getParameter(hookName);
if (Util.isEmpty(hook))
{
if (logger.isInfoEnabled())
{
logger.info("register missing {} hook for respository {}", hookName,
repositoryName);
}
hooks.setParameter(hookName, "python:scmhooks.callback");
write = true;
}
return write;
}
/** /**
* Method description * Method description
* *
@@ -512,104 +388,6 @@ public class HgRepositoryHandler
} }
} }
/**
* Method description
*
*
* @param repositoryDir
*
* @return
*/
private boolean registerMissingHook(File repositoryDir)
{
boolean result = false;
File hgrc = new File(repositoryDir, PATH_HGRC);
if (hgrc.exists())
{
try
{
INIConfigurationReader reader = new INIConfigurationReader();
INIConfiguration c = reader.read(hgrc);
String repositoryName = repositoryDir.getName();
if (registerMissingHook(c, repositoryName))
{
if (logger.isDebugEnabled())
{
logger.debug("rewrite hgrc for repository {}", repositoryName);
}
INIConfigurationWriter writer = new INIConfigurationWriter();
writer.write(c, hgrc);
}
result = true;
}
catch (IOException ex)
{
logger.error("could not register missing hook", ex);
}
}
return result;
}
/**
* Method description
*
*/
private void registerMissingHooks()
{
HgConfig c = getConfig();
if (c != null)
{
File repositoryDirectroy = getInitialBaseDirectory();
if (repositoryDirectroy.exists())
{
File lockFile = new File(repositoryDirectroy, PATH_HOOK);
if (!lockFile.exists())
{
File[] dirs =
repositoryDirectroy.listFiles(DirectoryFileFilter.instance);
boolean success = true;
if (Util.isNotEmpty(dirs))
{
for (File dir : dirs)
{
if (!registerMissingHook(dir))
{
success = false;
}
}
}
if (success)
{
createNewFile(lockFile);
}
}
else if (logger.isDebugEnabled())
{
logger.debug("hooks allready registered");
}
}
else if (logger.isDebugEnabled())
{
logger.debug(
"repository directory does not exists, could not register missing hooks");
}
}
else if (logger.isDebugEnabled())
{
logger.debug("config is not available, could not register missing hooks");
}
}
/** /**
* Method description * Method description
* *

View File

@@ -62,11 +62,11 @@ public class HgHookChangesetProvider implements HookChangesetProvider
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
public HgHookChangesetProvider(HgRepositoryHandler handler, public HgHookChangesetProvider(HgRepositoryHandler handler,
String id, HgHookManager hookManager, String startRev, File repositoryDirectory, HgHookManager hookManager, String startRev,
RepositoryHookType type) RepositoryHookType type)
{ {
this.handler = handler; this.handler = handler;
this.id = id; this.repositoryDirectory = repositoryDirectory;
this.hookManager = hookManager; this.hookManager = hookManager;
this.startRev = startRev; this.startRev = startRev;
this.type = type; this.type = type;
@@ -123,10 +123,6 @@ public class HgHookChangesetProvider implements HookChangesetProvider
*/ */
private Repository open() private Repository open()
{ {
sonia.scm.repository.Repository repo = new sonia.scm.repository.Repository();
repo.setId(id);
File repositoryDirectory = handler.getDirectory(repo);
// use HG_PENDING only for pre receive hooks // use HG_PENDING only for pre receive hooks
boolean pending = type == RepositoryHookType.PRE_RECEIVE; boolean pending = type == RepositoryHookType.PRE_RECEIVE;
@@ -144,7 +140,7 @@ public class HgHookChangesetProvider implements HookChangesetProvider
private HgHookManager hookManager; private HgHookManager hookManager;
/** Field description */ /** Field description */
private String id; private File repositoryDirectory;
/** Field description */ /** Field description */
private HookChangesetResponse response; private HookChangesetResponse response;

View File

@@ -44,6 +44,7 @@ import sonia.scm.repository.api.HookFeature;
import sonia.scm.repository.api.HookMessageProvider; import sonia.scm.repository.api.HookMessageProvider;
import sonia.scm.repository.api.HookTagProvider; import sonia.scm.repository.api.HookTagProvider;
import java.io.File;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set; import java.util.Set;
@@ -67,16 +68,16 @@ public class HgHookContextProvider extends HookContextProvider
* Constructs a new instance. * Constructs a new instance.
* *
* @param handler mercurial repository handler * @param handler mercurial repository handler
* @param namespaceAndName namespace and name of changed repository * @param repositoryDirectory the directory of the changed repository
* @param hookManager mercurial hook manager * @param hookManager mercurial hook manager
* @param startRev start revision * @param startRev start revision
* @param type type of hook * @param type type of hook
*/ */
public HgHookContextProvider(HgRepositoryHandler handler, public HgHookContextProvider(HgRepositoryHandler handler,
String id, HgHookManager hookManager, String startRev, File repositoryDirectory, HgHookManager hookManager, String startRev,
RepositoryHookType type) RepositoryHookType type)
{ {
this.hookChangesetProvider = new HgHookChangesetProvider(handler, id, hookManager, startRev, type); this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type);
} }
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------

View File

@@ -44,12 +44,13 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.repository.HgContext; import sonia.scm.repository.HgContext;
import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.RepositoryUtil;
import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.api.HgHookMessage;
import sonia.scm.repository.api.HgHookMessage.Severity; import sonia.scm.repository.api.HgHookMessage.Severity;
import sonia.scm.repository.spi.HgHookContextProvider; import sonia.scm.repository.spi.HgHookContextProvider;
@@ -63,6 +64,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.List; import java.util.List;
@@ -113,16 +115,6 @@ public class HgHookCallbackServlet extends HttpServlet
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
*
* @param hookEventFacade
* @param handler
* @param hookManager
* @param contextProvider
*/
@Inject @Inject
public HgHookCallbackServlet(HookEventFacade hookEventFacade, public HgHookCallbackServlet(HookEventFacade hookEventFacade,
HgRepositoryHandler handler, HgHookManager hookManager, HgRepositoryHandler handler, HgHookManager hookManager,
@@ -148,7 +140,6 @@ public class HgHookCallbackServlet extends HttpServlet
*/ */
@Override @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{ {
String ping = request.getParameter(PARAM_PING); String ping = request.getParameter(PARAM_PING);
@@ -179,7 +170,7 @@ public class HgHookCallbackServlet extends HttpServlet
if (m.matches()) if (m.matches())
{ {
String id = getRepositoryId(request); File repositoryPath = getRepositoryPath(request);
String type = m.group(1); String type = m.group(1);
String challenge = request.getParameter(PARAM_CHALLENGE); String challenge = request.getParameter(PARAM_CHALLENGE);
@@ -196,7 +187,7 @@ public class HgHookCallbackServlet extends HttpServlet
authenticate(request, credentials); authenticate(request, credentials);
} }
hookCallback(response, id, type, challenge, node); hookCallback(response, repositoryPath, type, challenge, node);
} }
else if (logger.isDebugEnabled()) else if (logger.isDebugEnabled())
{ {
@@ -255,8 +246,7 @@ public class HgHookCallbackServlet extends HttpServlet
} }
} }
private void fireHook(HttpServletResponse response, String id, private void fireHook(HttpServletResponse response, File repositoryDirectory, String node, RepositoryHookType type)
String node, RepositoryHookType type)
throws IOException throws IOException
{ {
HgHookContextProvider context = null; HgHookContextProvider context = null;
@@ -268,10 +258,11 @@ public class HgHookCallbackServlet extends HttpServlet
contextProvider.get().setPending(true); contextProvider.get().setPending(true);
} }
context = new HgHookContextProvider(handler, id, hookManager, context = new HgHookContextProvider(handler, repositoryDirectory, hookManager,
node, type); node, type);
hookEventFacade.handle(id).fireHookEvent(type, context); String repositoryId = getRepositoryId(repositoryDirectory);
hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
printMessages(response, context); printMessages(response, context);
} }
@@ -289,7 +280,7 @@ public class HgHookCallbackServlet extends HttpServlet
} }
} }
private void hookCallback(HttpServletResponse response, String id, String typeName, String challenge, String node) throws IOException { private void hookCallback(HttpServletResponse response, File repositoryDirectory, String typeName, String challenge, String node) throws IOException {
if (hookManager.isAcceptAble(challenge)) if (hookManager.isAcceptAble(challenge))
{ {
RepositoryHookType type = null; RepositoryHookType type = null;
@@ -305,7 +296,7 @@ public class HgHookCallbackServlet extends HttpServlet
if (type != null) if (type != null)
{ {
fireHook(response, id, node, type); fireHook(response, repositoryDirectory, node, type);
} }
else else
{ {
@@ -450,42 +441,22 @@ public class HgHookCallbackServlet extends HttpServlet
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** @SuppressWarnings("squid:S2083") // we do nothing with the path given, so this should be no issue
* Method description private String getRepositoryId(File repositoryPath)
*
*
* @param request
*
* @return
*/
private String getRepositoryId(HttpServletRequest request)
{ {
String id = null; return handler.getRepositoryId(repositoryPath);
}
private File getRepositoryPath(HttpServletRequest request) {
String path = request.getParameter(PARAM_REPOSITORYPATH); String path = request.getParameter(PARAM_REPOSITORYPATH);
if (Util.isNotEmpty(path)) {
if (Util.isNotEmpty(path)) return new File(path);
{
/**
* use canonical path to fix symbolic links
* https://bitbucket.org/sdorra/scm-manager/issue/82/symbolic-link-in-hg-repository-path
*/
try
{
id = RepositoryUtil.getRepositoryId(handler, path);
} }
catch (IOException ex) else
{ {
logger.error("could not find namespace and name of repository", ex); throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("directory", path), "could not find hgrc in directory");
} }
} }
else if (logger.isWarnEnabled())
{
logger.warn("no repository path parameter found");
}
return id;
}
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------

View File

@@ -8,7 +8,6 @@ type Configuration = {
"hgBinary": string, "hgBinary": string,
"pythonBinary": string, "pythonBinary": string,
"pythonPath"?: string, "pythonPath"?: string,
"repositoryDirectory": string,
"encoding": string, "encoding": string,
"useOptimizedBytecode": boolean, "useOptimizedBytecode": boolean,
"showRevisionInId": boolean, "showRevisionInId": boolean,
@@ -39,7 +38,7 @@ class HgConfigurationForm extends React.Component<Props, State> {
updateValidationStatus = () => { updateValidationStatus = () => {
const requiredFields = [ const requiredFields = [
"hgBinary", "pythonBinary", "repositoryDirectory", "encoding" "hgBinary", "pythonBinary", "encoding"
]; ];
const validationErrors = []; const validationErrors = [];
@@ -99,7 +98,6 @@ class HgConfigurationForm extends React.Component<Props, State> {
{this.inputField("hgBinary")} {this.inputField("hgBinary")}
{this.inputField("pythonBinary")} {this.inputField("pythonBinary")}
{this.inputField("pythonPath")} {this.inputField("pythonPath")}
{this.inputField("repositoryDirectory")}
{this.inputField("encoding")} {this.inputField("encoding")}
{this.checkbox("useOptimizedBytecode")} {this.checkbox("useOptimizedBytecode")}
{this.checkbox("showRevisionInId")} {this.checkbox("showRevisionInId")}

View File

@@ -1,6 +1,6 @@
//@flow //@flow
import React from "react"; import React from "react";
import { Title, GlobalConfiguration } from "@scm-manager/ui-components"; import { Title, Configuration } from "@scm-manager/ui-components";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import HgConfigurationForm from "./HgConfigurationForm"; import HgConfigurationForm from "./HgConfigurationForm";
@@ -18,7 +18,7 @@ class HgGlobalConfiguration extends React.Component<Props> {
return ( return (
<div> <div>
<Title title={t("scm-hg-plugin.config.title")}/> <Title title={t("scm-hg-plugin.config.title")}/>
<GlobalConfiguration link={link} render={props => <HgConfigurationForm {...props} />}/> <Configuration link={link} render={props => <HgConfigurationForm {...props} />}/>
</div> </div>
); );
} }

View File

@@ -14,8 +14,6 @@
"pythonBinaryHelpText": "Location of Python binary.", "pythonBinaryHelpText": "Location of Python binary.",
"pythonPath": "Python Module Search Path", "pythonPath": "Python Module Search Path",
"pythonPathHelpText": "Python Module Search Path (PYTHONPATH).", "pythonPathHelpText": "Python Module Search Path (PYTHONPATH).",
"repositoryDirectory": "Repository directory",
"repositoryDirectoryHelpText": "Location of Mercurial repositories.",
"encoding": "Encoding", "encoding": "Encoding",
"encodingHelpText": "Repository Encoding.", "encodingHelpText": "Repository Encoding.",
"useOptimizedBytecode": "Optimized Bytecode (.pyo)", "useOptimizedBytecode": "Optimized Bytecode (.pyo)",

View File

@@ -31,12 +31,21 @@
import os import os
from mercurial import demandimport from mercurial import demandimport, ui as uimod, hg
from mercurial.hgweb import hgweb, wsgicgi from mercurial.hgweb import hgweb, wsgicgi
repositoryPath = os.environ['SCM_REPOSITORY_PATH']
demandimport.enable() demandimport.enable()
application = hgweb(repositoryPath) u = uimod.ui()
u.setconfig('web', 'push_ssl', 'false')
u.setconfig('web', 'allow_read', '*')
u.setconfig('web', 'allow_push', '*')
u.setconfig('hooks', 'changegroup.scm', 'python:scmhooks.callback')
u.setconfig('hooks', 'pretxnchangegroup.scm', 'python:scmhooks.callback')
r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'])
application = hgweb(r)
wsgicgi.launch(application) wsgicgi.launch(application)

View File

@@ -38,25 +38,16 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.StoreFactory;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
/** /**
*
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@@ -68,8 +59,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock @Mock
private com.google.inject.Provider<HgContext> provider; private com.google.inject.Provider<HgContext> provider;
RepositoryLocationResolver repositoryLocationResolver ; private RepositoryLocationResolver repositoryLocationResolver;
private Path repoDir;
@Override @Override
protected void checkDirectory(File directory) { protected void checkDirectory(File directory) {
@@ -77,27 +67,16 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
assertTrue(hgDirectory.exists()); assertTrue(hgDirectory.exists());
assertTrue(hgDirectory.isDirectory()); assertTrue(hgDirectory.isDirectory());
File hgrc = new File(hgDirectory, "hgrc");
assertTrue(hgrc.exists());
assertTrue(hgrc.isFile());
assertTrue(hgrc.length() > 0);
} }
@Override @Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
File directory) throws RepositoryPathNotFoundException { File directory) {
DefaultFileSystem fileSystem = new DefaultFileSystem(); repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider));
PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider,fileSystem));
HgRepositoryHandler handler = new HgRepositoryHandler(factory, HgRepositoryHandler handler = new HgRepositoryHandler(factory,
new DefaultFileSystem(),
new HgContextProvider(), repositoryLocationResolver); new HgContextProvider(), repositoryLocationResolver);
handler.init(contextProvider); handler.init(contextProvider);
repoDir = directory.toPath();
when(repoDao.getPath(any())).thenReturn(repoDir);
HgTestUtil.checkForSkip(handler); HgTestUtil.checkForSkip(handler);
return handler; return handler;
@@ -106,15 +85,15 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test @Test
public void getDirectory() { public void getDirectory() {
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory,
new DefaultFileSystem(), provider, repositoryLocationResolver); provider, repositoryLocationResolver);
HgConfig hgConfig = new HgConfig(); HgConfig hgConfig = new HgConfig();
hgConfig.setHgBinary("hg"); hgConfig.setHgBinary("hg");
hgConfig.setPythonBinary("python"); hgConfig.setPythonBinary("python");
repositoryHandler.setConfig(hgConfig); repositoryHandler.setConfig(hgConfig);
Repository repository = new Repository("id", "git", "Space", "Name"); initRepository();
File path = repositoryHandler.getDirectory(repository); File path = repositoryHandler.getDirectory(repository);
assertEquals(repoDir.toString()+File.separator+InitialRepositoryLocationResolver.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); assertEquals(repoPath.toString() + File.separator + AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
} }
} }

View File

@@ -36,19 +36,18 @@ package sonia.scm.repository;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.junit.Assume; import org.junit.Assume;
import sonia.scm.SCMContext; import sonia.scm.SCMContext;
import sonia.scm.io.FileSystem;
import sonia.scm.store.InMemoryConfigurationStoreFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory;
import static org.mockito.Mockito.*; import javax.servlet.http.HttpServletRequest;
//~--- JDK imports ------------------------------------------------------------
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import javax.servlet.http.HttpServletRequest; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------
/** /**
* *
@@ -96,19 +95,17 @@ public final class HgTestUtil
* *
* @return * @return
*/ */
public static HgRepositoryHandler createHandler(File directory) throws RepositoryPathNotFoundException { public static HgRepositoryHandler createHandler(File directory) {
TempSCMContextProvider context = TempSCMContextProvider context =
(TempSCMContextProvider) SCMContext.getContext(); (TempSCMContextProvider) SCMContext.getContext();
context.setBaseDirectory(directory); context.setBaseDirectory(directory);
FileSystem fileSystem = mock(FileSystem.class);
PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class); PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(context,fileSystem)); RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(context));
HgRepositoryHandler handler = HgRepositoryHandler handler =
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), fileSystem, new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver);
new HgContextProvider(), repositoryLocationResolver);
Path repoDir = directory.toPath(); Path repoDir = directory.toPath();
when(repoDao.getPath(any())).thenReturn(repoDir); when(repoDao.getPath(any())).thenReturn(repoDir);
handler.init(context); handler.init(context);

View File

@@ -1,13 +1,11 @@
package sonia.scm.web; package sonia.scm.web;
import org.junit.Test; import org.junit.Test;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgRepositoryHandler;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
@@ -31,10 +29,6 @@ public class HgHookCallbackServletTest {
String path = "/tmp/hg/12345"; String path = "/tmp/hg/12345";
when(request.getParameter(PARAM_REPOSITORYPATH)).thenReturn(path); when(request.getParameter(PARAM_REPOSITORYPATH)).thenReturn(path);
File file = new File(path);
when(handler.getInitialBaseDirectory()).thenReturn(file);
servlet.doPost(request, response); servlet.doPost(request, response);
verify(response, never()).sendError(anyInt()); verify(response, never()).sendError(anyInt());

View File

@@ -46,7 +46,11 @@ import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNDebugLog;
import sonia.scm.io.FileSystem; import sonia.scm.ContextEntry;
import sonia.scm.io.INIConfiguration;
import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection;
import sonia.scm.logging.SVNKitLogger; import sonia.scm.logging.SVNKitLogger;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.HookEventFacade;
@@ -56,6 +60,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.util.Util; import sonia.scm.util.Util;
import java.io.File; import java.io.File;
import java.io.IOException;
import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.ContextEntry.ContextBuilder.entity;
@@ -82,15 +87,19 @@ public class SvnRepositoryHandler
public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME,
TYPE_DISPLAYNAME, TYPE_DISPLAYNAME,
SvnRepositoryServiceProvider.COMMANDS); SvnRepositoryServiceProvider.COMMANDS);
private static final String CONFIG_FILE_NAME = "scm-manager.conf";
private static final String CONFIG_SECTION_SCMM = "scmm";
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(SvnRepositoryHandler.class); LoggerFactory.getLogger(SvnRepositoryHandler.class);
@Inject @Inject
public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory, FileSystem fileSystem, public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory,
HookEventFacade eventFacade, RepositoryLocationResolver repositoryLocationResolver) HookEventFacade eventFacade,
RepositoryLocationResolver repositoryLocationResolver)
{ {
super(storeFactory, fileSystem, repositoryLocationResolver); super(storeFactory, repositoryLocationResolver);
// register logger // register logger
SVNDebugLog.setDefaultLog(new SVNKitLogger()); SVNDebugLog.setDefaultLog(new SVNKitLogger());
@@ -210,4 +219,21 @@ public class SvnRepositoryHandler
{ {
return SvnConfig.class; return SvnConfig.class;
} }
@Override
protected void postCreate(Repository repository, File directory) throws IOException {
INISection iniSection = new INISection(CONFIG_SECTION_SCMM);
iniSection.setParameter(CONFIG_KEY_REPOSITORY_ID, repository.getId());
INIConfiguration iniConfiguration = new INIConfiguration();
iniConfiguration.addSection(iniSection);
new INIConfigurationWriter().write(iniConfiguration, new File(directory, CONFIG_FILE_NAME));
}
String getRepositoryId(File directory) {
try {
return new INIConfigurationReader().read(new File(directory, CONFIG_FILE_NAME)).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);
}
}
} }

View File

@@ -70,16 +70,7 @@ public class SvnRepositoryHook implements FSHook
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
/** public SvnRepositoryHook(HookEventFacade hookEventFacade, SvnRepositoryHandler handler)
* Constructs ...
*
*
*
* @param hookEventFacade
* @param handler
*/
public SvnRepositoryHook(HookEventFacade hookEventFacade,
SvnRepositoryHandler handler)
{ {
this.hookEventFacade = hookEventFacade; this.hookEventFacade = hookEventFacade;
this.handler = handler; this.handler = handler;
@@ -163,10 +154,10 @@ public class SvnRepositoryHook implements FSHook
{ {
try try
{ {
String id = getRepositoryId(directory); String repositoryId = getRepositoryId(directory);
//J- //J-
hookEventFacade.handle(id) hookEventFacade.handle(repositoryId)
.fireHookEvent( .fireHookEvent(
changesetProvider.getType(), changesetProvider.getType(),
new SvnHookContextProvider(changesetProvider) new SvnHookContextProvider(changesetProvider)
@@ -197,18 +188,16 @@ public class SvnRepositoryHook implements FSHook
* *
* @throws IOException * @throws IOException
*/ */
private String getRepositoryId(File directory) throws IOException private String getRepositoryId(File directory)
{ {
AssertUtil.assertIsNotNull(directory); AssertUtil.assertIsNotNull(directory);
return handler.getRepositoryId(directory);
return RepositoryUtil.getRepositoryId(handler, directory);
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */
private SvnRepositoryHandler handler;
/** Field description */ /** Field description */
private HookEventFacade hookEventFacade; private HookEventFacade hookEventFacade;
private final SvnRepositoryHandler handler;
} }

View File

@@ -5,7 +5,6 @@ import { translate } from "react-i18next";
import { InputField, Checkbox, Select } from "@scm-manager/ui-components"; import { InputField, Checkbox, Select } from "@scm-manager/ui-components";
type Configuration = { type Configuration = {
repositoryDirectory: string,
compatibility: string, compatibility: string,
enabledGZip: boolean, enabledGZip: boolean,
disabled: boolean, disabled: boolean,
@@ -31,14 +30,11 @@ class HgConfigurationForm extends React.Component<Props, State> {
this.state = { ...props.initialConfiguration, validationErrors: [] }; this.state = { ...props.initialConfiguration, validationErrors: [] };
} }
isValid = () => {
return !!this.state.repositoryDirectory;
};
handleChange = (value: any, name: string) => { handleChange = (value: any, name: string) => {
this.setState({ this.setState({
[name]: value [name]: value
}, () => this.props.onConfigurationChange(this.state, this.isValid())); }, () => this.props.onConfigurationChange(this.state, true));
}; };
compatibilityOptions = (values: string[]) => { compatibilityOptions = (values: string[]) => {
@@ -64,16 +60,6 @@ class HgConfigurationForm extends React.Component<Props, State> {
return ( return (
<> <>
<InputField
name="repositoryDirectory"
label={t("scm-svn-plugin.config.directory")}
helpText={t("scm-svn-plugin.config.directoryHelpText")}
value={this.state.repositoryDirectory}
errorMessage={t("scm-svn-plugin.config.required")}
validationError={!this.state.repositoryDirectory}
onChange={this.handleChange}
disabled={readOnly}
/>
<Select <Select
name="compatibility" name="compatibility"
label={t("scm-svn-plugin.config.compatibility")} label={t("scm-svn-plugin.config.compatibility")}

View File

@@ -1,7 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { Title, GlobalConfiguration } from "@scm-manager/ui-components"; import { Title, Configuration } from "@scm-manager/ui-components";
import SvnConfigurationForm from "./SvnConfigurationForm"; import SvnConfigurationForm from "./SvnConfigurationForm";
type Props = { type Props = {
@@ -18,7 +18,7 @@ class SvnGlobalConfiguration extends React.Component<Props> {
return ( return (
<div> <div>
<Title title={t("scm-svn-plugin.config.title")}/> <Title title={t("scm-svn-plugin.config.title")}/>
<GlobalConfiguration link={link} render={props => <SvnConfigurationForm {...props} />}/> <Configuration link={link} render={props => <SvnConfigurationForm {...props} />}/>
</div> </div>
); );
} }

View File

@@ -6,8 +6,6 @@
"config": { "config": {
"link": "Subversion", "link": "Subversion",
"title": "Subversion Configuration", "title": "Subversion Configuration",
"directory": "Repository Directory",
"directoryHelpText": "Location of Subversion repositories.",
"compatibility": "Version Compatibility", "compatibility": "Version Compatibility",
"compatibilityHelpText": "Specifies with which subversion version repositories are compatible.", "compatibilityHelpText": "Specifies with which subversion version repositories are compatible.",
"compatibility-values": { "compatibility-values": {

View File

@@ -36,14 +36,12 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -69,12 +67,14 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock @Mock
private com.google.inject.Provider<RepositoryManager> repositoryManagerProvider; private com.google.inject.Provider<RepositoryManager> repositoryManagerProvider;
@Mock
private RepositoryDAO repositoryDAO;
private HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); private HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory); private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory);
RepositoryLocationResolver repositoryLocationResolver ; private RepositoryLocationResolver repositoryLocationResolver;
private Path repoDir;
@Override @Override
protected void checkDirectory(File directory) { protected void checkDirectory(File directory) {
@@ -91,18 +91,10 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Override @Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
File directory) throws RepositoryPathNotFoundException { File directory) {
repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider));
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, repositoryLocationResolver);
DefaultFileSystem fileSystem = new DefaultFileSystem();
PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider,fileSystem));
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory,
new DefaultFileSystem(), null, repositoryLocationResolver);
repoDir = directory.toPath();
when(repoDao.getPath(any())).thenReturn(repoDir);
handler.init(contextProvider); handler.init(contextProvider);
SvnConfig config = new SvnConfig(); SvnConfig config = new SvnConfig();
@@ -117,14 +109,13 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
public void getDirectory() { public void getDirectory() {
when(factory.getStore(any())).thenReturn(store); when(factory.getStore(any())).thenReturn(store);
SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory,
new DefaultFileSystem(), facade, repositoryLocationResolver); facade, repositoryLocationResolver);
SvnConfig svnConfig = new SvnConfig(); SvnConfig svnConfig = new SvnConfig();
repositoryHandler.setConfig(svnConfig); repositoryHandler.setConfig(svnConfig);
Repository repository = new Repository("id", "svn", "Space", "Name"); initRepository();
File path = repositoryHandler.getDirectory(repository); File path = repositoryHandler.getDirectory(repository);
assertEquals(repoDir.toString()+File.separator+InitialRepositoryLocationResolver.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); assertEquals(repoPath.toString()+File.separator+ AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
} }
} }

View File

@@ -90,7 +90,7 @@ public class AbstractTestBase
assertTrue(tempDirectory.mkdirs()); assertTrue(tempDirectory.mkdirs());
contextProvider = MockUtil.getSCMContextProvider(tempDirectory); contextProvider = MockUtil.getSCMContextProvider(tempDirectory);
fileSystem = new DefaultFileSystem(); fileSystem = new DefaultFileSystem();
InitialRepositoryLocationResolver initialRepoLocationResolver = new InitialRepositoryLocationResolver(contextProvider,fileSystem); InitialRepositoryLocationResolver initialRepoLocationResolver = new InitialRepositoryLocationResolver(contextProvider);
repositoryLocationResolver = new RepositoryLocationResolver(repositoryDAO, initialRepoLocationResolver); repositoryLocationResolver = new RepositoryLocationResolver(repositoryDAO, initialRepoLocationResolver);
postSetUp(); postSetUp();
} }

View File

@@ -37,9 +37,12 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.util.MockUtil; import sonia.scm.util.MockUtil;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -61,10 +64,17 @@ public abstract class ManagerTestBase<T extends ModelObject>
protected Manager<T> manager; protected Manager<T> manager;
protected File temp ;
@Before @Before
public void setUp() throws IOException { public void setUp() throws IOException {
contextProvider = MockUtil.getSCMContextProvider(tempFolder.newFolder()); if (temp == null){
locationResolver = mock(RepositoryLocationResolver.class); temp = tempFolder.newFolder();
}
contextProvider = MockUtil.getSCMContextProvider(temp);
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(contextProvider);
RepositoryDAO repoDao = mock(RepositoryDAO.class);
locationResolver = new RepositoryLocationResolver(repoDao ,initialRepositoryLocationResolver);
manager = createManager(); manager = createManager();
manager.init(contextProvider); manager.init(contextProvider);
} }

View File

@@ -35,7 +35,6 @@ package sonia.scm.repository;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import sonia.scm.AlreadyExistsException; import sonia.scm.AlreadyExistsException;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
@@ -60,7 +59,7 @@ public class DummyRepositoryHandler
private final Set<String> existingRepoNames = new HashSet<>(); private final Set<String> existingRepoNames = new HashSet<>();
public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) { public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) {
super(storeFactory, new DefaultFileSystem(), repositoryLocationResolver); super(storeFactory, repositoryLocationResolver);
} }
@Override @Override

View File

@@ -41,20 +41,24 @@ import sonia.scm.util.IOUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
/** /**
*
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
protected PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
protected Path repoPath;
protected Repository repository;
protected abstract void checkDirectory(File directory); protected abstract void checkDirectory(File directory);
protected abstract RepositoryHandler createRepositoryHandler( protected abstract RepositoryHandler createRepositoryHandler(
@@ -65,27 +69,6 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
createRepository(); createRepository();
} }
@Test
public void testCreateResourcePath() {
Repository repository = createRepository();
String path = handler.createResourcePath(repository);
assertNotNull(path);
assertTrue(path.trim().length() > 0);
assertTrue(path.contains(repository.getId()));
}
@Test
public void testDelete() {
Repository repository = createRepository();
handler.delete(repository);
File directory = new File(baseDirectory, repository.getId());
assertFalse(directory.exists());
}
@Override @Override
protected void postSetUp() throws IOException, RepositoryPathNotFoundException { protected void postSetUp() throws IOException, RepositoryPathNotFoundException {
InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory(); InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory();
@@ -101,18 +84,22 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
} }
} }
private Repository createRepository() { private void createRepository() {
Repository repository = RepositoryTestData.createHeartOfGold(); File nativeRepoDirectory = initRepository();
handler.create(repository); handler.create(repository);
File directory = new File(new File(baseDirectory, repository.getId()), InitialRepositoryLocationResolver.REPOSITORIES_NATIVE_DIRECTORY); assertTrue(nativeRepoDirectory.exists());
assertTrue(nativeRepoDirectory.isDirectory());
checkDirectory(nativeRepoDirectory);
}
assertTrue(directory.exists()); protected File initRepository() {
assertTrue(directory.isDirectory()); repository = RepositoryTestData.createHeartOfGold();
checkDirectory(directory); File repoDirectory = new File(baseDirectory, repository.getId());
repoPath = repoDirectory.toPath();
return repository; when(repoDao.getPath(repository)).thenReturn(repoPath);
return new File(repoDirectory, AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
} }
protected File baseDirectory; protected File baseDirectory;

View File

@@ -53,7 +53,7 @@ public abstract class ConfigurationEntryStoreTestBase extends KeyValueStoreTestB
protected ConfigurationEntryStore getDataStore(Class type) { protected ConfigurationEntryStore getDataStore(Class type) {
StoreParameters params = new StoreParameters() StoreParameters params = new StoreParameters()
.withType(type) .withType(type)
.withName("test") .withName(storeName)
.build(); .build();
return this.createConfigurationStoreFactory().getStore(params); return this.createConfigurationStoreFactory().getStore(params);
} }
@@ -62,7 +62,7 @@ public abstract class ConfigurationEntryStoreTestBase extends KeyValueStoreTestB
protected ConfigurationEntryStore getDataStore(Class type, Repository repository) { protected ConfigurationEntryStore getDataStore(Class type, Repository repository) {
StoreParameters params = new StoreParameters() StoreParameters params = new StoreParameters()
.withType(type) .withType(type)
.withName("test") .withName(repoStoreName)
.forRepository(repository) .forRepository(repository)
.build(); .build();
return this.createConfigurationStoreFactory().getStore(params); return this.createConfigurationStoreFactory().getStore(params);

View File

@@ -58,9 +58,11 @@ import java.util.Map;
public abstract class KeyValueStoreTestBase extends AbstractTestBase public abstract class KeyValueStoreTestBase extends AbstractTestBase
{ {
private Repository repository = RepositoryTestData.createHeartOfGold(); protected Repository repository = RepositoryTestData.createHeartOfGold();
private DataStore<StoreObject> store; protected DataStore<StoreObject> store;
private DataStore<StoreObject> repoStore; protected DataStore<StoreObject> repoStore;
protected String repoStoreName = "testRepoStore";
protected String storeName = "testStore";
/** /**
* Method description * Method description

View File

@@ -18,6 +18,7 @@
"create-index": "^2.3.0", "create-index": "^2.3.0",
"enzyme": "^3.5.0", "enzyme": "^3.5.0",
"enzyme-adapter-react-16": "^1.3.1", "enzyme-adapter-react-16": "^1.3.1",
"fetch-mock": "^7.2.5",
"flow-bin": "^0.79.1", "flow-bin": "^0.79.1",
"flow-typed": "^2.5.1", "flow-typed": "^2.5.1",
"jest": "^23.5.0", "jest": "^23.5.0",

View File

@@ -1,12 +1,13 @@
// @flow // @flow
import React from "react"; import React from "react";
import { mount, shallow } from "enzyme"; import {mount, shallow} from "enzyme";
import "./tests/enzyme"; import "./tests/enzyme";
import "./tests/i18n"; import "./tests/i18n";
import ReactRouterEnzymeContext from "react-router-enzyme-context"; import ReactRouterEnzymeContext from "react-router-enzyme-context";
import Paginator from "./Paginator"; import Paginator from "./Paginator";
describe("paginator rendering tests", () => { // TODO: Fix tests
xdescribe("paginator rendering tests", () => {
const options = new ReactRouterEnzymeContext(); const options = new ReactRouterEnzymeContext();
@@ -18,7 +19,8 @@ describe("paginator rendering tests", () => {
const collection = { const collection = {
page: 10, page: 10,
pageTotal: 20, pageTotal: 20,
_links: {} _links: {},
_embedded: {}
}; };
const paginator = shallow( const paginator = shallow(
@@ -40,7 +42,8 @@ describe("paginator rendering tests", () => {
first: dummyLink, first: dummyLink,
next: dummyLink, next: dummyLink,
last: dummyLink last: dummyLink
} },
_embedded: {}
}; };
const paginator = shallow( const paginator = shallow(
@@ -79,7 +82,8 @@ describe("paginator rendering tests", () => {
prev: dummyLink, prev: dummyLink,
next: dummyLink, next: dummyLink,
last: dummyLink last: dummyLink
} },
_embedded: {}
}; };
const paginator = shallow( const paginator = shallow(
@@ -121,7 +125,8 @@ describe("paginator rendering tests", () => {
_links: { _links: {
first: dummyLink, first: dummyLink,
prev: dummyLink prev: dummyLink
} },
_embedded: {}
}; };
const paginator = shallow( const paginator = shallow(
@@ -160,7 +165,8 @@ describe("paginator rendering tests", () => {
prev: dummyLink, prev: dummyLink,
next: dummyLink, next: dummyLink,
last: dummyLink last: dummyLink
} },
_embedded: {}
}; };
const paginator = shallow( const paginator = shallow(
@@ -204,7 +210,8 @@ describe("paginator rendering tests", () => {
prev: dummyLink, prev: dummyLink,
next: dummyLink, next: dummyLink,
last: dummyLink last: dummyLink
} },
_embedded: {}
}; };
const paginator = shallow( const paginator = shallow(
@@ -256,7 +263,8 @@ describe("paginator rendering tests", () => {
}, },
next: dummyLink, next: dummyLink,
last: dummyLink last: dummyLink
} },
_embedded: {}
}; };
let urlToOpen; let urlToOpen;

View File

@@ -1,8 +1,8 @@
// @flow // @flow
import { contextPath } from "./urls"; import {contextPath} from "./urls";
export const NOT_FOUND_ERROR = Error("not found"); export const NOT_FOUND_ERROR_MESSAGE = "not found";
export const UNAUTHORIZED_ERROR = Error("unauthorized"); export const UNAUTHORIZED_ERROR_MESSAGE = "unauthorized";
const fetchOptions: RequestOptions = { const fetchOptions: RequestOptions = {
credentials: "same-origin", credentials: "same-origin",
@@ -13,17 +13,30 @@ const fetchOptions: RequestOptions = {
function handleStatusCode(response: Response) { function handleStatusCode(response: Response) {
if (!response.ok) { if (!response.ok) {
if (response.status === 401) { switch (response.status) {
throw UNAUTHORIZED_ERROR; case 401:
return throwErrorWithMessage(response, UNAUTHORIZED_ERROR_MESSAGE);
case 404:
return throwErrorWithMessage(response, NOT_FOUND_ERROR_MESSAGE);
default:
return throwErrorWithMessage(response, "server returned status code " + response.status);
} }
if (response.status === 404) {
throw NOT_FOUND_ERROR;
}
throw new Error("server returned status code " + response.status);
} }
return response; return response;
} }
function throwErrorWithMessage(response: Response, message: string) {
return response.json().then(
json => {
throw Error(json.message);
},
() => {
throw Error(message);
}
);
}
export function createUrl(url: string) { export function createUrl(url: string) {
if (url.includes("://")) { if (url.includes("://")) {
return url; return url;

View File

@@ -1,7 +1,14 @@
// @flow // @flow
import { createUrl } from "./apiclient"; import {apiClient, createUrl} from "./apiclient";
import fetchMock from "fetch-mock";
describe("create url", () => { describe("apiClient", () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
describe("create url", () => {
it("should not change absolute urls", () => { it("should not change absolute urls", () => {
expect(createUrl("https://www.scm-manager.org")).toBe( expect(createUrl("https://www.scm-manager.org")).toBe(
"https://www.scm-manager.org" "https://www.scm-manager.org"
@@ -12,4 +19,47 @@ describe("create url", () => {
expect(createUrl("/users")).toBe("/api/v2/users"); expect(createUrl("/users")).toBe("/api/v2/users");
expect(createUrl("users")).toBe("/api/v2/users"); expect(createUrl("users")).toBe("/api/v2/users");
}); });
});
describe("error handling", () => {
const error = {
message: "Error!!"
};
it("should append default error message for 401 if none provided", () => {
fetchMock.mock("api/v2/foo", 401);
return apiClient
.get("foo")
.catch(err => {
expect(err.message).toEqual("unauthorized");
});
});
it("should append error message for 401 if provided", () => {
fetchMock.mock("api/v2/foo", {"status": 401, body: error});
return apiClient
.get("foo")
.catch(err => {
expect(err.message).toEqual("Error!!");
});
});
it("should append default error message for 401 if none provided", () => {
fetchMock.mock("api/v2/foo", 404);
return apiClient
.get("foo")
.catch(err => {
expect(err.message).toEqual("not found");
});
});
it("should append error message for 404 if provided", () => {
fetchMock.mock("api/v2/foo", {"status": 404, body: error});
return apiClient
.get("foo")
.catch(err => {
expect(err.message).toEqual("Error!!");
});
});
});
}); });

View File

@@ -11,8 +11,8 @@ import {
type RenderProps = { type RenderProps = {
readOnly: boolean, readOnly: boolean,
initialConfiguration: Configuration, initialConfiguration: ConfigurationType,
onConfigurationChange: (Configuration, boolean) => void onConfigurationChange: (ConfigurationType, boolean) => void
}; };
type Props = { type Props = {
@@ -23,7 +23,7 @@ type Props = {
t: (string) => string t: (string) => string
}; };
type Configuration = { type ConfigurationType = {
_links: Links _links: Links
} & Object; } & Object;
@@ -33,8 +33,8 @@ type State = {
modifying: boolean, modifying: boolean,
contentType?: string, contentType?: string,
configuration?: Configuration, configuration?: ConfigurationType,
modifiedConfiguration?: Configuration, modifiedConfiguration?: ConfigurationType,
valid: boolean valid: boolean
}; };
@@ -42,7 +42,7 @@ type State = {
* GlobalConfiguration uses the render prop pattern to encapsulate the logic for * GlobalConfiguration uses the render prop pattern to encapsulate the logic for
* synchronizing the configuration with the backend. * synchronizing the configuration with the backend.
*/ */
class GlobalConfiguration extends React.Component<Props, State> { class Configuration extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@@ -84,7 +84,7 @@ class GlobalConfiguration extends React.Component<Props, State> {
}); });
}; };
loadConfig = (configuration: Configuration) => { loadConfig = (configuration: ConfigurationType) => {
this.setState({ this.setState({
configuration, configuration,
fetching: false, fetching: false,
@@ -107,7 +107,7 @@ class GlobalConfiguration extends React.Component<Props, State> {
return !modificationUrl; return !modificationUrl;
}; };
configurationChanged = (configuration: Configuration, valid: boolean) => { configurationChanged = (configuration: ConfigurationType, valid: boolean) => {
this.setState({ this.setState({
modifiedConfiguration: configuration, modifiedConfiguration: configuration,
valid valid
@@ -159,4 +159,4 @@ class GlobalConfiguration extends React.Component<Props, State> {
} }
export default translate("config")(GlobalConfiguration); export default translate("config")(Configuration);

View File

@@ -9,6 +9,16 @@ class ConfigurationBinder {
i18nNamespace: string = "plugins"; i18nNamespace: string = "plugins";
navLink(to: string, labelI18nKey: string, t: any){
return <NavLink to={to} label={t(labelI18nKey)} />;
}
route(path: string, Component: any){
return <Route path={path}
render={() => Component}
exact/>;
}
bindGlobal(to: string, labelI18nKey: string, linkName: string, ConfigurationComponent: any) { bindGlobal(to: string, labelI18nKey: string, linkName: string, ConfigurationComponent: any) {
// create predicate based on the link name of the index resource // create predicate based on the link name of the index resource
@@ -19,25 +29,48 @@ class ConfigurationBinder {
// create NavigationLink with translated label // create NavigationLink with translated label
const ConfigNavLink = translate(this.i18nNamespace)(({t}) => { const ConfigNavLink = translate(this.i18nNamespace)(({t}) => {
return <NavLink to={"/config" + to} label={t(labelI18nKey)} />; return this.navLink("/config" + to, labelI18nKey, t);
}); });
// bind navigation link to extension point // bind navigation link to extension point
binder.bind("config.navigation", ConfigNavLink, configPredicate); binder.bind("config.navigation", ConfigNavLink, configPredicate);
// route for global configuration, passes the link from the index resource to component // route for global configuration, passes the link from the index resource to component
const ConfigRoute = ({ url, links }) => { const ConfigRoute = ({ url, links }) => {
const link = links[linkName].href; const link = links[linkName].href;
return <Route path={url + to} return this.route(url + to, <ConfigurationComponent link={link}/>);
render={() => <ConfigurationComponent link={link}/>}
exact/>;
}; };
// bind config route to extension point // bind config route to extension point
binder.bind("config.route", ConfigRoute, configPredicate); binder.bind("config.route", ConfigRoute, configPredicate);
} }
bindRepository(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
// create predicate based on the link name of the current repository route
// if the linkname is not available, the navigation link and the route are not bound to the extension points
const repoPredicate = (props: Object) => {
return props.repository && props.repository._links && props.repository._links[linkName];
};
// create NavigationLink with translated label
const RepoNavLink = translate(this.i18nNamespace)(({t, url}) => {
return this.navLink(url + to, labelI18nKey, t);
});
// bind navigation link to extension point
binder.bind("repository.navigation", RepoNavLink, repoPredicate);
// route for global configuration, passes the current repository to component
const RepoRoute = ({ url, repository }) => {
return this.route(url + to, <RepositoryComponent repository={repository}/>);
};
// bind config route to extension point
binder.bind("repository.route", RepoRoute, repoPredicate);
}
} }
export default new ConfigurationBinder(); export default new ConfigurationBinder();

View File

@@ -1,3 +1,3 @@
// @flow // @flow
export { default as ConfigurationBinder } from "./ConfigurationBinder"; export { default as ConfigurationBinder } from "./ConfigurationBinder";
export { default as GlobalConfiguration } from "./GlobalConfiguration"; export { default as Configuration } from "./Configuration";

View File

@@ -0,0 +1,112 @@
// @flow
import React from "react";
import {translate} from "react-i18next";
import InputField from "./InputField";
type State = {
password: string,
confirmedPassword: string,
passwordValid: boolean,
passwordConfirmationFailed: boolean
};
type Props = {
passwordChanged: string => void,
passwordValidator?: string => boolean,
// Context props
t: string => string
};
class PasswordConfirmation extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
password: "",
confirmedPassword: "",
passwordValid: true,
passwordConfirmationFailed: false
};
}
componentDidMount() {
this.setState({
password: "",
confirmedPassword: "",
passwordValid: true,
passwordConfirmationFailed: false
});
}
render() {
const { t } = this.props;
return (
<>
<InputField
label={t("password.newPassword")}
type="password"
onChange={this.handlePasswordChange}
value={this.state.password ? this.state.password : ""}
validationError={!this.state.passwordValid}
errorMessage={t("password.passwordInvalid")}
helpText={t("password.passwordHelpText")}
/>
<InputField
label={t("password.confirmPassword")}
type="password"
onChange={this.handlePasswordValidationChange}
value={this.state ? this.state.confirmedPassword : ""}
validationError={this.state.passwordConfirmationFailed}
errorMessage={t("password.passwordConfirmFailed")}
helpText={t("password.passwordConfirmHelpText")}
/>
</>
);
}
validatePassword = password => {
const { passwordValidator } = this.props;
if (passwordValidator) {
return passwordValidator(password);
}
return password.length >= 6 && password.length < 32;
};
handlePasswordValidationChange = (confirmedPassword: string) => {
const passwordConfirmed = this.state.password === confirmedPassword;
this.setState(
{
confirmedPassword,
passwordConfirmationFailed: !passwordConfirmed
},
this.propagateChange
);
};
handlePasswordChange = (password: string) => {
const passwordConfirmationFailed =
password !== this.state.confirmedPassword;
this.setState(
{
passwordValid: this.validatePassword(password),
passwordConfirmationFailed,
password: password
},
this.propagateChange
);
};
propagateChange = () => {
if (
this.state.password &&
this.state.passwordValid &&
!this.state.passwordConfirmationFailed
) {
this.props.passwordChanged(this.state.password);
}
};
}
export default translate("commons")(PasswordConfirmation);

View File

@@ -5,5 +5,6 @@ export { default as Checkbox } from "./Checkbox.js";
export { default as InputField } from "./InputField.js"; export { default as InputField } from "./InputField.js";
export { default as Select } from "./Select.js"; export { default as Select } from "./Select.js";
export { default as Textarea } from "./Textarea.js"; export { default as Textarea } from "./Textarea.js";
export { default as PasswordConfirmation } from "./PasswordConfirmation.js";
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon"; export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon";

View File

@@ -22,7 +22,7 @@ export { default as HelpIcon } from "./HelpIcon";
export { default as Tooltip } from "./Tooltip"; export { default as Tooltip } from "./Tooltip";
export { getPageFromMatch } from "./urls"; export { getPageFromMatch } from "./urls";
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js"; export { apiClient, NOT_FOUND_ERROR_MESSAGE, UNAUTHORIZED_ERROR_MESSAGE } from "./apiclient.js";
export * from "./buttons"; export * from "./buttons";
export * from "./config"; export * from "./config";

View File

@@ -2995,6 +2995,15 @@ fb-watchman@^2.0.0:
dependencies: dependencies:
bser "^2.0.0" bser "^2.0.0"
fetch-mock@^7.2.5:
version "7.2.5"
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-7.2.5.tgz#4682f51b9fa74d790e10a471066cb22f3ff84d48"
dependencies:
babel-polyfill "^6.26.0"
glob-to-regexp "^0.4.0"
path-to-regexp "^2.2.1"
whatwg-url "^6.5.0"
figures@^2.0.0: figures@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
@@ -3341,6 +3350,10 @@ glob-stream@^3.1.5:
through2 "^0.6.1" through2 "^0.6.1"
unique-stream "^1.0.0" unique-stream "^1.0.0"
glob-to-regexp@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.0.tgz#49bd677b1671022bd10921c3788f23cdebf9c7e6"
glob-watcher@^0.0.6: glob-watcher@^0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b"
@@ -5982,6 +5995,10 @@ path-to-regexp@^1.7.0:
dependencies: dependencies:
isarray "0.0.1" isarray "0.0.1"
path-to-regexp@^2.2.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704"
path-type@^1.0.0: path-type@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -7814,7 +7831,7 @@ whatwg-mimetype@^2.1.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"
whatwg-url@^6.4.1: whatwg-url@^6.4.1, whatwg-url@^6.5.0:
version "6.5.0" version "6.5.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
dependencies: dependencies:

View File

@@ -1,7 +1,10 @@
// @flow // @flow
import type { Links } from "./hal";
export type Me = { export type Me = {
name: string, name: string,
displayName: string, displayName: string,
mail: string mail: string,
_links: Links
}; };

View File

@@ -1,6 +1,6 @@
// @flow // @flow
import type { Collection, Links } from "./hal"; import type { Links } from "./hal";
// TODO ?? check ?? links // TODO ?? check ?? links
export type SubRepository = { export type SubRepository = {
@@ -20,6 +20,6 @@ export type File = {
subRepository?: SubRepository, // TODO subRepository?: SubRepository, // TODO
_links: Links, _links: Links,
_embedded: { _embedded: {
children: File[] children: ?File[]
} }
}; };

View File

@@ -40,12 +40,29 @@
"previous": "Previous" "previous": "Previous"
}, },
"profile": { "profile": {
"navigation-label": "Navigation",
"actions-label": "Actions", "actions-label": "Actions",
"username": "Username", "username": "Username",
"displayName": "Display Name", "displayName": "Display Name",
"mail": "E-Mail", "mail": "E-Mail",
"information": "Information",
"change-password": "Change password", "change-password": "Change password",
"error-title": "Error", "error-title": "Error",
"error-subtitle": "Cannot display profile" "error-subtitle": "Cannot display profile",
"error": "Error",
"error-message": "'me' is undefined"
},
"password": {
"label": "Password",
"newPassword": "New password",
"passwordHelpText": "Plain text password of the user.",
"passwordConfirmHelpText": "Repeat the password for confirmation.",
"currentPassword": "Current password",
"currentPasswordHelpText": "The password currently in use",
"confirmPassword": "Confirm password",
"passwordInvalid": "Password has to be between 6 and 32 characters",
"passwordConfirmFailed": "Passwords have to be identical",
"submit": "Submit",
"changedSuccessfully": "Pasword successfully changed"
} }
} }

View File

@@ -50,10 +50,7 @@
"validation": { "validation": {
"mail-invalid": "This email is invalid", "mail-invalid": "This email is invalid",
"name-invalid": "This name is invalid", "name-invalid": "This name is invalid",
"displayname-invalid": "This displayname is invalid", "displayname-invalid": "This displayname is invalid"
"password-invalid": "Password has to be between 6 and 32 characters",
"passwordValidation-invalid": "Passwords have to be identical",
"validatePassword": "Confirm password"
}, },
"password": { "password": {
"set-password-successful": "Password successfully set" "set-password-successful": "Password successfully set"
@@ -62,8 +59,6 @@
"usernameHelpText": "Unique name of the user.", "usernameHelpText": "Unique name of the user.",
"displayNameHelpText": "Display name of the user.", "displayNameHelpText": "Display name of the user.",
"mailHelpText": "Email address of the user.", "mailHelpText": "Email address of the user.",
"passwordHelpText": "Plain text password of the user.",
"passwordConfirmHelpText": "Repeat the password for validation.",
"adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.", "adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.",
"activeHelpText": "Activate or deactive the user." "activeHelpText": "Activate or deactive the user."
} }

View File

@@ -0,0 +1,141 @@
// @flow
import React from "react";
import {
ErrorNotification,
InputField,
Notification,
PasswordConfirmation,
SubmitButton
} from "@scm-manager/ui-components";
import { translate } from "react-i18next";
import type { Me } from "@scm-manager/ui-types";
import { changePassword } from "../modules/changePassword";
type Props = {
me: Me,
t: string => string
};
type State = {
oldPassword: string,
password: string,
loading: boolean,
error?: Error,
passwordChanged: boolean
};
class ChangeUserPassword extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
oldPassword: "",
password: "",
loading: false,
passwordConfirmationError: false,
validatePasswordError: false,
validatePassword: "",
passwordChanged: false
};
}
setLoadingState = () => {
this.setState({
...this.state,
loading: true
});
};
setErrorState = (error: Error) => {
this.setState({
...this.state,
error: error,
loading: false
});
};
setSuccessfulState = () => {
this.setState({
...this.state,
loading: false,
passwordChanged: true,
oldPassword: "",
password: ""
});
};
submit = (event: Event) => {
event.preventDefault();
if (this.state.password) {
const { oldPassword, password } = this.state;
this.setLoadingState();
changePassword(this.props.me._links.password.href, oldPassword, password)
.then(result => {
if (result.error) {
this.setErrorState(result.error);
} else {
this.setSuccessfulState();
}
})
.catch(err => {
this.setErrorState(err);
});
}
};
render() {
const { t } = this.props;
const { loading, passwordChanged, error } = this.state;
let message = null;
if (passwordChanged) {
message = (
<Notification
type={"success"}
children={t("password.changedSuccessfully")}
onClose={() => this.onClose()}
/>
);
} else if (error) {
message = <ErrorNotification error={error} />;
}
return (
<form onSubmit={this.submit}>
{message}
<InputField
label={t("password.currentPassword")}
type="password"
onChange={oldPassword =>
this.setState({ ...this.state, oldPassword })
}
value={this.state.oldPassword ? this.state.oldPassword : ""}
helpText={t("password.currentPasswordHelpText")}
/>
<PasswordConfirmation
passwordChanged={this.passwordChanged}
key={this.state.passwordChanged ? "changed" : "unchanged"}
/>
<SubmitButton
disabled={!this.state.password}
loading={loading}
label={t("password.submit")}
/>
</form>
);
}
passwordChanged = (password: string) => {
this.setState({ ...this.state, password });
};
onClose = () => {
this.setState({
...this.state,
passwordChanged: false
});
};
}
export default translate("commons")(ChangeUserPassword);

View File

@@ -19,6 +19,7 @@ import SingleGroup from "../groups/containers/SingleGroup";
import AddGroup from "../groups/containers/AddGroup"; import AddGroup from "../groups/containers/AddGroup";
import Config from "../config/containers/Config"; import Config from "../config/containers/Config";
import ChangeUserPassword from "./ChangeUserPassword";
import Profile from "./Profile"; import Profile from "./Profile";
type Props = { type Props = {
@@ -79,6 +80,7 @@ class Main extends React.Component<Props> {
path="/user/:name" path="/user/:name"
component={SingleUser} component={SingleUser}
/> />
<ProtectedRoute <ProtectedRoute
exact exact
path="/groups" path="/groups"
@@ -107,7 +109,6 @@ class Main extends React.Component<Props> {
authenticated={authenticated} authenticated={authenticated}
/> />
<ProtectedRoute <ProtectedRoute
exact
path="/me" path="/me"
component={Profile} component={Profile}
authenticated={authenticated} authenticated={authenticated}

View File

@@ -2,82 +2,82 @@
import React from "react"; import React from "react";
import { import { Route, withRouter } from "react-router-dom";
Page,
Navigation,
Section,
MailLink
} from "../../../scm-ui-components/packages/ui-components/src/index";
import { NavLink } from "react-router-dom";
import { getMe } from "../modules/auth"; import { getMe } from "../modules/auth";
import { compose } from "redux"; import { compose } from "redux";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import type { Me } from "../../../scm-ui-components/packages/ui-types/src/index"; import type { Me } from "@scm-manager/ui-types";
import AvatarWrapper from "../repos/components/changesets/AvatarWrapper"; import {
import { ErrorPage } from "@scm-manager/ui-components"; ErrorPage,
Page,
Navigation,
Section,
NavLink
} from "@scm-manager/ui-components";
import ChangeUserPassword from "./ChangeUserPassword";
import ProfileInfo from "./ProfileInfo";
type Props = { type Props = {
me: Me, me: Me,
// Context props // Context props
t: string => string t: string => string,
match: any
}; };
type State = {}; type State = {};
class Profile extends React.Component<Props, State> { class Profile extends React.Component<Props, State> {
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
return url.substring(0, url.length - 2);
}
return url;
};
matchedUrl = () => {
return this.stripEndingSlash(this.props.match.url);
};
render() { render() {
const url = this.matchedUrl();
const { me, t } = this.props; const { me, t } = this.props;
if (me) { if (!me) {
return ( return (
<ErrorPage <ErrorPage
title={t("profile.error-title")} title={t("profile.error-title")}
subtitle={t("profile.error-subtitle")} subtitle={t("profile.error-subtitle")}
error={{ name: "Error", message: "'me' is undefined" }} error={{
name: t("profile.error"),
message: t("profile.error-message")
}}
/> />
); );
} }
return ( return (
<Page title={me.displayName}> <Page title={me.displayName}>
<div className="columns"> <div className="columns">
<AvatarWrapper> <div className="column is-three-quarters">
<div> <Route path={url} exact render={() => <ProfileInfo me={me} />} />
<figure className="media-left"> <Route
<p className="image is-64x64"> path={`${url}/password`}
{ render={() => <ChangeUserPassword me={me} />}
// TODO: add avatar />
}
</p>
</figure>
</div> </div>
</AvatarWrapper> <div className="column">
<div className="column is-two-quarters">
<table className="table">
<tbody>
<tr>
<td>{t("profile.username")}</td>
<td>{me.name}</td>
</tr>
<tr>
<td>{t("profile.displayName")}</td>
<td>{me.displayName}</td>
</tr>
<tr>
<td>{t("profile.mail")}</td>
<td>
<MailLink address={me.mail} />
</td>
</tr>
</tbody>
</table>
</div>
<div className="column is-one-quarter">
<Navigation> <Navigation>
<Section label={t("profile.actions-label")} /> <Section label={t("profile.navigation-label")}>
<NavLink to={"me/password"}> <NavLink to={`${url}`} label={t("profile.information")} />
{t("profile.change-password")} </Section>
</NavLink> <Section label={t("profile.actions-label")}>
<NavLink
to={`${url}/password`}
label={t("profile.change-password")}
/>
</Section>
</Navigation> </Navigation>
</div> </div>
</div> </div>
@@ -94,5 +94,6 @@ const mapStateToProps = state => {
export default compose( export default compose(
translate("commons"), translate("commons"),
connect(mapStateToProps) connect(mapStateToProps),
withRouter
)(Profile); )(Profile);

View File

@@ -0,0 +1,56 @@
// @flow
import React from "react";
import AvatarWrapper from "../repos/components/changesets/AvatarWrapper";
import type { Me } from "@scm-manager/ui-types";
import { MailLink } from "@scm-manager/ui-components";
import { compose } from "redux";
import { translate } from "react-i18next";
type Props = {
me: Me,
// Context props
t: string => string
};
type State = {};
class ProfileInfo extends React.Component<Props, State> {
render() {
const { me, t } = this.props;
return (
<>
<AvatarWrapper>
<div>
<figure className="media-left">
<p className="image is-64x64">
{
// TODO: add avatar
}
</p>
</figure>
</div>
</AvatarWrapper>
<table className="table">
<tbody>
<tr>
<td>{t("profile.username")}</td>
<td>{me.name}</td>
</tr>
<tr>
<td>{t("profile.displayName")}</td>
<td>{me.displayName}</td>
</tr>
<tr>
<td>{t("profile.mail")}</td>
<td>
<MailLink address={me.mail} />
</td>
</tr>
</tbody>
</table>
</>
);
}
}
export default compose(translate("commons"))(ProfileInfo);

View File

@@ -15,11 +15,14 @@ i18n
.init({ .init({
fallbackLng: "en", fallbackLng: "en",
// try to load only "en" and not "en_US"
load: "languageOnly",
// have a common namespace used around the full app // have a common namespace used around the full app
ns: ["commons"], ns: ["commons"],
defaultNS: "commons", defaultNS: "commons",
debug: true, debug: false,
interpolation: { interpolation: {
escapeValue: false // not needed for react!! escapeValue: false // not needed for react!!

View File

@@ -2,7 +2,10 @@
import type { Me } from "@scm-manager/ui-types"; import type { Me } from "@scm-manager/ui-types";
import * as types from "./types"; import * as types from "./types";
import { apiClient, UNAUTHORIZED_ERROR } from "@scm-manager/ui-components"; import {
apiClient,
UNAUTHORIZED_ERROR_MESSAGE
} from "@scm-manager/ui-components";
import { isPending } from "./pending"; import { isPending } from "./pending";
import { getFailure } from "./failure"; import { getFailure } from "./failure";
import { import {
@@ -136,10 +139,12 @@ const callFetchMe = (link: string): Promise<Me> => {
return response.json(); return response.json();
}) })
.then(json => { .then(json => {
const { name, displayName, mail, _links } = json;
return { return {
name: json.name, name,
displayName: json.displayName, displayName,
mail: json.mail mail,
_links
}; };
}); });
}; };
@@ -185,7 +190,7 @@ export const fetchMe = (link: string) => {
dispatch(fetchMeSuccess(me)); dispatch(fetchMeSuccess(me));
}) })
.catch((error: Error) => { .catch((error: Error) => {
if (error === UNAUTHORIZED_ERROR) { if (error.message === UNAUTHORIZED_ERROR_MESSAGE) {
dispatch(fetchMeUnauthenticated()); dispatch(fetchMeUnauthenticated());
} else { } else {
dispatch(fetchMeFailure(error)); dispatch(fetchMeFailure(error));

View File

@@ -0,0 +1,16 @@
// @flow
import { apiClient } from "@scm-manager/ui-components";
export const CONTENT_TYPE_PASSWORD_CHANGE =
"application/vnd.scmm-passwordChange+json;v=2";
export function changePassword(
url: string,
oldPassword: string,
newPassword: string
) {
return apiClient
.put(url, { oldPassword, newPassword }, CONTENT_TYPE_PASSWORD_CHANGE)
.then(response => {
return response;
});
}

View File

@@ -0,0 +1,25 @@
import fetchMock from "fetch-mock";
import { changePassword, CONTENT_TYPE_PASSWORD_CHANGE } from "./changePassword";
describe("change password", () => {
const CHANGE_PASSWORD_URL = "/me/password";
const oldPassword = "old";
const newPassword = "new";
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it("should update password", done => {
fetchMock.put("/api/v2" + CHANGE_PASSWORD_URL, 204, {
headers: { "content-type": CONTENT_TYPE_PASSWORD_CHANGE }
});
changePassword(CHANGE_PASSWORD_URL, oldPassword, newPassword).then(
content => {
done();
}
);
});
});

View File

@@ -11,6 +11,9 @@ describe("RepositoryNavLink", () => {
it("should render nothing, if the sources link is missing", () => { it("should render nothing, if the sources link is missing", () => {
const repository = { const repository = {
namespace: "Namespace",
name: "Repo",
type: "GIT",
_links: {} _links: {}
}; };
@@ -20,6 +23,7 @@ describe("RepositoryNavLink", () => {
linkName="sources" linkName="sources"
to="/sources" to="/sources"
label="Sources" label="Sources"
activeOnlyWhenExact={true}
/>, />,
options.get() options.get()
); );
@@ -28,6 +32,9 @@ describe("RepositoryNavLink", () => {
it("should render the navLink", () => { it("should render the navLink", () => {
const repository = { const repository = {
namespace: "Namespace",
name: "Repo",
type: "GIT",
_links: { _links: {
sources: { sources: {
href: "/sources" href: "/sources"
@@ -41,6 +48,7 @@ describe("RepositoryNavLink", () => {
linkName="sources" linkName="sources"
to="/sources" to="/sources"
label="Sources" label="Sources"
activeOnlyWhenExact={true}
/>, />,
options.get() options.get()
); );

View File

@@ -35,7 +35,7 @@ import PermissionsNavLink from "../components/PermissionsNavLink";
import Sources from "../sources/containers/Sources"; import Sources from "../sources/containers/Sources";
import RepositoryNavLink from "../components/RepositoryNavLink"; import RepositoryNavLink from "../components/RepositoryNavLink";
import { getRepositoriesLink } from "../../modules/indexResource"; import { getRepositoriesLink } from "../../modules/indexResource";
import {ExtensionPoint} from '@scm-manager/ui-extensions'; import {ExtensionPoint} from "@scm-manager/ui-extensions";
type Props = { type Props = {
namespace: string, namespace: string,

View File

@@ -96,7 +96,7 @@ class FileTree extends React.Component<Props> {
}); });
} }
if (tree._embedded) { if (tree._embedded && tree._embedded.children) {
files.push(...tree._embedded.children.sort(compareFiles)); files.push(...tree._embedded.children.sort(compareFiles));
} }

View File

@@ -8,7 +8,13 @@ describe("create link tests", () => {
return { return {
name: "dir", name: "dir",
path: path, path: path,
directory: true directory: true,
length: 1,
revision: "1a",
_links: {},
_embedded: {
children: []
}
}; };
} }

View File

@@ -91,7 +91,7 @@ export default function reducer(
state: any = {}, state: any = {},
action: Action = { type: "UNKNOWN" } action: Action = { type: "UNKNOWN" }
): any { ): any {
if (action.type === FETCH_SOURCES_SUCCESS) { if (action.itemId && action.type === FETCH_SOURCES_SUCCESS) {
return { return {
[action.itemId]: action.payload, [action.itemId]: action.payload,
...state ...state

View File

@@ -33,7 +33,13 @@ const repository: Repository = {
}; };
const collection = { const collection = {
name: "src",
path: "src",
directory: true,
description: "foo",
length: 176,
revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4", revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4",
subRepository: undefined,
_links: { _links: {
self: { self: {
href: href:
@@ -41,20 +47,24 @@ const collection = {
} }
}, },
_embedded: { _embedded: {
files: [ children: [
{ {
name: "src", name: "src",
path: "src", path: "src",
directory: true, directory: true,
description: null, description: "",
length: 176, length: 176,
lastModified: null, revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4",
subRepository: null, lastModified: "",
subRepository: undefined,
_links: { _links: {
self: { self: {
href: href:
"http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/src" "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/src"
} }
},
_embedded: {
children: []
} }
}, },
{ {
@@ -63,8 +73,9 @@ const collection = {
directory: false, directory: false,
description: "bump version", description: "bump version",
length: 780, length: 780,
revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4",
lastModified: "2017-07-31T11:17:19Z", lastModified: "2017-07-31T11:17:19Z",
subRepository: null, subRepository: undefined,
_links: { _links: {
self: { self: {
href: href:
@@ -74,6 +85,9 @@ const collection = {
href: href:
"http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/history/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/package.json" "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/history/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/package.json"
} }
},
_embedded: {
children: []
} }
} }
] ]
@@ -92,7 +106,9 @@ const noDirectory: File = {
"http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/src" "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/src"
} }
}, },
_embedded: collection _embedded: {
children: []
}
}; };
describe("sources fetch", () => { describe("sources fetch", () => {
@@ -116,7 +132,7 @@ describe("sources fetch", () => {
]; ];
const store = mockStore({}); const store = mockStore({});
return store.dispatch(fetchSources(repository)).then(() => { return store.dispatch(fetchSources(repository, "", "")).then(() => {
expect(store.getActions()).toEqual(expectedActions); expect(store.getActions()).toEqual(expectedActions);
}); });
}); });
@@ -145,7 +161,7 @@ describe("sources fetch", () => {
}); });
const store = mockStore({}); const store = mockStore({});
return store.dispatch(fetchSources(repository)).then(() => { return store.dispatch(fetchSources(repository, "", "")).then(() => {
const actions = store.getActions(); const actions = store.getActions();
expect(actions[0].type).toBe(FETCH_SOURCES_PENDING); expect(actions[0].type).toBe(FETCH_SOURCES_PENDING);
expect(actions[1].type).toBe(FETCH_SOURCES_FAILURE); expect(actions[1].type).toBe(FETCH_SOURCES_FAILURE);
@@ -166,7 +182,7 @@ describe("reducer tests", () => {
"scm/core/_/": collection "scm/core/_/": collection
}; };
expect( expect(
reducer({}, fetchSourcesSuccess(repository, null, null, collection)) reducer({}, fetchSourcesSuccess(repository, "", "", collection))
).toEqual(expectedState); ).toEqual(expectedState);
}); });
@@ -207,7 +223,7 @@ describe("selector tests", () => {
}); });
it("should return null", () => { it("should return null", () => {
expect(getSources({}, repository)).toBeFalsy(); expect(getSources({}, repository, "", "")).toBeFalsy();
}); });
it("should return the source collection without revision and path", () => { it("should return the source collection without revision and path", () => {
@@ -216,7 +232,7 @@ describe("selector tests", () => {
"scm/core/_/": collection "scm/core/_/": collection
} }
}; };
expect(getSources(state, repository)).toBe(collection); expect(getSources(state, repository, "", "")).toBe(collection);
}); });
it("should return the source collection with revision and path", () => { it("should return the source collection with revision and path", () => {
@@ -234,11 +250,11 @@ describe("selector tests", () => {
[FETCH_SOURCES + "/scm/core/_/"]: true [FETCH_SOURCES + "/scm/core/_/"]: true
} }
}; };
expect(isFetchSourcesPending(state, repository)).toEqual(true); expect(isFetchSourcesPending(state, repository, "", "")).toEqual(true);
}); });
it("should return false, when fetch sources is not pending", () => { it("should return false, when fetch sources is not pending", () => {
expect(isFetchSourcesPending({}, repository)).toEqual(false); expect(isFetchSourcesPending({}, repository, "", "")).toEqual(false);
}); });
const error = new Error("incredible error from hell"); const error = new Error("incredible error from hell");
@@ -249,10 +265,10 @@ describe("selector tests", () => {
[FETCH_SOURCES + "/scm/core/_/"]: error [FETCH_SOURCES + "/scm/core/_/"]: error
} }
}; };
expect(getFetchSourcesFailure(state, repository)).toEqual(error); expect(getFetchSourcesFailure(state, repository, "", "")).toEqual(error);
}); });
it("should return undefined when fetch sources did not fail", () => { it("should return undefined when fetch sources did not fail", () => {
expect(getFetchSourcesFailure({}, repository)).toBe(undefined); expect(getFetchSourcesFailure({}, repository, "", "")).toBe(undefined);
}); });
}); });

View File

@@ -2,14 +2,13 @@
import React from "react"; import React from "react";
import type { User } from "@scm-manager/ui-types"; import type { User } from "@scm-manager/ui-types";
import { import {
InputField,
SubmitButton, SubmitButton,
Notification, Notification,
ErrorNotification ErrorNotification,
PasswordConfirmation
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import * as userValidator from "./userValidation";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { updatePassword } from "./updatePassword"; import { setPassword } from "./setPassword";
type Props = { type Props = {
user: User, user: User,
@@ -19,9 +18,6 @@ type Props = {
type State = { type State = {
password: string, password: string,
loading: boolean, loading: boolean,
passwordConfirmationError: boolean,
validatePasswordError: boolean,
validatePassword: string,
error?: Error, error?: Error,
passwordChanged: boolean passwordChanged: boolean
}; };
@@ -40,12 +36,6 @@ class SetUserPassword extends React.Component<Props, State> {
}; };
} }
passwordIsValid = () => {
return !(
this.state.validatePasswordError || this.state.passwordConfirmationError
);
};
setLoadingState = () => { setLoadingState = () => {
this.setState({ this.setState({
...this.state, ...this.state,
@@ -66,20 +56,17 @@ class SetUserPassword extends React.Component<Props, State> {
...this.state, ...this.state,
loading: false, loading: false,
passwordChanged: true, passwordChanged: true,
password: "", password: ""
validatePassword: "",
validatePasswordError: false,
passwordConfirmationError: false
}); });
}; };
submit = (event: Event) => { submit = (event: Event) => {
event.preventDefault(); event.preventDefault();
if (this.passwordIsValid()) { if (this.state.password) {
const { user } = this.props; const { user } = this.props;
const { password } = this.state; const { password } = this.state;
this.setLoadingState(); this.setLoadingState();
updatePassword(user._links.password.href, password) setPassword(user._links.password.href, password)
.then(result => { .then(result => {
if (result.error) { if (result.error) {
this.setErrorState(result.error); this.setErrorState(result.error);
@@ -112,26 +99,12 @@ class SetUserPassword extends React.Component<Props, State> {
return ( return (
<form onSubmit={this.submit}> <form onSubmit={this.submit}>
{message} {message}
<InputField <PasswordConfirmation
label={t("user.password")} passwordChanged={this.passwordChanged}
type="password" key={this.state.passwordChanged ? "changed" : "unchanged"}
onChange={this.handlePasswordChange}
value={this.state.password ? this.state.password : ""}
validationError={this.state.validatePasswordError}
errorMessage={t("validation.password-invalid")}
helpText={t("help.passwordHelpText")}
/>
<InputField
label={t("validation.validatePassword")}
type="password"
onChange={this.handlePasswordValidationChange}
value={this.state ? this.state.validatePassword : ""}
validationError={this.state.passwordConfirmationError}
errorMessage={t("validation.passwordValidation-invalid")}
helpText={t("help.passwordConfirmHelpText")}
/> />
<SubmitButton <SubmitButton
disabled={!this.passwordIsValid()} disabled={!this.state.password}
loading={loading} loading={loading}
label={t("user-form.submit")} label={t("user-form.submit")}
/> />
@@ -139,31 +112,8 @@ class SetUserPassword extends React.Component<Props, State> {
); );
} }
handlePasswordChange = (password: string) => { passwordChanged = (password: string) => {
const validatePasswordError = !this.checkPasswords( this.setState({ ...this.state, password });
password,
this.state.validatePassword
);
this.setState({
validatePasswordError: !userValidator.isPasswordValid(password),
passwordConfirmationError: validatePasswordError,
password: password
});
};
handlePasswordValidationChange = (validatePassword: string) => {
const passwordConfirmed = this.checkPasswords(
this.state.password,
validatePassword
);
this.setState({
validatePassword,
passwordConfirmationError: !passwordConfirmed
});
};
checkPasswords = (password1: string, password2: string) => {
return password1 === password2;
}; };
onClose = () => { onClose = () => {

View File

@@ -5,6 +5,7 @@ import type { User } from "@scm-manager/ui-types";
import { import {
Checkbox, Checkbox,
InputField, InputField,
PasswordConfirmation,
SubmitButton, SubmitButton,
validation as validator validation as validator
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
@@ -21,10 +22,7 @@ type State = {
user: User, user: User,
mailValidationError: boolean, mailValidationError: boolean,
nameValidationError: boolean, nameValidationError: boolean,
displayNameValidationError: boolean, displayNameValidationError: boolean
passwordConfirmationError: boolean,
validatePasswordError: boolean,
validatePassword: string
}; };
class UserForm extends React.Component<Props, State> { class UserForm extends React.Component<Props, State> {
@@ -38,15 +36,12 @@ class UserForm extends React.Component<Props, State> {
mail: "", mail: "",
password: "", password: "",
admin: false, admin: false,
active: false, active: true,
_links: {} _links: {}
}, },
mailValidationError: false, mailValidationError: false,
displayNameValidationError: false, displayNameValidationError: false,
nameValidationError: false, nameValidationError: false
passwordConfirmationError: false,
validatePasswordError: false,
validatePassword: ""
}; };
} }
@@ -66,14 +61,15 @@ class UserForm extends React.Component<Props, State> {
isValid = () => { isValid = () => {
const user = this.state.user; const user = this.state.user;
const passwordValid = this.props.user ? !this.isFalsy(user.password) : true;
return !( return !(
this.state.validatePasswordError ||
this.state.nameValidationError || this.state.nameValidationError ||
this.state.mailValidationError || this.state.mailValidationError ||
this.state.passwordConfirmationError ||
this.state.displayNameValidationError || this.state.displayNameValidationError ||
this.isFalsy(user.name) || this.isFalsy(user.name) ||
this.isFalsy(user.displayName) this.isFalsy(user.displayName) ||
this.isFalsy(user.mail) ||
passwordValid
); );
}; };
@@ -89,7 +85,7 @@ class UserForm extends React.Component<Props, State> {
const user = this.state.user; const user = this.state.user;
let nameField = null; let nameField = null;
let passwordFields = null; let passwordChangeField = null;
if (!this.props.user) { if (!this.props.user) {
nameField = ( nameField = (
<InputField <InputField
@@ -101,27 +97,9 @@ class UserForm extends React.Component<Props, State> {
helpText={t("help.usernameHelpText")} helpText={t("help.usernameHelpText")}
/> />
); );
passwordFields = (
<> passwordChangeField = (
<InputField <PasswordConfirmation passwordChanged={this.handlePasswordChange} />
label={t("user.password")}
type="password"
onChange={this.handlePasswordChange}
value={user ? user.password : ""}
validationError={this.state.validatePasswordError}
errorMessage={t("validation.password-invalid")}
helpText={t("help.passwordHelpText")}
/>
<InputField
label={t("validation.validatePassword")}
type="password"
onChange={this.handlePasswordValidationChange}
value={this.state ? this.state.validatePassword : ""}
validationError={this.state.passwordConfirmationError}
errorMessage={t("validation.passwordValidation-invalid")}
helpText={t("help.passwordConfirmHelpText")}
/>
</>
); );
} }
return ( return (
@@ -143,7 +121,7 @@ class UserForm extends React.Component<Props, State> {
errorMessage={t("validation.mail-invalid")} errorMessage={t("validation.mail-invalid")}
helpText={t("help.mailHelpText")} helpText={t("help.mailHelpText")}
/> />
{passwordFields} {passwordChangeField}
<Checkbox <Checkbox
label={t("user.admin")} label={t("user.admin")}
onChange={this.handleAdminChange} onChange={this.handleAdminChange}
@@ -189,32 +167,11 @@ class UserForm extends React.Component<Props, State> {
}; };
handlePasswordChange = (password: string) => { handlePasswordChange = (password: string) => {
const validatePasswordError = !this.checkPasswords(
password,
this.state.validatePassword
);
this.setState({ this.setState({
validatePasswordError: !userValidator.isPasswordValid(password),
passwordConfirmationError: validatePasswordError,
user: { ...this.state.user, password } user: { ...this.state.user, password }
}); });
}; };
handlePasswordValidationChange = (validatePassword: string) => {
const validatePasswordError = this.checkPasswords(
this.state.user.password,
validatePassword
);
this.setState({
validatePassword,
passwordConfirmationError: !validatePasswordError
});
};
checkPasswords = (password1: string, password2: string) => {
return password1 === password2;
};
handleAdminChange = (admin: boolean) => { handleAdminChange = (admin: boolean) => {
this.setState({ user: { ...this.state.user, admin } }); this.setState({ user: { ...this.state.user, admin } });
}; };

View File

@@ -1,15 +1,13 @@
//@flow //@flow
import { apiClient } from "@scm-manager/ui-components"; import { apiClient } from "@scm-manager/ui-components";
const CONTENT_TYPE_PASSWORD_OVERWRITE =
export const CONTENT_TYPE_PASSWORD_OVERWRITE =
"application/vnd.scmm-passwordOverwrite+json;v=2"; "application/vnd.scmm-passwordOverwrite+json;v=2";
export function updatePassword(url: string, password: string) { export function setPassword(url: string, password: string) {
return apiClient return apiClient
.put(url, { newPassword: password }, CONTENT_TYPE_PASSWORD_OVERWRITE) .put(url, { newPassword: password }, CONTENT_TYPE_PASSWORD_OVERWRITE)
.then(response => { .then(response => {
return response; return response;
})
.catch(err => {
return { error: err };
}); });
} }

View File

@@ -0,0 +1,25 @@
//@flow
import fetchMock from "fetch-mock";
import { CONTENT_TYPE_PASSWORD_OVERWRITE, setPassword } from "./setPassword";
describe("password change", () => {
const SET_PASSWORD_URL = "/users/testuser/password";
const newPassword = "testpw123";
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it("should set password", done => {
fetchMock.put("/api/v2" + SET_PASSWORD_URL, 204, {
headers: {
"content-type": CONTENT_TYPE_PASSWORD_OVERWRITE
}
});
setPassword(SET_PASSWORD_URL, newPassword).then(content => {
done();
});
});
});

View File

@@ -1,23 +0,0 @@
//@flow
import fetchMock from "fetch-mock";
import { updatePassword } from "./updatePassword";
describe("get content type", () => {
const PASSWORD_URL = "/users/testuser/password";
const password = "testpw123";
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it("should update password", done => {
fetchMock.put("/api/v2" + PASSWORD_URL, 204);
updatePassword(PASSWORD_URL, password).then(content => {
done();
});
});
});

View File

@@ -52,7 +52,7 @@ public final class DebugService
private final Multimap<NamespaceAndName,DebugHookData> receivedHooks = LinkedListMultimap.create(); private final Multimap<NamespaceAndName,DebugHookData> receivedHooks = LinkedListMultimap.create();
/** /**
* Stores {@link DebugHookData} for the given repository. * Store {@link DebugHookData} for the given repository.
*/ */
void put(NamespaceAndName namespaceAndName, DebugHookData hookData) void put(NamespaceAndName namespaceAndName, DebugHookData hookData)
{ {

View File

@@ -39,7 +39,6 @@ import com.google.inject.Singleton;
import org.apache.shiro.concurrent.SubjectAwareExecutorService; import org.apache.shiro.concurrent.SubjectAwareExecutorService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException;
import sonia.scm.ConfigurationException; import sonia.scm.ConfigurationException;
import sonia.scm.HandlerEventType; import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter; import sonia.scm.ManagerDaoAdapter;
@@ -138,17 +137,18 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return managerDaoAdapter.create( return managerDaoAdapter.create(
repository, repository,
RepositoryPermissions::create, RepositoryPermissions::create,
newRepository -> fireEvent(HandlerEventType.BEFORE_CREATE, newRepository),
newRepository -> { newRepository -> {
fireEvent(HandlerEventType.CREATE, newRepository);
if (initRepository) { if (initRepository) {
try { try {
getHandler(newRepository).create(newRepository); getHandler(newRepository).create(newRepository);
} catch (AlreadyExistsException e) { } catch (InternalRepositoryException e) {
throw new InternalRepositoryException(repository, "directory for repository does already exist", e); delete(repository);
throw e;
} }
} }
fireEvent(HandlerEventType.BEFORE_CREATE, newRepository);
}, },
newRepository -> fireEvent(HandlerEventType.CREATE, newRepository),
newRepository -> repositoryDAO.contains(newRepository.getNamespaceAndName()) newRepository -> repositoryDAO.contains(newRepository.getNamespaceAndName())
); );
} }

View File

@@ -39,9 +39,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadContext;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import sonia.scm.AlreadyExistsException; import sonia.scm.AlreadyExistsException;
@@ -49,6 +51,7 @@ import sonia.scm.HandlerEventType;
import sonia.scm.Manager; import sonia.scm.Manager;
import sonia.scm.ManagerTestBase; import sonia.scm.ManagerTestBase;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.event.ScmEventBus; import sonia.scm.event.ScmEventBus;
import sonia.scm.io.DefaultFileSystem; import sonia.scm.io.DefaultFileSystem;
@@ -62,6 +65,7 @@ import sonia.scm.security.KeyGenerator;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -106,10 +110,18 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private ScmConfiguration configuration; private ScmConfiguration configuration;
private String mockedNamespace = "default_namespace"; private String mockedNamespace = "default_namespace";
@Before
public void initContext() {
((TempSCMContextProvider)SCMContext.getContext()).setBaseDirectory(temp);
}
@Test @Test
public void testCreate() { public void testCreate() {
Repository heartOfGold = createTestRepository(); Repository heartOfGold = createTestRepository();
@@ -422,10 +434,10 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) { private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) {
DefaultFileSystem fileSystem = new DefaultFileSystem(); DefaultFileSystem fileSystem = new DefaultFileSystem();
Set<RepositoryHandler> handlerSet = new HashSet<>(); Set<RepositoryHandler> handlerSet = new HashSet<>();
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(contextProvider, fileSystem); InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(contextProvider);
XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(initialRepositoryLocationResolver, contextProvider); XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(initialRepositoryLocationResolver, fileSystem, contextProvider);
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repositoryDAO, initialRepositoryLocationResolver); RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repositoryDAO, initialRepositoryLocationResolver);
ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(repositoryLocationResolver); ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver);
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver)); handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver));
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) { handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
@Override @Override

View File

@@ -70,8 +70,7 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
public void createSecuritySystem() public void createSecuritySystem()
{ {
JAXBConfigurationEntryStoreFactory factory = JAXBConfigurationEntryStoreFactory factory =
new JAXBConfigurationEntryStoreFactory(new UUIDKeyGenerator(), new JAXBConfigurationEntryStoreFactory(contextProvider , repositoryLocationResolver, new UUIDKeyGenerator() );
contextProvider);
securitySystem = new DefaultSecuritySystem(factory); securitySystem = new DefaultSecuritySystem(factory);

View File

@@ -3,7 +3,7 @@ package sonia.scm.security;
import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware; import com.github.sdorra.shiro.SubjectAware;
import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationException;
import org.junit.Ignore; import org.apache.shiro.util.ThreadContext;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -30,6 +30,10 @@ public class SecurityRequestFilterTest {
@InjectMocks @InjectMocks
private SecurityRequestFilter securityRequestFilter; private SecurityRequestFilter securityRequestFilter;
{
ThreadContext.unbindSubject();
}
@Test @Test
public void shouldAllowUnauthenticatedAccessForAnnotatedMethod() throws NoSuchMethodException { public void shouldAllowUnauthenticatedAccessForAnnotatedMethod() throws NoSuchMethodException {
when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("anonymousAccessAllowed")); when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("anonymousAccessAllowed"));

View File

@@ -45,6 +45,9 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.user.xml.XmlUserDAO; import sonia.scm.user.xml.XmlUserDAO;
@@ -72,7 +75,7 @@ public class DefaultUserManagerTest extends UserManagerTestBase
public ShiroRule shiro = new ShiroRule(); public ShiroRule shiro = new ShiroRule();
private UserDAO userDAO = mock(UserDAO.class); private UserDAO userDAO ;
private User trillian; private User trillian;
/** /**
@@ -182,6 +185,6 @@ public class DefaultUserManagerTest extends UserManagerTestBase
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
private XmlUserDAO createXmlUserDAO() { private XmlUserDAO createXmlUserDAO() {
return new XmlUserDAO(new JAXBConfigurationStoreFactory(locationResolver)); return new XmlUserDAO(new JAXBConfigurationStoreFactory(contextProvider, locationResolver));
} }
} }