merged with 2.0.0-m3

This commit is contained in:
Florian Scholdei
2018-12-21 14:31:23 +01:00
56 changed files with 1461 additions and 369 deletions

View File

@@ -72,7 +72,7 @@ public interface ConfigurationStore<T>
* Stores the given configuration object to the store.
*
*
* @param obejct configuration object to store
* @param object configuration object to store
*/
void set(T object);
}

View File

@@ -128,7 +128,7 @@ public class AuthenticationFilter extends HttpFilter
}
else if (subject.isAuthenticated())
{
logger.trace("user is allready authenticated");
logger.trace("user is already authenticated");
processChain(request, response, chain, subject);
}
else if (isAnonymousAccessEnabled())

View File

@@ -9,13 +9,17 @@ import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.web.GitVndMediaType;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
/**
* RESTful Web Service Resource to manage the configuration of the git plugin.
*/
@@ -26,13 +30,15 @@ public class GitConfigResource {
private final GitConfigDtoToGitConfigMapper dtoToConfigMapper;
private final GitConfigToGitConfigDtoMapper configToDtoMapper;
private final GitRepositoryHandler repositoryHandler;
private final Provider<GitRepositoryConfigResource> gitRepositoryConfigResource;
@Inject
public GitConfigResource(GitConfigDtoToGitConfigMapper dtoToConfigMapper, GitConfigToGitConfigDtoMapper configToDtoMapper,
GitRepositoryHandler repositoryHandler) {
GitRepositoryHandler repositoryHandler, Provider<GitRepositoryConfigResource> gitRepositoryConfigResource) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper;
this.repositoryHandler = repositoryHandler;
this.gitRepositoryConfigResource = gitRepositoryConfigResource;
}
/**
@@ -88,4 +94,9 @@ public class GitConfigResource {
return Response.noContent().build();
}
@Path("{namespace}/{name}")
public GitRepositoryConfigResource getRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name) {
return gitRepositoryConfigResource.get();
}
}

View File

@@ -0,0 +1,19 @@
package sonia.scm.api.v2.resources;
import com.github.legman.Subscribe;
import sonia.scm.EagerSingleton;
import sonia.scm.event.ScmEventBus;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.ClearRepositoryCacheEvent;
import java.util.Objects;
@EagerSingleton @Extension
public class GitRepositoryConfigChangeClearRepositoryCacheListener {
@Subscribe
public void sendClearRepositoryCacheEvent(GitRepositoryConfigChangedEvent event) {
if (!Objects.equals(event.getOldConfig().getDefaultBranch(), event.getNewConfig().getDefaultBranch())) {
ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(event.getRepository()));
}
}
}

View File

@@ -0,0 +1,30 @@
package sonia.scm.api.v2.resources;
import sonia.scm.event.Event;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.Repository;
@Event
public class GitRepositoryConfigChangedEvent {
private final Repository repository;
private final GitRepositoryConfig oldConfig;
private final GitRepositoryConfig newConfig;
public GitRepositoryConfigChangedEvent(Repository repository, GitRepositoryConfig oldConfig, GitRepositoryConfig newConfig) {
this.repository = repository;
this.oldConfig = oldConfig;
this.newConfig = newConfig;
}
public Repository getRepository() {
return repository;
}
public GitRepositoryConfig getOldConfig() {
return oldConfig;
}
public GitRepositoryConfig getNewConfig() {
return newConfig;
}
}

View File

@@ -0,0 +1,24 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("squid:S2160") // there is no proper semantic for equals on this dto
public class GitRepositoryConfigDto extends HalRepresentation {
private String defaultBranch;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,37 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.AbstractRepositoryJsonEnricher;
import javax.inject.Inject;
import javax.inject.Provider;
@Extension
public class GitRepositoryConfigEnricher extends AbstractRepositoryJsonEnricher {
private final Provider<ScmPathInfoStore> scmPathInfoStore;
private final RepositoryManager manager;
@Inject
public GitRepositoryConfigEnricher(Provider<ScmPathInfoStore> scmPathInfoStore, ObjectMapper objectMapper, RepositoryManager manager) {
super(objectMapper);
this.scmPathInfoStore = scmPathInfoStore;
this.manager = manager;
}
@Override
protected void enrichRepositoryNode(JsonNode repositoryNode, String namespace, String name) {
if (GitRepositoryHandler.TYPE_NAME.equals(manager.get(new NamespaceAndName(namespace, name)).getType())) {
String repositoryConfigLink = new LinkBuilder(scmPathInfoStore.get().get(), GitConfigResource.class)
.method("getRepositoryConfig")
.parameters(namespace, name)
.href();
addLink(repositoryNode, "configuration", repositoryConfigLink);
}
}
}

View File

@@ -0,0 +1,46 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class GitRepositoryConfigMapper {
@Inject
private ScmPathInfoStore scmPathInfoStore;
public abstract GitRepositoryConfigDto map(GitRepositoryConfig config, @Context Repository repository);
public abstract GitRepositoryConfig map(GitRepositoryConfigDto dto);
@AfterMapping
void appendLinks(@MappingTarget GitRepositoryConfigDto target, @Context Repository repository) {
Links.Builder linksBuilder = linkingTo().self(self());
if (RepositoryPermissions.modify(repository).isPermitted()) {
linksBuilder.single(link("update", update()));
}
target.add(linksBuilder.build());
}
private String self() {
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), GitConfigResource.class);
return linkBuilder.method("get").parameters().href();
}
private String update() {
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), GitConfigResource.class);
return linkBuilder.method("update").parameters().href();
}
}

View File

@@ -0,0 +1,90 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.web.GitVndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
public class GitRepositoryConfigResource {
private static final Logger LOG = LoggerFactory.getLogger(GitRepositoryConfigResource.class);
private final GitRepositoryConfigMapper repositoryConfigMapper;
private final RepositoryManager repositoryManager;
private final GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider;
@Inject
public GitRepositoryConfigResource(GitRepositoryConfigMapper repositoryConfigMapper, RepositoryManager repositoryManager, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
this.repositoryConfigMapper = repositoryConfigMapper;
this.repositoryManager = repositoryManager;
this.gitRepositoryConfigStoreProvider = gitRepositoryConfigStoreProvider;
}
@GET
@Path("/")
@Produces(GitVndMediaType.GIT_REPOSITORY_CONFIG)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository config"),
@ResponseCode(code = 404, condition = "not found, no repository with the specified namespace and name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name) {
Repository repository = getRepository(namespace, name);
ConfigurationStore<GitRepositoryConfig> repositoryConfigStore = getStore(repository);
GitRepositoryConfig config = repositoryConfigStore.get();
GitRepositoryConfigDto dto = repositoryConfigMapper.map(config, repository);
return Response.ok(dto).build();
}
@PUT
@Path("/")
@Consumes(GitVndMediaType.GIT_REPOSITORY_CONFIG)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the privilege to change this repositories config"),
@ResponseCode(code = 404, condition = "not found, no repository with the specified namespace and name available/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response setRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name, GitRepositoryConfigDto dto) {
Repository repository = getRepository(namespace, name);
ConfigurationStore<GitRepositoryConfig> repositoryConfigStore = getStore(repository);
GitRepositoryConfig config = repositoryConfigMapper.map(dto);
repositoryConfigStore.set(config);
LOG.info("git default branch of repository {} has changed, sending clear cache event", repository.getNamespaceAndName());
return Response.noContent().build();
}
private Repository getRepository(@PathParam("namespace") String namespace, @PathParam("name") String name) {
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
Repository repository = repositoryManager.get(namespaceAndName);
if (repository == null) {
throw notFound(entity(namespaceAndName));
}
return repository;
}
private ConfigurationStore<GitRepositoryConfig> getStore(Repository repository) {
return gitRepositoryConfigStoreProvider.get(repository);
}
}

View File

@@ -0,0 +1,50 @@
package sonia.scm.api.v2.resources;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.Repository;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
import javax.inject.Inject;
public class GitRepositoryConfigStoreProvider {
private final ConfigurationStoreFactory configurationStoreFactory;
@Inject
public GitRepositoryConfigStoreProvider(ConfigurationStoreFactory configurationStoreFactory) {
this.configurationStoreFactory = configurationStoreFactory;
}
public ConfigurationStore<GitRepositoryConfig> get(Repository repository) {
return new StoreWrapper(configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(), repository);
}
private static class StoreWrapper implements ConfigurationStore<GitRepositoryConfig> {
private final ConfigurationStore<GitRepositoryConfig> delegate;
private final Repository repository;
private StoreWrapper(ConfigurationStore<GitRepositoryConfig> delegate, Repository repository) {
this.delegate = delegate;
this.repository = repository;
}
@Override
public GitRepositoryConfig get() {
GitRepositoryConfig config = delegate.get();
if (config == null) {
return new GitRepositoryConfig();
}
return config;
}
@Override
public void set(GitRepositoryConfig newConfig) {
GitRepositoryConfig oldConfig = get();
delegate.set(newConfig);
ScmEventBus.getInstance().post(new GitRepositoryConfigChangedEvent(repository, oldConfig, newConfig));
}
}
}

View File

@@ -1,49 +0,0 @@
/**
* Copyright (c) 2014, 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;
/**
* Constants for Git.
*
* @author Sebastian Sdorra
* @since 1.50
*/
public final class GitConstants {
/**
* Default branch repository property.
*/
public static final String PROPERTY_DEFAULT_BRANCH = "git.default-branch";
private GitConstants() {
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2014, 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;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
/**
* The GitHeadModifier is able to modify the head of a git repository.
*
* @author Sebastian Sdorra
* @since 1.61
*/
public class GitHeadModifier {
private static final Logger LOG = LoggerFactory.getLogger(GitHeadModifier.class);
private final GitRepositoryHandler repositoryHandler;
@Inject
public GitHeadModifier(GitRepositoryHandler repositoryHandler) {
this.repositoryHandler = repositoryHandler;
}
/**
* Ensures that the repositories head points to the given branch. The method will return {@code false} if the
* repositories head points already to the given branch.
*
* @param repository repository to modify
* @param newHead branch which should be the new head of the repository
*
* @return {@code true} if the head has changed
*/
public boolean ensure(Repository repository, String newHead) {
try (org.eclipse.jgit.lib.Repository gitRepository = open(repository)) {
String currentHead = resolve(gitRepository);
if (!Objects.equals(currentHead, newHead)) {
return modify(gitRepository, newHead);
}
} catch (IOException ex) {
LOG.warn("failed to change head of repository", ex);
}
return false;
}
private String resolve(org.eclipse.jgit.lib.Repository gitRepository) throws IOException {
Ref ref = gitRepository.getRefDatabase().getRef(Constants.HEAD);
if ( ref.isSymbolic() ) {
ref = ref.getTarget();
}
return GitUtil.getBranch(ref);
}
private boolean modify(org.eclipse.jgit.lib.Repository gitRepository, String newHead) throws IOException {
RefUpdate refUpdate = gitRepository.getRefDatabase().newUpdate(Constants.HEAD, true);
refUpdate.setForceUpdate(true);
RefUpdate.Result result = refUpdate.link(Constants.R_HEADS + newHead);
return result == RefUpdate.Result.FORCED;
}
private org.eclipse.jgit.lib.Repository open(Repository repository) throws IOException {
File directory = repositoryHandler.getDirectory(repository.getId());
return GitUtil.open(directory);
}
}

View File

@@ -0,0 +1,27 @@
package sonia.scm.repository;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "config")
@XmlAccessorType(XmlAccessType.FIELD)
public class GitRepositoryConfig {
public GitRepositoryConfig() {
}
public GitRepositoryConfig(String defaultBranch) {
this.defaultBranch = defaultBranch;
}
private String defaultBranch;
public String getDefaultBranch() {
return defaultBranch;
}
public void setDefaultBranch(String defaultBranch) {
this.defaultBranch = defaultBranch;
}
}

View File

@@ -31,67 +31,45 @@
package sonia.scm.repository;
import com.github.legman.Subscribe;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.HandlerEventType;
import sonia.scm.event.ScmEventBus;
import sonia.scm.api.v2.resources.GitRepositoryConfigChangedEvent;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.plugin.Extension;
import javax.inject.Inject;
/**
* Repository listener which handles git related repository events.
*
*
* @author Sebastian Sdorra
* @since 1.50
*/
@Extension
@EagerSingleton
public class GitRepositoryModifyListener {
/**
* the logger for GitRepositoryModifyListener
*/
private static final Logger logger = LoggerFactory.getLogger(GitRepositoryModifyListener.class);
private final GitHeadModifier headModifier;
private final GitRepositoryConfigStoreProvider storeProvider;
@Inject
public GitRepositoryModifyListener(GitHeadModifier headModifier, GitRepositoryConfigStoreProvider storeProvider) {
this.headModifier = headModifier;
this.storeProvider = storeProvider;
}
/**
* Receives {@link RepositoryModificationEvent} and fires a {@link ClearRepositoryCacheEvent} if
* the default branch of a git repository was modified.
*
*
* @param event repository modification event
*/
@Subscribe
public void handleEvent(RepositoryModificationEvent event){
Repository repository = event.getItem();
if ( isModifyEvent(event) &&
isGitRepository(event.getItem()) &&
hasDefaultBranchChanged(event.getItemBeforeModification(), repository))
{
logger.info("git default branch of repository {} has changed, sending clear cache event", repository.getId());
sendClearRepositoryCacheEvent(repository);
public void handleEvent(GitRepositoryConfigChangedEvent event){
Repository repository = event.getRepository();
String defaultBranch = storeProvider.get(repository).get().getDefaultBranch();
if (defaultBranch != null) {
headModifier.ensure(repository, defaultBranch);
}
}
@VisibleForTesting
protected void sendClearRepositoryCacheEvent(Repository repository) {
ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository));
}
private boolean isModifyEvent(RepositoryEvent event) {
return event.getEventType() == HandlerEventType.MODIFY;
}
private boolean isGitRepository(Repository repository) {
return GitRepositoryHandler.TYPE_NAME.equals(repository.getType());
}
private boolean hasDefaultBranchChanged(Repository old, Repository current) {
return !Objects.equal(
old.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH),
current.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH)
);
}
}

View File

@@ -40,7 +40,6 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitConstants;
import sonia.scm.repository.GitUtil;
import java.io.IOException;
@@ -110,7 +109,7 @@ public class AbstractGitCommand
protected Ref getBranchOrDefault(Repository gitRepository, String requestedBranch) throws IOException {
if ( Strings.isNullOrEmpty(requestedBranch) ) {
String defaultBranchName = repository.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH);
String defaultBranchName = context.getConfig().getDefaultBranch();
if (!Strings.isNullOrEmpty(defaultBranchName)) {
return GitUtil.getBranchId(gitRepository, defaultBranchName);
} else {

View File

@@ -37,6 +37,8 @@ package sonia.scm.repository.spi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.Repository;
@@ -68,10 +70,11 @@ public class GitContext implements Closeable
* @param directory
* @param repository
*/
public GitContext(File directory, Repository repository)
public GitContext(File directory, Repository repository, GitRepositoryConfigStoreProvider storeProvider)
{
this.directory = directory;
this.repository = repository;
this.storeProvider = storeProvider;
}
//~--- methods --------------------------------------------------------------
@@ -117,11 +120,25 @@ public class GitContext implements Closeable
return directory;
}
GitRepositoryConfig getConfig() {
GitRepositoryConfig config = storeProvider.get(repository).get();
if (config == null) {
return new GitRepositoryConfig();
} else {
return config;
}
}
void setConfig(GitRepositoryConfig newConfig) {
storeProvider.get(repository).set(newConfig);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final File directory;
private final Repository repository;
private final GitRepositoryConfigStoreProvider storeProvider;
/** Field description */
private org.eclipse.jgit.lib.Repository gitRepository;

View File

@@ -205,7 +205,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
ObjectId ancestorId = null;
if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) {
ancestorId = computeCommonAncestor(request, repository, startId, branch);
ancestorId = repository.resolve(request.getAncestorChangeset());
}
revWalk = new RevWalk(repository);
@@ -225,16 +225,15 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
revWalk.markStart(revWalk.lookupCommit(branch.getObjectId()));
}
if (ancestorId != null) {
revWalk.markUninteresting(revWalk.lookupCommit(ancestorId));
}
Iterator<RevCommit> iterator = revWalk.iterator();
while (iterator.hasNext()) {
RevCommit commit = iterator.next();
if (commit.getId().equals(ancestorId)) {
break;
}
if ((counter >= start)
&& ((limit < 0) || (counter < start + limit))) {
changesetList.add(converter.createChangeset(commit));

View File

@@ -34,6 +34,7 @@
package sonia.scm.repository.spi;
import com.google.common.collect.ImmutableSet;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.Feature;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -73,10 +74,10 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
//~--- constructors ---------------------------------------------------------
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository) {
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider) {
this.handler = handler;
this.repository = repository;
this.context = new GitContext(handler.getDirectory(repository.getId()), repository);
this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider);
}
//~--- methods --------------------------------------------------------------

View File

@@ -35,6 +35,7 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -47,10 +48,12 @@ import sonia.scm.repository.Repository;
public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
private final GitRepositoryHandler handler;
private final GitRepositoryConfigStoreProvider storeProvider;
@Inject
public GitRepositoryServiceResolver(GitRepositoryHandler handler) {
public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider) {
this.handler = handler;
this.storeProvider = storeProvider;
}
@Override
@@ -58,7 +61,7 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
GitRepositoryServiceProvider provider = null;
if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
provider = new GitRepositoryServiceProvider(handler, repository);
provider = new GitRepositoryServiceProvider(handler, repository, storeProvider);
}
return provider;

View File

@@ -40,6 +40,7 @@ import org.eclipse.jgit.transport.ScmTransportProtocol;
import org.mapstruct.factory.Mappers;
import sonia.scm.api.v2.resources.GitConfigDtoToGitConfigMapper;
import sonia.scm.api.v2.resources.GitConfigToGitConfigDtoMapper;
import sonia.scm.api.v2.resources.GitRepositoryConfigMapper;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.GitWorkdirFactory;
import sonia.scm.repository.spi.SimpleGitWorkdirFactory;
@@ -65,6 +66,7 @@ public class GitServletModule extends ServletModule
bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass());
bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass());
bind(GitRepositoryConfigMapper.class).to(Mappers.getMapper(GitRepositoryConfigMapper.class).getClass());
bind(GitWorkdirFactory.class).to(SimpleGitWorkdirFactory.class);
}

View File

@@ -2,6 +2,7 @@ package sonia.scm.web;
public class GitVndMediaType {
public static final String GIT_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX;
public static final String GIT_REPOSITORY_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX;
private GitVndMediaType() {
}

View File

@@ -0,0 +1,155 @@
// @flow
import React from "react";
import {apiClient, BranchSelector, ErrorPage, Loading, SubmitButton} from "@scm-manager/ui-components";
import type {Branch, Repository} from "@scm-manager/ui-types";
import {translate} from "react-i18next";
type Props = {
repository: Repository,
t: string => string
};
type State = {
loadingBranches: boolean,
loadingDefaultBranch: boolean,
submitPending: boolean,
error?: Error,
branches: Branch[],
selectedBranchName?: string,
defaultBranchChanged: boolean
};
const GIT_CONFIG_CONTENT_TYPE = "application/vnd.scmm-gitConfig+json";
class RepositoryConfig extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
loadingBranches: true,
loadingDefaultBranch: true,
submitPending: false,
branches: [],
defaultBranchChanged: false
};
}
componentDidMount() {
const { repository } = this.props;
this.setState({ ...this.state, loadingBranches: true });
apiClient
.get(repository._links.branches.href)
.then(response => response.json())
.then(payload => payload._embedded.branches)
.then(branches =>
this.setState({ ...this.state, branches, loadingBranches: false })
)
.catch(error => this.setState({ ...this.state, error }));
this.setState({ ...this.state, loadingDefaultBranch: true });
apiClient
.get(repository._links.configuration.href)
.then(response => response.json())
.then(payload => payload.defaultBranch)
.then(selectedBranchName =>
this.setState({
...this.state,
selectedBranchName,
loadingDefaultBranch: false
})
)
.catch(error => this.setState({ ...this.state, error }));
}
branchSelected = (branch: Branch) => {
if (!branch) {
this.setState({ ...this.state, selectedBranchName: undefined, defaultBranchChanged: false});
return;
}
this.setState({ ...this.state, selectedBranchName: branch.name, defaultBranchChanged: false });
};
submit = (event: Event) => {
event.preventDefault();
const { repository } = this.props;
const newConfig = {
defaultBranch: this.state.selectedBranchName
};
this.setState({ ...this.state, submitPending: true });
apiClient
.put(
repository._links.configuration.href,
newConfig,
GIT_CONFIG_CONTENT_TYPE
)
.then(() =>
this.setState({
...this.state,
submitPending: false,
defaultBranchChanged: true
})
)
.catch(error => this.setState({ ...this.state, error }));
};
render() {
const { t } = this.props;
const { loadingBranches, loadingDefaultBranch, submitPending, error } = this.state;
if (error) {
return (
<ErrorPage
title={t("scm-git-plugin.repo-config.error.title")}
subtitle={t("scm-git-plugin.repo-config.error.subtitle")}
error={error}
/>
);
}
if (!(loadingBranches || loadingDefaultBranch)) {
return (
<>
{this.renderBranchChangedNotification()}
<form onSubmit={this.submit}>
<BranchSelector
label={t("scm-git-plugin.repo-config.default-branch")}
branches={this.state.branches}
selected={this.branchSelected}
selectedBranch={this.state.selectedBranchName}
/>
<SubmitButton
label={t("scm-git-plugin.repo-config.submit")}
loading={submitPending}
disabled={!this.state.selectedBranchName}
/>
</form>
</>
);
} else {
return <Loading />;
}
}
renderBranchChangedNotification = () => {
if (this.state.defaultBranchChanged) {
return (
<div className="notification is-primary">
<button
className="delete"
onClick={() =>
this.setState({ ...this.state, defaultBranchChanged: false })
}
/>
{this.props.t("scm-git-plugin.repo-config.success")}
</div>
);
}
return null;
};
}
export default translate("plugins")(RepositoryConfig);

View File

@@ -1,11 +1,13 @@
//@flow
import { binder } from "@scm-manager/ui-extensions";
import React from "react";
import {binder} from "@scm-manager/ui-extensions";
import ProtocolInformation from "./ProtocolInformation";
import GitAvatar from "./GitAvatar";
import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components";
import {ConfigurationBinder as cfgBinder} from "@scm-manager/ui-components";
import GitGlobalConfiguration from "./GitGlobalConfiguration";
import GitMergeInformation from "./GitMergeInformation";
import RepositoryConfig from "./RepositoryConfig";
// repository
@@ -13,10 +15,29 @@ const gitPredicate = (props: Object) => {
return props.repository && props.repository.type === "git";
};
binder.bind("repos.repository-details.information", ProtocolInformation, gitPredicate);
binder.bind("repos.repository-merge.information", GitMergeInformation, gitPredicate);
binder.bind(
"repos.repository-details.information",
ProtocolInformation,
gitPredicate
);
binder.bind(
"repos.repository-merge.information",
GitMergeInformation,
gitPredicate
);
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
cfgBinder.bindRepository(
"/configuration",
"scm-git-plugin.repo-config.link",
"configuration",
RepositoryConfig
);
// global config
cfgBinder.bindGlobal("/git", "scm-git-plugin.config.link", "gitConfig", GitGlobalConfiguration);
cfgBinder.bindGlobal(
"/git",
"scm-git-plugin.config.link",
"gitConfig",
GitGlobalConfiguration
);

View File

@@ -1,9 +1,9 @@
{
"scm-git-plugin": {
"information": {
"clone" : "Clone the repository",
"create" : "Create a new repository",
"replace" : "Push an existing repository",
"clone": "Clone the repository",
"create": "Create a new repository",
"replace": "Push an existing repository",
"merge": {
"heading": "How to merge source branch into target branch",
"checkout": "1. Make sure your workspace is clean and checkout target branch",
@@ -22,6 +22,16 @@
"disabled": "Disabled",
"disabledHelpText": "Enable or disable the Git plugin",
"submit": "Submit"
},
"repo-config": {
"link": "Configuration",
"default-branch": "Default branch",
"submit": "Submit",
"error": {
"title": "Error",
"subtitle": "Something went wrong"
},
"success": "Default branch changed!"
}
}
}

View File

@@ -1,7 +1,5 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import org.jboss.resteasy.core.Dispatcher;
@@ -14,22 +12,33 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.web.GitVndMediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import static com.google.inject.util.Providers.of;
import static junit.framework.TestCase.assertTrue;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
@SubjectAware(
@@ -55,30 +64,48 @@ public class GitConfigResourceTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ScmPathInfoStore scmPathInfoStore;
@Mock
private RepositoryManager repositoryManager;
@InjectMocks
private GitConfigToGitConfigDtoMapperImpl configToDtoMapper;
@InjectMocks
private GitRepositoryConfigMapperImpl repositoryConfigMapper;
@Mock
private GitRepositoryHandler repositoryHandler;
@Mock(answer = Answers.CALLS_REAL_METHODS)
private ConfigurationStoreFactory configurationStoreFactory;
@Spy
private ConfigurationStore<Object> configurationStore;
@Captor
private ArgumentCaptor<Object> configurationStoreCaptor;
@Before
public void prepareEnvironment() {
GitConfig gitConfig = createConfiguration();
when(repositoryHandler.getConfig()).thenReturn(gitConfig);
GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler);
GitRepositoryConfigResource gitRepositoryConfigResource = new GitRepositoryConfigResource(repositoryConfigMapper, repositoryManager, new GitRepositoryConfigStoreProvider(configurationStoreFactory));
GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, of(gitRepositoryConfigResource));
dispatcher.getRegistry().addSingletonResource(gitConfigResource);
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
}
@Before
public void initConfigStore() {
when(configurationStoreFactory.getStore(any())).thenReturn(configurationStore);
doNothing().when(configurationStore).set(configurationStoreCaptor.capture());
}
@Test
@SubjectAware(username = "readWrite")
public void shouldGetGitConfig() throws URISyntaxException, IOException {
public void shouldGetGitConfig() throws URISyntaxException {
MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
String responseString = response.getContentAsString();
ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class);
assertTrue(responseString.contains("\"disabled\":false"));
assertTrue(responseString.contains("\"gcExpression\":\"valid Git GC Cron Expression\""));
@@ -88,7 +115,7 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "readWrite")
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, IOException {
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException {
when(repositoryHandler.getConfig()).thenReturn(null);
MockHttpResponse response = get();
@@ -124,12 +151,84 @@ public class GitConfigResourceTest {
@Test
@SubjectAware(username = "readOnly")
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException {
thrown.expectMessage("Subject does not have permission [configuration:write:git]");
put();
}
@Test
@SubjectAware(username = "writeOnly")
public void shouldReadDefaultRepositoryConfig() throws URISyntaxException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertThat(response.getContentAsString())
.contains("\"defaultBranch\":null")
.contains("self")
.contains("update");
}
@Test
@SubjectAware(username = "readOnly")
public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertThat(response.getContentAsString())
.contains("\"defaultBranch\":null")
.contains("self")
.doesNotContain("update");
}
@Test
@SubjectAware(username = "writeOnly")
public void shouldReadStoredRepositoryConfig() throws URISyntaxException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
GitRepositoryConfig gitRepositoryConfig = new GitRepositoryConfig();
gitRepositoryConfig.setDefaultBranch("test");
when(configurationStore.get()).thenReturn(gitRepositoryConfig);
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertThat(response.getContentAsString())
.contains("\"defaultBranch\":\"test\"");
}
@Test
@SubjectAware(username = "writeOnly")
public void shouldStoreChangedRepositoryConfig() throws URISyntaxException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
MockHttpRequest request = MockHttpRequest
.put("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X")
.contentType(GitVndMediaType.GIT_REPOSITORY_CONFIG)
.content("{\"defaultBranch\": \"new\"}".getBytes());
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
assertThat(configurationStoreCaptor.getValue())
.isInstanceOfSatisfying(GitRepositoryConfig.class, x -> { })
.extracting("defaultBranch")
.containsExactly("new");
}
private MockHttpResponse get() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
@@ -153,6 +252,4 @@ public class GitConfigResourceTest {
config.setDisabled(false);
return config;
}
}

View File

@@ -0,0 +1,152 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import com.google.inject.Provider;
import com.google.inject.util.Providers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.JsonEnricherContext;
import sonia.scm.web.VndMediaType;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class GitRepositoryConfigEnricherTest {
private final ObjectMapper objectMapper = new ObjectMapper();
private GitRepositoryConfigEnricher linkEnricher;
private JsonNode rootNode;
@Mock
private RepositoryManager manager;
@BeforeEach
void globalSetUp() {
ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
pathInfoStore.set(() -> URI.create("/"));
Provider<ScmPathInfoStore> pathInfoStoreProvider = Providers.of(pathInfoStore);
linkEnricher = new GitRepositoryConfigEnricher(pathInfoStoreProvider, objectMapper, manager);
}
@Nested
class ForSingleRepository {
@BeforeEach
void setUp() throws IOException {
URL resource = Resources.getResource("sonia/scm/repository/repository-001.json");
rootNode = objectMapper.readTree(resource);
when(manager.get(new NamespaceAndName("scmadmin", "web-resources"))).thenReturn(new Repository("id", "git", "scmadmin", "web-resources"));
}
@Test
void shouldEnrichGitRepositories() {
JsonEnricherContext context = new JsonEnricherContext(
URI.create("/"),
MediaType.valueOf(VndMediaType.REPOSITORY),
rootNode
);
linkEnricher.enrich(context);
String configLink = context.getResponseEntity()
.get("_links")
.get("configuration")
.get("href")
.asText();
assertThat(configLink).isEqualTo("/v2/config/git/scmadmin/web-resources");
}
@Test
void shouldNotEnrichOtherRepositories() {
when(manager.get(new NamespaceAndName("scmadmin", "web-resources"))).thenReturn(new Repository("id", "hg", "scmadmin", "web-resources"));
JsonEnricherContext context = new JsonEnricherContext(
URI.create("/"),
MediaType.valueOf(VndMediaType.REPOSITORY),
rootNode
);
linkEnricher.enrich(context);
JsonNode configLink = context.getResponseEntity()
.get("_links")
.get("configuration");
assertThat(configLink).isNull();
}
}
@Nested
class ForRepositoryCollection {
@BeforeEach
void setUp() throws IOException {
URL resource = Resources.getResource("sonia/scm/repository/repository-collection-001.json");
rootNode = objectMapper.readTree(resource);
when(manager.get(new NamespaceAndName("scmadmin", "web-resources"))).thenReturn(new Repository("id", "git", "scmadmin", "web-resources"));
}
@Test
void shouldEnrichAllRepositories() {
JsonEnricherContext context = new JsonEnricherContext(
URI.create("/"),
MediaType.valueOf(VndMediaType.REPOSITORY_COLLECTION),
rootNode
);
linkEnricher.enrich(context);
context.getResponseEntity()
.get("_embedded")
.withArray("repositories")
.elements()
.forEachRemaining(node -> {
String configLink = node
.get("_links")
.get("configuration")
.get("href")
.asText();
assertThat(configLink).isEqualTo("/v2/config/git/scmadmin/web-resources");
});
}
}
@Test
void shouldNotModifyObjectsWithUnsupportedMediaType() throws IOException {
URL resource = Resources.getResource("sonia/scm/repository/repository-001.json");
rootNode = objectMapper.readTree(resource);
JsonEnricherContext context = new JsonEnricherContext(
URI.create("/"),
MediaType.valueOf(VndMediaType.USER),
rootNode
);
linkEnricher.enrich(context);
boolean hasNewPullRequestLink = context.getResponseEntity()
.get("_links")
.has("configuration");
assertThat(hasNewPullRequestLink).isFalse();
}
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright (c) 2014, 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;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class GitHeadModifierTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Mock
private GitRepositoryHandler repositoryHandler;
@InjectMocks
private GitHeadModifier modifier;
@Test
public void testEnsure() throws IOException, GitAPIException {
Repository repository = RepositoryTestData.createHeartOfGold("git");
File headFile = create(repository, "master");
boolean result = modifier.ensure(repository, "develop");
assertEquals("ref: refs/heads/develop", Files.readFirstLine(headFile, Charsets.UTF_8));
assertTrue(result);
}
@Test
public void testEnsureWithSameBranch() throws IOException, GitAPIException {
Repository repository = RepositoryTestData.createHeartOfGold("git");
create(repository, "develop");
boolean result = modifier.ensure(repository, "develop");
assertFalse(result);
}
private File create(Repository repository, String head) throws IOException, GitAPIException {
File directory = temporaryFolder.newFolder();
Git.init()
.setBare(true)
.setDirectory(directory)
.call();
File headFile = new File(directory, "HEAD");
Files.write(String.format("ref: refs/heads/%s\n", head), headFile, Charsets.UTF_8);
when(repositoryHandler.getDirectory(repository.getId())).thenReturn(directory);
return headFile;
}
}

View File

@@ -53,7 +53,7 @@ import static org.mockito.Mockito.when;
/**
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
@RunWith(MockitoJUnitRunner.Silent.class)
public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock

View File

@@ -1,163 +0,0 @@
/**
* Copyright (c) 2014, 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;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Before;
import sonia.scm.HandlerEventType;
/**
* Unit tests for {@link GitRepositoryModifyListener}.
*
* @author Sebastian Sdorra
*/
public class GitRepositoryModifyListenerTest {
private GitRepositoryModifyTestListener repositoryModifyListener;
/**
* Set up test object.
*/
@Before
public void setUpObjectUnderTest(){
repositoryModifyListener = new GitRepositoryModifyTestListener();
}
/**
* Tests happy path.
*/
@Test
public void testHandleEvent() {
Repository old = RepositoryTestData.createHeartOfGold("git");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(HandlerEventType.MODIFY, current, old);
repositoryModifyListener.handleEvent(event);
assertNotNull(repositoryModifyListener.repository);
assertSame(current, repositoryModifyListener.repository);
}
/**
* Tests with new default branch.
*/
@Test
public void testWithNewDefaultBranch() {
Repository old = RepositoryTestData.createHeartOfGold("git");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(HandlerEventType.MODIFY, current, old);
repositoryModifyListener.handleEvent(event);
assertNotNull(repositoryModifyListener.repository);
assertSame(current, repositoryModifyListener.repository);
}
/**
* Tests with non git repositories.
*/
@Test
public void testNonGitRepository(){
Repository old = RepositoryTestData.createHeartOfGold("hg");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("hg");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(HandlerEventType.MODIFY, current, old);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
/**
* Tests without default branch.
*/
@Test
public void testWithoutDefaultBranch(){
Repository old = RepositoryTestData.createHeartOfGold("git");
Repository current = RepositoryTestData.createHeartOfGold("git");
RepositoryModificationEvent event = new RepositoryModificationEvent(HandlerEventType.MODIFY, current, old);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
/**
* Tests with non modify event.
*/
@Test
public void testNonModifyEvent(){
Repository old = RepositoryTestData.createHeartOfGold("git");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(HandlerEventType.CREATE, current, old);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
/**
* Tests with non git repositories.
*/
@Test
public void testNoModification(){
Repository old = RepositoryTestData.createHeartOfGold("git");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
RepositoryModificationEvent event = new RepositoryModificationEvent(HandlerEventType.MODIFY, current, old);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
private static class GitRepositoryModifyTestListener extends GitRepositoryModifyListener {
private Repository repository;
@Override
protected void sendClearRepositoryCacheEvent(Repository repository) {
this.repository = repository;
}
}
}

View File

@@ -35,6 +35,11 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.After;
import org.junit.Before;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStore;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
/**
*
@@ -51,6 +56,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
public void close()
{
if (context != null) {
context.setConfig(new GitRepositoryConfig());
context.close();
}
}
@@ -65,7 +71,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
{
if (context == null)
{
context = new GitContext(repositoryDirectory, repository);
context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()));
}
return context;

View File

@@ -35,9 +35,11 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.BlameLine;
import sonia.scm.repository.BlameResult;
import sonia.scm.repository.GitConstants;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;
@@ -73,7 +75,7 @@ public class GitBlameCommandTest extends AbstractGitCommandTestBase
assertEquals("fcd0ef1831e4002ac43ea539f4094334c79ea9ec", result.getLine(1).getRevision());
// set default branch and test again
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
createContext().setConfig(new GitRepositoryConfig("test-branch"));
result = createCommand().getBlameResult(request);
assertNotNull(result);
assertEquals(1, result.getTotal());

View File

@@ -32,9 +32,11 @@
package sonia.scm.repository.spi;
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.GitConstants;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;
import java.util.Collection;
@@ -78,7 +80,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase {
@Test
public void testExplicitDefaultBranch() throws IOException {
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
createContext().setConfig(new GitRepositoryConfig("test-branch"));
FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile();
assertNotNull(root);

View File

@@ -38,7 +38,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import sonia.scm.NotFoundException;
import sonia.scm.repository.GitConstants;
import sonia.scm.repository.GitRepositoryConfig;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -67,7 +67,7 @@ public class GitCatCommandTest extends AbstractGitCommandTestBase {
assertEquals("a\nline for blame", execute(request));
// set default branch for repository and check again
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
createContext().setConfig(new GitRepositoryConfig("test-branch"));
assertEquals("a and b", execute(request));
}

View File

@@ -38,7 +38,9 @@ import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Ignore;
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;
@@ -103,7 +105,7 @@ public class GitIncomingCommandTest
commit(outgoing, "added a");
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, null), incomingRepository);
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), incomingRepository);
PullCommandRequest req = new PullCommandRequest();
req.setRemoteRepository(outgoingRepository);
pull.pull(req);
@@ -187,7 +189,7 @@ public class GitIncomingCommandTest
*/
private GitIncomingCommand createCommand()
{
return new GitIncomingCommand(handler, new GitContext(incomingDirectory, null),
return new GitIncomingCommand(handler, new GitContext(incomingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
incomingRepository);
}
}

View File

@@ -0,0 +1,102 @@
/**
* 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.spi;
import org.junit.Test;
import sonia.scm.repository.ChangesetPagingResult;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Unit tests for {@link GitLogCommand} with an ancestor commit. This test uses the following git repository:
*
* <pre>
* * 86e9ca0 (HEAD -> b) b5
* * d69edb3 Merge branch 'master' into b
* |\
* | * 946a8db (master) f
* | * b19b9cc e
* * | 3d6109c b4
* * | 6330653 b3
* * | a49a28e Merge branch 'master' into b
* |\ \
* | |/
* | * 0235584 d
* | * 20251c5 c
* * | 5023b85 b2
* * | 201ecc1 b1
* |/
* * 36b19e4 b
* * c2190a9 a
* </pre>
* @author Sebastian Sdorra
*/
public class GitLogCommandAncestorTest extends AbstractGitCommandTestBase
{
@Override
protected String getZippedRepositoryResource()
{
return "sonia/scm/repository/spi/scm-git-ancestor-test.zip";
}
@Test
public void testGetAncestor()
{
LogCommandRequest request = new LogCommandRequest();
request.setBranch("b");
request.setAncestorChangeset("master");
ChangesetPagingResult result = createCommand().getChangesets(request);
assertNotNull(result);
assertEquals(7, result.getTotal());
assertEquals(7, result.getChangesets().size());
assertEquals("86e9ca012202b36865373a63c12ef4f4353506cd", result.getChangesets().get(0).getId());
assertEquals("d69edb314d07ab20ad626e3101597702d3510b5d", result.getChangesets().get(1).getId());
assertEquals("3d6109c4c830e91eaf12ac6a331a5fccd670fe3c", result.getChangesets().get(2).getId());
assertEquals("63306538d06924d6b254f86541c638021c001141", result.getChangesets().get(3).getId());
assertEquals("a49a28e0beb0ab55f985598d05b8628c2231c9b6", result.getChangesets().get(4).getId());
assertEquals("5023b850c2077db857593a3c0269329c254a370d", result.getChangesets().get(5).getId());
assertEquals("201ecc1131e6b99fb0a0fe9dcbc8c044383e1a07", result.getChangesets().get(6).getId());
}
private GitLogCommand createCommand()
{
return new GitLogCommand(createContext(), repository);
}
}

View File

@@ -36,9 +36,11 @@ package sonia.scm.repository.spi;
import com.google.common.io.Files;
import org.junit.Test;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.GitConstants;
import sonia.scm.repository.ClearRepositoryCacheEvent;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.Modifications;
import java.io.File;
@@ -78,7 +80,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
assertTrue(result.getChangesets().stream().allMatch(r -> r.getBranches().isEmpty()));
// set default branch and fetch again
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
createContext().setConfig(new GitRepositoryConfig("test-branch"));
result = createCommand().getChangesets(new LogCommandRequest());

View File

@@ -18,8 +18,8 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
@Before
public void init() {
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, null), incomingRepository);
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, null), outgoingRepository);
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, null, null), incomingRepository);
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, null, null), outgoingRepository);
}
@Test
@@ -63,12 +63,12 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
}
void pushOutgoingAndPullIncoming() throws IOException {
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, null),
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, null, null),
outgoingRepository);
PushCommandRequest request = new PushCommandRequest();
request.setRemoteRepository(incomingRepository);
cmd.push(request);
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, null),
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, null, null),
incomingRepository);
PullCommandRequest pullRequest = new PullCommandRequest();
pullRequest.setRemoteRepository(incomingRepository);

View File

@@ -38,7 +38,9 @@ package sonia.scm.repository.spi;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;
@@ -104,7 +106,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
commit(outgoing, "added a");
GitPushCommand push = new GitPushCommand(handler,
new GitContext(outgoingDirectory, null),
new GitContext(outgoingDirectory, null, null),
outgoingRepository);
PushCommandRequest req = new PushCommandRequest();
@@ -158,7 +160,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
*/
private GitOutgoingCommand createCommand()
{
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, null),
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
outgoingRepository);
}
}

View File

@@ -98,7 +98,7 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
*/
private GitPushCommand createCommand()
{
return new GitPushCommand(handler, new GitContext(outgoingDirectory, null),
return new GitPushCommand(handler, new GitContext(outgoingDirectory, null, null),
outgoingRepository);
}
}

View File

@@ -1,6 +1,6 @@
[users]
readOnly = secret, reader
writeOnly = secret, writer
readOnly = secret, reader, repoRead
writeOnly = secret, writer, repoWrite
readWrite = secret, readerWriter
admin = secret, admin
@@ -9,3 +9,5 @@ reader = configuration:read:git
writer = configuration:write:git
readerWriter = configuration:*:git
admin = *
repoRead = repository:read:*
repoWrite = repository:modify:*

View File

@@ -0,0 +1,42 @@
{
"creationDate": "2018-11-09T09:48:32.732Z",
"description": "Handling static webresources made easy",
"healthCheckFailures": [],
"lastModified": "2018-11-09T09:49:20.973Z",
"namespace": "scmadmin",
"name": "web-resources",
"archived": false,
"type": "git",
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"delete": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"update": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"permissions": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/permissions/"
},
"protocol": [
{
"href": "http://localhost:8081/scm/repo/scmadmin/web-resources",
"name": "http"
}
],
"tags": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/tags/"
},
"branches": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/branches/"
},
"changesets": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/changesets/"
},
"sources": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/sources/"
}
}
}

View File

@@ -0,0 +1,106 @@
{
"page": 0,
"pageTotal": 1,
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/?page=0&pageSize=10"
},
"first": {
"href": "http://localhost:8081/scm/api/v2/repositories/?page=0&pageSize=10"
},
"last": {
"href": "http://localhost:8081/scm/api/v2/repositories/?page=0&pageSize=10"
},
"create": {
"href": "http://localhost:8081/scm/api/v2/repositories/"
}
},
"_embedded": {
"repositories": [
{
"creationDate": "2018-11-09T09:48:32.732Z",
"description": "Handling static webresources made easy",
"healthCheckFailures": [],
"lastModified": "2018-11-09T09:49:20.973Z",
"namespace": "scmadmin",
"name": "web-resources",
"archived": false,
"type": "git",
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"delete": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"update": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"permissions": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/permissions/"
},
"protocol": [
{
"href": "http://localhost:8081/scm/repo/scmadmin/web-resources",
"name": "http"
}
],
"tags": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/tags/"
},
"branches": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/branches/"
},
"changesets": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/changesets/"
},
"sources": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/sources/"
}
}
},
{
"creationDate": "2018-11-09T09:48:32.732Z",
"description": "Handling static webresources made easy",
"healthCheckFailures": [],
"lastModified": "2018-11-09T09:49:20.973Z",
"namespace": "scmadmin",
"name": "web-resources",
"archived": false,
"type": "git",
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"delete": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"update": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"permissions": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/permissions/"
},
"protocol": [
{
"href": "http://localhost:8081/scm/repo/scmadmin/web-resources",
"name": "http"
}
],
"tags": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/tags/"
},
"branches": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/branches/"
},
"changesets": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/changesets/"
},
"sources": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/sources/"
}
}
}
]
}
}

View File

@@ -37,6 +37,8 @@ package sonia.scm.store;
/**
* In memory configuration store factory for testing purposes.
*
* Use {@link #create()} to get a store that creates the same store on each request.
*
* @author Sebastian Sdorra
*/
@@ -44,6 +46,10 @@ public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFact
private ConfigurationStore store;
public static ConfigurationStoreFactory create() {
return new InMemoryConfigurationStoreFactory(new InMemoryConfigurationStore());
}
public InMemoryConfigurationStoreFactory() {
}

View File

@@ -1,12 +1,10 @@
// @flow
import React from "react";
import type { Branch } from "@scm-manager/ui-types";
import DropDown from "../components/DropDown";
import { translate } from "react-i18next";
import type {Branch} from "@scm-manager/ui-types";
import injectSheet from "react-jss";
import { compose } from "redux";
import classNames from "classnames";
import DropDown from "./forms/DropDown";
const styles = {
zeroflex: {
@@ -25,11 +23,11 @@ const styles = {
type Props = {
branches: Branch[], // TODO: Use generics?
selected: (branch?: Branch) => void,
selectedBranch: string,
selectedBranch?: string,
label: string,
// context props
classes: Object,
t: string => string
classes: Object
};
type State = { selectedBranch?: Branch };
@@ -41,13 +39,12 @@ class BranchSelector extends React.Component<Props, State> {
}
componentDidMount() {
this.props.branches
.filter(branch => branch.name === this.props.selectedBranch)
.forEach(branch => this.setState({ selectedBranch: branch }));
const selectedBranch = this.props.branches.find(branch => branch.name === this.props.selectedBranch);
this.setState({ selectedBranch });
}
render() {
const { branches, classes, t } = this.props;
const { branches, classes, label } = this.props;
if (branches) {
return (
@@ -66,7 +63,7 @@ class BranchSelector extends React.Component<Props, State> {
classes.minWidthOfLabel
)}
>
<label className="label">{t("branch-selector.label")}</label>
<label className="label">{label}</label>
</div>
<div className="field-body">
<div className="field is-narrow">
@@ -93,6 +90,12 @@ class BranchSelector extends React.Component<Props, State> {
branchSelected = (branchName: string) => {
const { branches, selected } = this.props;
if (!branchName) {
this.setState({ selectedBranch: undefined });
selected(undefined);
return;
}
const branch = branches.find(b => b.name === branchName);
selected(branch);
@@ -100,7 +103,4 @@ class BranchSelector extends React.Component<Props, State> {
};
}
export default compose(
injectSheet(styles),
translate("repos")
)(BranchSelector);
export default injectSheet(styles)(BranchSelector);

View File

@@ -7,5 +7,6 @@ export { default as InputField } from "./InputField.js";
export { default as Select } from "./Select.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.js";
export { default as DropDown } from "./DropDown.js";

View File

@@ -24,6 +24,7 @@ export { default as HelpIcon } from "./HelpIcon";
export { default as Tooltip } from "./Tooltip";
export { getPageFromMatch } from "./urls";
export { default as Autocomplete} from "./Autocomplete";
export { default as BranchSelector } from "./BranchSelector";
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR, CONFLICT_ERROR } from "./apiclient.js";

View File

@@ -30,6 +30,16 @@ class LoadingDiff extends React.Component<Props, State> {
}
componentDidMount() {
this.fetchDiff();
}
componentDidUpdate(prevProps: Props) {
if(prevProps.url !== this.props.url){
this.fetchDiff();
}
}
fetchDiff = () => {
const { url } = this.props;
apiClient
.get(url)
@@ -46,15 +56,18 @@ class LoadingDiff extends React.Component<Props, State> {
error
});
});
}
};
render() {
const { diff, loading, error } = this.state;
if (error) {
return <ErrorNotification error={error} />;
} else if (loading || !diff) {
} else if (loading) {
return <Loading />;
} else {
} else if(!diff){
return null;
}
else {
return <Diff diff={diff} />;
}
}

View File

@@ -2,11 +2,15 @@
import React from "react";
import type { Branch, Repository } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import { Route, withRouter } from "react-router-dom";
import Changesets from "./Changesets";
import BranchSelector from "./BranchSelector";
import { connect } from "react-redux";
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
import {
BranchSelector,
ErrorNotification,
Loading
} from "@scm-manager/ui-components";
import {
fetchBranches,
getBranches,
@@ -32,7 +36,8 @@ type Props = {
// Context props
history: any, // TODO flow type
match: any
match: any,
t: string => string
};
class BranchRoot extends React.Component<Props> {
@@ -92,10 +97,11 @@ class BranchRoot extends React.Component<Props> {
}
renderBranchSelector = () => {
const { repository, branches, selected } = this.props;
const { repository, branches, selected, t } = this.props;
if (repository._links.branches) {
return (
<BranchSelector
label={t("branch-selector.label")}
branches={branches}
selectedBranch={selected}
selected={(b: Branch) => {
@@ -133,6 +139,7 @@ const mapStateToProps = (state: any, ownProps: Props) => {
export default compose(
withRouter,
translate("repos"),
connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -1,32 +1,19 @@
//@flow
import React from "react";
import {
deleteRepo,
fetchRepoByName,
getFetchRepoFailure,
getRepository,
isFetchRepoPending
} from "../modules/repos";
import {deleteRepo, fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos";
import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom";
import type { Repository } from "@scm-manager/ui-types";
import {connect} from "react-redux";
import {Route, Switch} from "react-router-dom";
import type {Repository} from "@scm-manager/ui-types";
import {
ErrorPage,
Loading,
Navigation,
NavLink,
Page,
Section
} from "@scm-manager/ui-components";
import { translate } from "react-i18next";
import {ErrorPage, Loading, Navigation, NavLink, Page, Section} from "@scm-manager/ui-components";
import {translate} from "react-i18next";
import RepositoryDetails from "../components/RepositoryDetails";
import DeleteNavAction from "../components/DeleteNavAction";
import Edit from "../containers/Edit";
import Permissions from "../permissions/containers/Permissions";
import type { History } from "history";
import type {History} from "history";
import EditNavLink from "../components/EditNavLink";
import BranchRoot from "./ChangesetsRoot";
@@ -34,8 +21,8 @@ import ChangesetView from "./ChangesetView";
import PermissionsNavLink from "../components/PermissionsNavLink";
import Sources from "../sources/containers/Sources";
import RepositoryNavLink from "../components/RepositoryNavLink";
import { getRepositoriesLink } from "../../modules/indexResource";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import {getRepositoriesLink} from "../../modules/indexResource";
import {ExtensionPoint} from "@scm-manager/ui-extensions";
type Props = {
namespace: string,
@@ -200,15 +187,15 @@ class RepositoryRoot extends React.Component<Props> {
label={t("repository-root.sources")}
activeOnlyWhenExact={false}
/>
<PermissionsNavLink
permissionUrl={`${url}/permissions`}
repository={repository}
/>
<ExtensionPoint
name="repository.navigation"
props={extensionProps}
renderAll={true}
/>
<PermissionsNavLink
permissionUrl={`${url}/permissions`}
repository={repository}
/>
</Section>
<Section label={t("repository-root.actions-label")}>
<DeleteNavAction repository={repository} delete={this.delete} />

View File

@@ -5,7 +5,8 @@ import { withRouter } from "react-router-dom";
import type { Branch, Repository } from "@scm-manager/ui-types";
import FileTree from "../components/FileTree";
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
import BranchSelector from "../../containers/BranchSelector";
import BranchSelector from "../../../../../scm-ui-components/packages/ui-components/src/BranchSelector";
import { translate } from "react-i18next";
import {
fetchBranches,
getBranches,
@@ -32,7 +33,8 @@ type Props = {
// Context props
history: any,
match: any
match: any,
t: string => string
};
class Sources extends React.Component<Props> {
@@ -109,13 +111,14 @@ class Sources extends React.Component<Props> {
}
renderBranchSelector = () => {
const { branches, revision } = this.props;
const { branches, revision, t } = this.props;
if (branches) {
return (
<BranchSelector
branches={branches}
selectedBranch={revision}
label={t("branch-selector.label")}
selected={(b: Branch) => {
this.branchSelected(b);
}}
@@ -160,6 +163,7 @@ const mapDispatchToProps = dispatch => {
};
export default compose(
translate("repos"),
withRouter,
connect(
mapStateToProps,

View File

@@ -115,7 +115,6 @@ $fa-font-path: "webfonts";
.media {
.media-content {
width: calc(50% - 0.75rem);
max-height: 120px;
.shorten-text {
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -112,9 +112,7 @@ import sonia.scm.util.ScmConfigurationUtil;
import sonia.scm.web.UserAgentParser;
import sonia.scm.web.cgi.CGIExecutorFactory;
import sonia.scm.web.cgi.DefaultCGIExecutorFactory;
import sonia.scm.web.filter.AuthenticationFilter;
import sonia.scm.web.filter.LoggingFilter;
import sonia.scm.web.protocol.HttpProtocolServlet;
import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.DefaultAdministrationContext;
@@ -315,8 +313,6 @@ public class ScmServletModule extends ServletModule
bind(TemplateEngineFactory.class);
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class);
filter(HttpProtocolServlet.PATTERN).through(AuthenticationFilter.class);
// bind events
// bind(LastModifiedUpdateListener.class);

View File

@@ -36,6 +36,7 @@ package sonia.scm.api.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
@@ -92,6 +93,7 @@ public class StatusExceptionMapper<E extends Throwable>
return Response.status(status)
.entity(exception.getMessage())
.type(MediaType.TEXT_PLAIN_TYPE)
.build();
}
}

View File

@@ -0,0 +1,50 @@
package sonia.scm.web.filter;
import sonia.scm.Priority;
import sonia.scm.PushStateDispatcher;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.filter.Filters;
import sonia.scm.filter.WebElement;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.UserAgent;
import sonia.scm.web.UserAgentParser;
import sonia.scm.web.WebTokenGenerator;
import sonia.scm.web.protocol.HttpProtocolServlet;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
import static sonia.scm.util.HttpUtil.AUTHENTICATION_REALM;
import static sonia.scm.util.HttpUtil.HEADER_WWW_AUTHENTICATE;
@Priority(Filters.PRIORITY_AUTHENTICATION)
@WebElement(value = HttpProtocolServlet.PATTERN)
public class HttpProtocolServletAuthenticationFilter extends AuthenticationFilter {
private final PushStateDispatcher dispatcher;
private final UserAgentParser userAgentParser;
@Inject
public HttpProtocolServletAuthenticationFilter(
ScmConfiguration configuration,
Set<WebTokenGenerator> tokenGenerators,
PushStateDispatcher dispatcher,
UserAgentParser userAgentParser) {
super(configuration, tokenGenerators);
this.dispatcher = dispatcher;
this.userAgentParser = userAgentParser;
}
@Override
protected void sendUnauthorizedError(HttpServletRequest request, HttpServletResponse response) throws IOException {
UserAgent userAgent = userAgentParser.parse(request);
if (userAgent.isBrowser()) {
dispatcher.dispatch(request, response, request.getRequestURI());
} else {
HttpUtil.sendUnauthorized(request, response);
}
}
}