From bc8c77682142515c9a54edfb0a8b03d7c10a5c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 12 Dec 2018 14:12:55 +0100 Subject: [PATCH 01/19] Introduce config rest endpoint for default git branch --- .../api/v2/resources/GitConfigResource.java | 13 ++- .../v2/resources/GitRepositoryConfigDto.java | 24 +++++ .../GitRepositoryConfigEnricher.java | 43 +++++++++ .../GitRepositoryConfigResource.java | 52 +++++++++++ ...yConfigToGitRepositoryConfigDtoMapper.java | 45 +++++++++ .../scm/repository/GitRepositoryConfig.java | 14 +++ .../java/sonia/scm/web/GitServletModule.java | 2 + .../java/sonia/scm/web/GitVndMediaType.java | 1 + .../v2/resources/GitConfigResourceTest.java | 92 ++++++++++++++++--- .../sonia/scm/configuration/shiro.ini | 6 +- 10 files changed, 278 insertions(+), 14 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigToGitRepositoryConfigDtoMapper.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index 1384d73d9c..e078b04b08 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -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; @Inject public GitConfigResource(GitConfigDtoToGitConfigMapper dtoToConfigMapper, GitConfigToGitConfigDtoMapper configToDtoMapper, - GitRepositoryHandler repositoryHandler) { + GitRepositoryHandler repositoryHandler, Provider 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(); + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java new file mode 100644 index 0000000000..d22d6c194e --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigDto.java @@ -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); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java new file mode 100644 index 0000000000..55c565f162 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java @@ -0,0 +1,43 @@ +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.web.JsonEnricherBase; +import sonia.scm.web.JsonEnricherContext; + +import javax.inject.Inject; +import javax.inject.Provider; + +import static java.util.Collections.singletonMap; +import static sonia.scm.web.VndMediaType.REPOSITORY; + +@Extension +public class GitRepositoryConfigEnricher extends JsonEnricherBase { + + private final Provider scmPathInfoStore; + + @Inject + public GitRepositoryConfigEnricher(Provider scmPathInfoStore, ObjectMapper objectMapper) { + super(objectMapper); + this.scmPathInfoStore = scmPathInfoStore; + } + + @Override + public void enrich(JsonEnricherContext context) { + if (resultHasMediaType(REPOSITORY, context)) { + JsonNode repositoryNode = context.getResponseEntity(); + String namespace = repositoryNode.get("namespace").asText(); + String name = repositoryNode.get("name").asText(); + + String newPullRequest = new LinkBuilder(scmPathInfoStore.get().get(), GitConfigResource.class) + .method("getRepositoryConfig") + .parameters(namespace, name) + .href(); + + JsonNode newPullRequestNode = createObject(singletonMap("href", value(newPullRequest))); + + addPropertyNode(repositoryNode.get("_links"), "configuration", newPullRequestNode); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java new file mode 100644 index 0000000000..2ff637031b --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java @@ -0,0 +1,52 @@ +package sonia.scm.api.v2.resources; + +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.store.ConfigurationStoreFactory; +import sonia.scm.web.GitVndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.GET; +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 final GitRepositoryConfigToGitRepositoryConfigDtoMapper repositoryConfigToDtoMapper; + private final RepositoryManager repositoryManager; + private final ConfigurationStoreFactory configurationStoreFactory; + + @Inject + public GitRepositoryConfigResource(GitRepositoryConfigToGitRepositoryConfigDtoMapper repositoryConfigToDtoMapper, RepositoryManager repositoryManager, ConfigurationStoreFactory configurationStoreFactory) { + this.repositoryConfigToDtoMapper = repositoryConfigToDtoMapper; + this.repositoryManager = repositoryManager; + this.configurationStoreFactory = configurationStoreFactory; + } + + @GET + @Path("/") + @Produces(GitVndMediaType.GIT_REPOSITORY_CONFIG) + public Response getRepositoryConfig(@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)); + } + + ConfigurationStore repositoryConfigStore = configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(); + GitRepositoryConfig config = repositoryConfigStore.get(); + if (config == null) { + config = new GitRepositoryConfig(); + } + GitRepositoryConfigDto dto = repositoryConfigToDtoMapper.map(config, repository); + return Response.ok(dto).build(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigToGitRepositoryConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigToGitRepositoryConfigDtoMapper.java new file mode 100644 index 0000000000..07fd83b700 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigToGitRepositoryConfigDtoMapper.java @@ -0,0 +1,45 @@ +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 GitRepositoryConfigToGitRepositoryConfigDtoMapper { + + @Inject + private ScmPathInfoStore scmPathInfoStore; + + public abstract GitRepositoryConfigDto map(GitRepositoryConfig config, @Context Repository repository); + + @AfterMapping + void appendLinks(GitRepositoryConfig config, @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(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java new file mode 100644 index 0000000000..ae41987247 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java @@ -0,0 +1,14 @@ +package sonia.scm.repository; + +public class GitRepositoryConfig { + + private String defaultBranch; + + public String getDefaultBranch() { + return defaultBranch; + } + + public void setDefaultBranch(String defaultBranch) { + this.defaultBranch = defaultBranch; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java index a3dac0e7d1..609ee6cb0b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java @@ -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.GitRepositoryConfigToGitRepositoryConfigDtoMapper; 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(GitRepositoryConfigToGitRepositoryConfigDtoMapper.class).to(Mappers.getMapper(GitRepositoryConfigToGitRepositoryConfigDtoMapper.class).getClass()); bind(GitWorkdirFactory.class).to(SimpleGitWorkdirFactory.class); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java index 8c81c6eefa..9bfa9fe63e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java @@ -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() { } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index 5bf68d3827..7bc0e5fe02 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -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; @@ -18,18 +16,25 @@ import org.mockito.InjectMocks; import org.mockito.Mock; 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.when; @SubjectAware( @@ -55,30 +60,45 @@ public class GitConfigResourceTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private ScmPathInfoStore scmPathInfoStore; + @Mock + private RepositoryManager repositoryManager; + @InjectMocks private GitConfigToGitConfigDtoMapperImpl configToDtoMapper; + @InjectMocks + private GitRepositoryConfigToGitRepositoryConfigDtoMapperImpl repositoryConfigToDtoMapper; @Mock private GitRepositoryHandler repositoryHandler; + @Mock(answer = Answers.CALLS_REAL_METHODS) + private ConfigurationStoreFactory configurationStoreFactory; + @Mock + private ConfigurationStore configurationStore; + @Before public void prepareEnvironment() { GitConfig gitConfig = createConfiguration(); when(repositoryHandler.getConfig()).thenReturn(gitConfig); - GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); + GitRepositoryConfigResource gitRepositoryConfigResource = new GitRepositoryConfigResource(repositoryConfigToDtoMapper, repositoryManager, 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); + } + @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 +108,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 +144,64 @@ 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\""); + } + private MockHttpResponse get() throws URISyntaxException { MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); @@ -153,6 +225,4 @@ public class GitConfigResourceTest { config.setDisabled(false); return config; } - } - diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini index 5d30a000f2..8a8ff98c2f 100644 --- a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini +++ b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini @@ -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:* From b4081aaa99bfad0a5ac8726b68a8382b4a9b21da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 12 Dec 2018 15:04:56 +0100 Subject: [PATCH 02/19] Add endpoint to set default branch --- ...er.java => GitRepositoryConfigMapper.java} | 5 ++- .../GitRepositoryConfigResource.java | 41 ++++++++++++++----- .../scm/repository/GitRepositoryConfig.java | 6 +++ .../java/sonia/scm/web/GitServletModule.java | 4 +- .../v2/resources/GitConfigResourceTest.java | 35 ++++++++++++++-- 5 files changed, 73 insertions(+), 18 deletions(-) rename scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/{GitRepositoryConfigToGitRepositoryConfigDtoMapper.java => GitRepositoryConfigMapper.java} (86%) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigToGitRepositoryConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java similarity index 86% rename from scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigToGitRepositoryConfigDtoMapper.java rename to scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java index 07fd83b700..6480e526b1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigToGitRepositoryConfigDtoMapper.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java @@ -17,15 +17,16 @@ 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 GitRepositoryConfigToGitRepositoryConfigDtoMapper { +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(GitRepositoryConfig config, @MappingTarget GitRepositoryConfigDto target, @Context Repository repository) { + void appendLinks(@MappingTarget GitRepositoryConfigDto target, @Context Repository repository) { Links.Builder linksBuilder = linkingTo().self(self()); if (RepositoryPermissions.modify(repository).isPermitted()) { linksBuilder.single(link("update", update())); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java index 2ff637031b..af3501ae9a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java @@ -9,7 +9,9 @@ import sonia.scm.store.ConfigurationStoreFactory; 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; @@ -20,13 +22,13 @@ import static sonia.scm.NotFoundException.notFound; public class GitRepositoryConfigResource { - private final GitRepositoryConfigToGitRepositoryConfigDtoMapper repositoryConfigToDtoMapper; + private final GitRepositoryConfigMapper repositoryConfigMapper; private final RepositoryManager repositoryManager; private final ConfigurationStoreFactory configurationStoreFactory; @Inject - public GitRepositoryConfigResource(GitRepositoryConfigToGitRepositoryConfigDtoMapper repositoryConfigToDtoMapper, RepositoryManager repositoryManager, ConfigurationStoreFactory configurationStoreFactory) { - this.repositoryConfigToDtoMapper = repositoryConfigToDtoMapper; + public GitRepositoryConfigResource(GitRepositoryConfigMapper repositoryConfigMapper, RepositoryManager repositoryManager, ConfigurationStoreFactory configurationStoreFactory) { + this.repositoryConfigMapper = repositoryConfigMapper; this.repositoryManager = repositoryManager; this.configurationStoreFactory = configurationStoreFactory; } @@ -35,18 +37,37 @@ public class GitRepositoryConfigResource { @Path("/") @Produces(GitVndMediaType.GIT_REPOSITORY_CONFIG) public Response getRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name) { + Repository repository = getRepository(namespace, name); + ConfigurationStore repositoryConfigStore = getStore(repository); + GitRepositoryConfig config = repositoryConfigStore.get(); + if (config == null) { + config = new GitRepositoryConfig(); + } + GitRepositoryConfigDto dto = repositoryConfigMapper.map(config, repository); + return Response.ok(dto).build(); + } + + @PUT + @Path("/") + @Consumes(GitVndMediaType.GIT_REPOSITORY_CONFIG) + public Response setRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name, GitRepositoryConfigDto dto) { + Repository repository = getRepository(namespace, name); + ConfigurationStore repositoryConfigStore = getStore(repository); + GitRepositoryConfig config = repositoryConfigMapper.map(dto); + repositoryConfigStore.set(config); + 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; + } - ConfigurationStore repositoryConfigStore = configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(); - GitRepositoryConfig config = repositoryConfigStore.get(); - if (config == null) { - config = new GitRepositoryConfig(); - } - GitRepositoryConfigDto dto = repositoryConfigToDtoMapper.map(config, repository); - return Response.ok(dto).build(); + private ConfigurationStore getStore(Repository repository) { + return configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java index ae41987247..a548d82153 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java @@ -1,5 +1,11 @@ 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 { private String defaultBranch; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java index 609ee6cb0b..0bbb993cc6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java @@ -40,7 +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.GitRepositoryConfigToGitRepositoryConfigDtoMapper; +import sonia.scm.api.v2.resources.GitRepositoryConfigMapper; import sonia.scm.plugin.Extension; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.spi.SimpleGitWorkdirFactory; @@ -66,7 +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(GitRepositoryConfigToGitRepositoryConfigDtoMapper.class).to(Mappers.getMapper(GitRepositoryConfigToGitRepositoryConfigDtoMapper.class).getClass()); + bind(GitRepositoryConfigMapper.class).to(Mappers.getMapper(GitRepositoryConfigMapper.class).getClass()); bind(GitWorkdirFactory.class).to(SimpleGitWorkdirFactory.class); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index 7bc0e5fe02..f4de22b2b5 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -12,8 +12,11 @@ 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; @@ -35,6 +38,7 @@ 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( @@ -66,21 +70,23 @@ public class GitConfigResourceTest { @InjectMocks private GitConfigToGitConfigDtoMapperImpl configToDtoMapper; @InjectMocks - private GitRepositoryConfigToGitRepositoryConfigDtoMapperImpl repositoryConfigToDtoMapper; + private GitRepositoryConfigMapperImpl repositoryConfigMapper; @Mock private GitRepositoryHandler repositoryHandler; @Mock(answer = Answers.CALLS_REAL_METHODS) private ConfigurationStoreFactory configurationStoreFactory; - @Mock - private ConfigurationStore configurationStore; + @Spy + private ConfigurationStore configurationStore; + @Captor + private ArgumentCaptor configurationStoreCaptor; @Before public void prepareEnvironment() { GitConfig gitConfig = createConfiguration(); when(repositoryHandler.getConfig()).thenReturn(gitConfig); - GitRepositoryConfigResource gitRepositoryConfigResource = new GitRepositoryConfigResource(repositoryConfigToDtoMapper, repositoryManager, configurationStoreFactory); + GitRepositoryConfigResource gitRepositoryConfigResource = new GitRepositoryConfigResource(repositoryConfigMapper, repositoryManager, configurationStoreFactory); GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, of(gitRepositoryConfigResource)); dispatcher.getRegistry().addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); @@ -89,6 +95,7 @@ public class GitConfigResourceTest { @Before public void initConfigStore() { when(configurationStoreFactory.getStore(any())).thenReturn(configurationStore); + doNothing().when(configurationStore).set(configurationStoreCaptor.capture()); } @Test @@ -202,6 +209,26 @@ public class GitConfigResourceTest { .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(); From ddc2cbbfab959eb97dde19faacb34a0ac737b13c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 12 Dec 2018 15:59:53 +0100 Subject: [PATCH 03/19] Fix directory for file based stores --- .../scm/store/FileBasedStoreFactory.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java index 099ab53baa..d37a150723 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java @@ -58,8 +58,6 @@ public abstract class FileBasedStoreFactory { private RepositoryLocationResolver repositoryLocationResolver; private Store store; - private File storeDirectory; - protected FileBasedStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, Store store) { this.contextProvider = contextProvider; this.repositoryLocationResolver = repositoryLocationResolver; @@ -75,17 +73,16 @@ public abstract class FileBasedStoreFactory { } 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); + File storeDirectory; + 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); } - return new File(this.storeDirectory, name); + IOUtil.mkdirs(storeDirectory); + return new File(storeDirectory, name); } /** From dbbe467479f9b2068d8f99b400734def0f7d71e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 12 Dec 2018 17:30:23 +0100 Subject: [PATCH 04/19] Remove old property for default branch --- .../GitRepositoryConfigResource.java | 20 ++- .../GitRepositoryConfigStoreProvider.java | 22 +++ .../sonia/scm/repository/GitConstants.java | 49 ------ .../scm/repository/GitRepositoryConfig.java | 7 + .../GitRepositoryModifyListener.java | 97 ----------- .../repository/spi/AbstractGitCommand.java | 3 +- .../sonia/scm/repository/spi/GitContext.java | 19 +- .../spi/GitRepositoryServiceProvider.java | 5 +- .../spi/GitRepositoryServiceResolver.java | 7 +- .../v2/resources/GitConfigResourceTest.java | 2 +- .../repository/GitRepositoryHandlerTest.java | 2 +- .../GitRepositoryModifyListenerTest.java | 163 ------------------ .../spi/AbstractGitCommandTestBase.java | 6 +- .../repository/spi/GitBlameCommandTest.java | 6 +- .../repository/spi/GitBrowseCommandTest.java | 6 +- .../scm/repository/spi/GitCatCommandTest.java | 4 +- .../spi/GitIncomingCommandTest.java | 6 +- .../scm/repository/spi/GitLogCommandTest.java | 6 +- .../spi/GitModificationsCommandTest.java | 8 +- .../spi/GitOutgoingCommandTest.java | 6 +- .../repository/spi/GitPushCommandTest.java | 2 +- .../InMemoryConfigurationStoreFactory.java | 44 ++++- 22 files changed, 148 insertions(+), 342 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java delete mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConstants.java delete mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java delete mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryModifyListenerTest.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java index af3501ae9a..031c324908 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java @@ -1,5 +1,9 @@ package sonia.scm.api.v2.resources; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.event.ScmEventBus; +import sonia.scm.repository.ClearRepositoryCacheEvent; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; @@ -22,15 +26,17 @@ 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 ConfigurationStoreFactory configurationStoreFactory; + private final GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider; @Inject - public GitRepositoryConfigResource(GitRepositoryConfigMapper repositoryConfigMapper, RepositoryManager repositoryManager, ConfigurationStoreFactory configurationStoreFactory) { + public GitRepositoryConfigResource(GitRepositoryConfigMapper repositoryConfigMapper, RepositoryManager repositoryManager, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) { this.repositoryConfigMapper = repositoryConfigMapper; this.repositoryManager = repositoryManager; - this.configurationStoreFactory = configurationStoreFactory; + this.gitRepositoryConfigStoreProvider = gitRepositoryConfigStoreProvider; } @GET @@ -55,9 +61,15 @@ public class GitRepositoryConfigResource { ConfigurationStore 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()); + sendClearRepositoryCacheEvent(repository); return Response.noContent().build(); } + private void sendClearRepositoryCacheEvent(Repository repository) { + ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository)); + } + private Repository getRepository(@PathParam("namespace") String namespace, @PathParam("name") String name) { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); Repository repository = repositoryManager.get(namespaceAndName); @@ -68,6 +80,6 @@ public class GitRepositoryConfigResource { } private ConfigurationStore getStore(Repository repository) { - return configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(); + return gitRepositoryConfigStoreProvider.get(repository); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java new file mode 100644 index 0000000000..917892968f --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java @@ -0,0 +1,22 @@ +package sonia.scm.api.v2.resources; + +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 get(Repository repository) { + return configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConstants.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConstants.java deleted file mode 100644 index 6d833577e1..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConstants.java +++ /dev/null @@ -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() { - } - -} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java index a548d82153..a0136c8ea6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryConfig.java @@ -8,6 +8,13 @@ import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.FIELD) public class GitRepositoryConfig { + public GitRepositoryConfig() { + } + + public GitRepositoryConfig(String defaultBranch) { + this.defaultBranch = defaultBranch; + } + private String defaultBranch; public String getDefaultBranch() { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java deleted file mode 100644 index 1cbcdc35bf..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java +++ /dev/null @@ -1,97 +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 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.plugin.Extension; - -/** - * 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); - - /** - * 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); - } - } - - @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) - ); - } - -} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java index 2970bbd627..f94ccd59f6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java @@ -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 { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContext.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContext.java index b0dd8f1fd6..0b93f9cf2b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContext.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContext.java @@ -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; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index 936962eaba..ef3d96d5cb 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -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 -------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java index deca141556..0730ffc9cf 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java @@ -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; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index f4de22b2b5..0c28a28e59 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -86,7 +86,7 @@ public class GitConfigResourceTest { public void prepareEnvironment() { GitConfig gitConfig = createConfiguration(); when(repositoryHandler.getConfig()).thenReturn(gitConfig); - GitRepositoryConfigResource gitRepositoryConfigResource = new GitRepositoryConfigResource(repositoryConfigMapper, repositoryManager, configurationStoreFactory); + 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); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java index dbd67a7f8e..cb10e15271 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java @@ -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 diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryModifyListenerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryModifyListenerTest.java deleted file mode 100644 index a542674484..0000000000 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryModifyListenerTest.java +++ /dev/null @@ -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; - } - - } - - -} \ No newline at end of file diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java index 0b3c1d6e9d..d6820bad06 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java @@ -35,6 +35,9 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import org.junit.After; +import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; +import sonia.scm.repository.GitRepositoryConfig; +import sonia.scm.store.InMemoryConfigurationStoreFactory; /** * @@ -51,6 +54,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase public void close() { if (context != null) { + context.setConfig(new GitRepositoryConfig()); context.close(); } } @@ -65,7 +69,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase { if (context == null) { - context = new GitContext(repositoryDirectory, repository); + context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())); } return context; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java index 5757cd5d5e..817e4641dd 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java @@ -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()); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index 5e63adfb70..2ff3c73420 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -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); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java index 079fcac1da..0418bc3e61 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java @@ -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)); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java index acf0b0f820..376d7cdf7a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java @@ -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); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 4afaf09c67..e2ab85d9a7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -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()); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java index 41a516a124..dbb510fb7e 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java @@ -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); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java index 65592cf7e4..2525a6fa38 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java @@ -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); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java index 70212ba233..6aa831ec60 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java @@ -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); } } diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java index 2c5641bfd1..99ef942ebc 100644 --- a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java @@ -35,15 +35,55 @@ package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + /** * In memory configuration store factory for testing purposes. - * + * * @author Sebastian Sdorra */ public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory { + private static final Map STORES = new HashMap<>(); + @Override public ConfigurationStore getStore(TypedStoreParameters storeParameters) { - return new InMemoryConfigurationStore<>(); + Key key = new Key(storeParameters.getType(), storeParameters.getName(), storeParameters.getRepository() == null? "-": storeParameters.getRepository().getId()); + if (STORES.containsKey(key)) { + return STORES.get(key); + } else { + InMemoryConfigurationStore store = new InMemoryConfigurationStore<>(); + STORES.put(key, store); + return store; + } + } + + private static class Key { + private final Class type; + private final String name; + private final String id; + + public Key(Class type, String name, String id) { + this.type = type; + this.name = name; + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Key key = (Key) o; + return Objects.equals(type, key.type) && + Objects.equals(name, key.name) && + Objects.equals(id, key.id); + } + + @Override + public int hashCode() { + return Objects.hash(type, name, id); + } } } From db092e57e741307e3a2b1d5494ed771e9710c084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 12 Dec 2018 18:49:38 +0100 Subject: [PATCH 05/19] Move cache event to central position --- .../GitRepositoryConfigResource.java | 8 ----- .../GitRepositoryConfigStoreProvider.java | 30 ++++++++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java index 031c324908..9a85fdf1f3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java @@ -2,14 +2,11 @@ package sonia.scm.api.v2.resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.event.ScmEventBus; -import sonia.scm.repository.ClearRepositoryCacheEvent; 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.store.ConfigurationStoreFactory; import sonia.scm.web.GitVndMediaType; import javax.inject.Inject; @@ -62,14 +59,9 @@ public class GitRepositoryConfigResource { GitRepositoryConfig config = repositoryConfigMapper.map(dto); repositoryConfigStore.set(config); LOG.info("git default branch of repository {} has changed, sending clear cache event", repository.getNamespaceAndName()); - sendClearRepositoryCacheEvent(repository); return Response.noContent().build(); } - private void sendClearRepositoryCacheEvent(Repository repository) { - ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository)); - } - private Repository getRepository(@PathParam("namespace") String namespace, @PathParam("name") String name) { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); Repository repository = repositoryManager.get(namespaceAndName); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java index 917892968f..d7ab5707d5 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java @@ -1,5 +1,7 @@ package sonia.scm.api.v2.resources; +import sonia.scm.event.ScmEventBus; +import sonia.scm.repository.ClearRepositoryCacheEvent; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.Repository; import sonia.scm.store.ConfigurationStore; @@ -17,6 +19,32 @@ public class GitRepositoryConfigStoreProvider { } public ConfigurationStore get(Repository repository) { - return configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(); + return new StoreWrapper(configurationStoreFactory.withType(GitRepositoryConfig.class).withName("gitConfig").forRepository(repository).build(), repository); + } + + private static class StoreWrapper implements ConfigurationStore { + + private final ConfigurationStore delegate; + private final Repository repository; + + private StoreWrapper(ConfigurationStore delegate, Repository repository) { + this.delegate = delegate; + this.repository = repository; + } + + @Override + public GitRepositoryConfig get() { + return delegate.get(); + } + + @Override + public void set(GitRepositoryConfig object) { + delegate.set(object); + sendClearRepositoryCacheEvent(); + } + + private void sendClearRepositoryCacheEvent() { + ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository)); + } } } From c3e6e28262ee1725a719a99093638d862b1a10a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 13 Dec 2018 08:18:04 +0100 Subject: [PATCH 06/19] Fix type --- .../src/main/java/sonia/scm/store/ConfigurationStore.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationStore.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationStore.java index a7f21dd304..1e14b848b0 100644 --- a/scm-core/src/main/java/sonia/scm/store/ConfigurationStore.java +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationStore.java @@ -58,7 +58,7 @@ public interface ConfigurationStore * Stores the given configuration object to the store. * * - * @param obejct configuration object to store + * @param object configuration object to store */ - public void set(T obejct); + public void set(T object); } From 24cd9c3f8c6b3b96183dd7a25a777b5c281ec12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 13 Dec 2018 09:22:10 +0100 Subject: [PATCH 07/19] Introduce repository config change event to clear caches --- ...figChangeClearRepositoryCacheListener.java | 19 ++++++++++++ .../GitRepositoryConfigChangedEvent.java | 30 +++++++++++++++++++ .../GitRepositoryConfigResource.java | 3 -- .../GitRepositoryConfigStoreProvider.java | 18 +++++------ 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangeClearRepositoryCacheListener.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangedEvent.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangeClearRepositoryCacheListener.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangeClearRepositoryCacheListener.java new file mode 100644 index 0000000000..df93fa4886 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangeClearRepositoryCacheListener.java @@ -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())); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangedEvent.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangedEvent.java new file mode 100644 index 0000000000..eaf575a610 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigChangedEvent.java @@ -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; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java index 9a85fdf1f3..277e712b92 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java @@ -43,9 +43,6 @@ public class GitRepositoryConfigResource { Repository repository = getRepository(namespace, name); ConfigurationStore repositoryConfigStore = getStore(repository); GitRepositoryConfig config = repositoryConfigStore.get(); - if (config == null) { - config = new GitRepositoryConfig(); - } GitRepositoryConfigDto dto = repositoryConfigMapper.map(config, repository); return Response.ok(dto).build(); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java index d7ab5707d5..ce37fb65f4 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigStoreProvider.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import sonia.scm.event.ScmEventBus; -import sonia.scm.repository.ClearRepositoryCacheEvent; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.Repository; import sonia.scm.store.ConfigurationStore; @@ -34,17 +33,18 @@ public class GitRepositoryConfigStoreProvider { @Override public GitRepositoryConfig get() { - return delegate.get(); + GitRepositoryConfig config = delegate.get(); + if (config == null) { + return new GitRepositoryConfig(); + } + return config; } @Override - public void set(GitRepositoryConfig object) { - delegate.set(object); - sendClearRepositoryCacheEvent(); - } - - private void sendClearRepositoryCacheEvent() { - ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository)); + public void set(GitRepositoryConfig newConfig) { + GitRepositoryConfig oldConfig = get(); + delegate.set(newConfig); + ScmEventBus.getInstance().post(new GitRepositoryConfigChangedEvent(repository, oldConfig, newConfig)); } } } From b392e3f9d2608feee32ae4a80a80313e12f96848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 13 Dec 2018 09:38:41 +0100 Subject: [PATCH 08/19] Fix content type for simple exception mappers --- .../src/main/java/sonia/scm/api/rest/StatusExceptionMapper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/StatusExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/StatusExceptionMapper.java index 6f4978637e..1590154369 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/StatusExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/StatusExceptionMapper.java @@ -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 return Response.status(status) .entity(exception.getMessage()) + .type(MediaType.TEXT_PLAIN_TYPE) .build(); } } From 9e963b6aeb0786fa7f60377aefbe2ec5d0e0f3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 13 Dec 2018 10:00:03 +0100 Subject: [PATCH 09/19] Enrich repository collection, too --- .../GitRepositoryConfigEnricher.java | 24 ++- .../GitRepositoryConfigEnricherTest.java | 152 ++++++++++++++++++ .../sonia/scm/repository/repository-001.json | 42 +++++ .../repository/repository-collection-001.json | 106 ++++++++++++ 4 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-001.json create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-collection-001.json diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java index 55c565f162..58634464ec 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java @@ -3,6 +3,8 @@ 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.NamespaceAndName; +import sonia.scm.repository.RepositoryManager; import sonia.scm.web.JsonEnricherBase; import sonia.scm.web.JsonEnricherContext; @@ -11,31 +13,43 @@ import javax.inject.Provider; import static java.util.Collections.singletonMap; import static sonia.scm.web.VndMediaType.REPOSITORY; +import static sonia.scm.web.VndMediaType.REPOSITORY_COLLECTION; @Extension public class GitRepositoryConfigEnricher extends JsonEnricherBase { private final Provider scmPathInfoStore; + private final RepositoryManager manager; @Inject - public GitRepositoryConfigEnricher(Provider scmPathInfoStore, ObjectMapper objectMapper) { + public GitRepositoryConfigEnricher(Provider scmPathInfoStore, ObjectMapper objectMapper, RepositoryManager manager) { super(objectMapper); this.scmPathInfoStore = scmPathInfoStore; + this.manager = manager; } @Override public void enrich(JsonEnricherContext context) { if (resultHasMediaType(REPOSITORY, context)) { JsonNode repositoryNode = context.getResponseEntity(); - String namespace = repositoryNode.get("namespace").asText(); - String name = repositoryNode.get("name").asText(); + enrichRepositoryNode(repositoryNode); + } else if (resultHasMediaType(REPOSITORY_COLLECTION, context)) { + JsonNode repositoryCollectionNode = context.getResponseEntity().get("_embedded").withArray("repositories"); + repositoryCollectionNode.elements().forEachRemaining(this::enrichRepositoryNode); + } + } - String newPullRequest = new LinkBuilder(scmPathInfoStore.get().get(), GitConfigResource.class) + private void enrichRepositoryNode(JsonNode repositoryNode) { + String namespace = repositoryNode.get("namespace").asText(); + String name = repositoryNode.get("name").asText(); + + if ("git".equals(manager.get(new NamespaceAndName(namespace, name)).getType())) { + String repositoryConfigLink = new LinkBuilder(scmPathInfoStore.get().get(), GitConfigResource.class) .method("getRepositoryConfig") .parameters(namespace, name) .href(); - JsonNode newPullRequestNode = createObject(singletonMap("href", value(newPullRequest))); + JsonNode newPullRequestNode = createObject(singletonMap("href", value(repositoryConfigLink))); addPropertyNode(repositoryNode.get("_links"), "configuration", newPullRequestNode); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java new file mode 100644 index 0000000000..d2942d08a3 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java @@ -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 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(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-001.json b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-001.json new file mode 100644 index 0000000000..43ea136942 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-001.json @@ -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/" + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-collection-001.json b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-collection-001.json new file mode 100644 index 0000000000..f4eeb24bbc --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/repository-collection-001.json @@ -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/" + } + } + } + ] + } +} From 360cba6c500029ad9ce1c8439639c5db80e06f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 13 Dec 2018 11:32:44 +0100 Subject: [PATCH 10/19] Use constant --- .../scm/api/v2/resources/GitRepositoryConfigEnricher.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java index 58634464ec..5aecd4229d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java @@ -3,6 +3,7 @@ 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.JsonEnricherBase; @@ -43,7 +44,7 @@ public class GitRepositoryConfigEnricher extends JsonEnricherBase { String namespace = repositoryNode.get("namespace").asText(); String name = repositoryNode.get("name").asText(); - if ("git".equals(manager.get(new NamespaceAndName(namespace, name)).getType())) { + 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) From c2d872bd599b9e2fc7cf1ad50095e78109bae5cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 13 Dec 2018 18:32:20 +0100 Subject: [PATCH 11/19] Modify git HEAD on default branch change --- .../sonia/scm/repository/GitHeadModifier.java | 101 ++++++++++++++++++ .../GitRepositoryModifyListener.java | 75 +++++++++++++ .../scm/repository/GitHeadModifierTest.java | 100 +++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadModifier.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadModifier.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadModifier.java new file mode 100644 index 0000000000..b82e8bdd3c --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadModifier.java @@ -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); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java new file mode 100644 index 0000000000..a16b34f6be --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryModifyListener.java @@ -0,0 +1,75 @@ +/** + * 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.github.legman.Subscribe; +import sonia.scm.EagerSingleton; +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 { + + 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(GitRepositoryConfigChangedEvent event){ + Repository repository = event.getRepository(); + + String defaultBranch = storeProvider.get(repository).get().getDefaultBranch(); + if (defaultBranch != null) { + headModifier.ensure(repository, defaultBranch); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java new file mode 100644 index 0000000000..23b3110567 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java @@ -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; + } + +} From 8a6a235e77116153cf467074f0bcece63cfdb487 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 14 Dec 2018 16:01:57 +0100 Subject: [PATCH 12/19] Implement Git repo config --- .../src/main/js/RepositoryConfig.js | 120 ++++++++++++++++++ .../scm-git-plugin/src/main/js/index.js | 31 ++++- .../main/resources/locales/en/plugins.json | 15 ++- .../ui-components/src}/BranchSelector.js | 26 ++-- .../ui-components/src/forms}/DropDown.js | 0 .../packages/ui-components/src/forms/index.js | 3 +- .../packages/ui-components/src/index.js | 1 + scm-ui/src/repos/containers/ChangesetsRoot.js | 23 ++-- scm-ui/src/repos/containers/RepositoryRoot.js | 41 ++---- .../src/repos/sources/containers/Sources.js | 21 ++- 10 files changed, 203 insertions(+), 78 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js rename {scm-ui/src/repos/containers => scm-ui-components/packages/ui-components/src}/BranchSelector.js (73%) rename {scm-ui/src/repos/components => scm-ui-components/packages/ui-components/src/forms}/DropDown.js (100%) diff --git a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js new file mode 100644 index 0000000000..520af4a38c --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js @@ -0,0 +1,120 @@ +// @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 +}; + +const GIT_CONFIG_CONTENT_TYPE = "application/vnd.scmm-gitConfig+json"; + +class RepositoryConfig extends React.Component { + state = { + branches: [] + }; + + 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: null }); + } + this.setState({ ...this.state, selectedBranchName: branch.name }); + }; + + 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 })) + .catch(error => this.setState({ ...this.state, error })); + }; + + render() { + const { t, error } = this.props; + const { loadingBranches, loadingDefaultBranch, submitPending } = this.state; + + if (error) { + return ( + + ); + } + if (!(loadingBranches || loadingDefaultBranch)) { + + return ( +
+ + + + ); + } else { + return ; + } + } +} + +export default translate("plugins")(RepositoryConfig); diff --git a/scm-plugins/scm-git-plugin/src/main/js/index.js b/scm-plugins/scm-git-plugin/src/main/js/index.js index bdeda4cd0e..a066247dde 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/index.js +++ b/scm-plugins/scm-git-plugin/src/main/js/index.js @@ -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 +); diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json index c02cd9e101..a1ad9764d1 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json @@ -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,15 @@ "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" + } } } } diff --git a/scm-ui/src/repos/containers/BranchSelector.js b/scm-ui-components/packages/ui-components/src/BranchSelector.js similarity index 73% rename from scm-ui/src/repos/containers/BranchSelector.js rename to scm-ui-components/packages/ui-components/src/BranchSelector.js index ced1cb8f44..1f17891140 100644 --- a/scm-ui/src/repos/containers/BranchSelector.js +++ b/scm-ui-components/packages/ui-components/src/BranchSelector.js @@ -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 "packages/ui-types/src/index"; import injectSheet from "react-jss"; -import { compose } from "redux"; import classNames from "classnames"; +import DropDown from "./forms/DropDown"; const styles = { zeroflex: { @@ -20,11 +18,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 }; @@ -36,13 +34,12 @@ class BranchSelector extends React.Component { } 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 ( @@ -55,7 +52,7 @@ class BranchSelector extends React.Component { classes.minWidthOfLabel )} > - +
@@ -89,7 +86,4 @@ class BranchSelector extends React.Component { }; } -export default compose( - injectSheet(styles), - translate("repos") -)(BranchSelector); +export default injectSheet(styles)(BranchSelector); diff --git a/scm-ui/src/repos/components/DropDown.js b/scm-ui-components/packages/ui-components/src/forms/DropDown.js similarity index 100% rename from scm-ui/src/repos/components/DropDown.js rename to scm-ui-components/packages/ui-components/src/forms/DropDown.js diff --git a/scm-ui-components/packages/ui-components/src/forms/index.js b/scm-ui-components/packages/ui-components/src/forms/index.js index 714b9b3301..3bc3820f16 100644 --- a/scm-ui-components/packages/ui-components/src/forms/index.js +++ b/scm-ui-components/packages/ui-components/src/forms/index.js @@ -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"; diff --git a/scm-ui-components/packages/ui-components/src/index.js b/scm-ui-components/packages/ui-components/src/index.js index 91c41a9d25..5b3cdb4c95 100644 --- a/scm-ui-components/packages/ui-components/src/index.js +++ b/scm-ui-components/packages/ui-components/src/index.js @@ -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"; diff --git a/scm-ui/src/repos/containers/ChangesetsRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js index 1f3f0c1e3b..b914cd8298 100644 --- a/scm-ui/src/repos/containers/ChangesetsRoot.js +++ b/scm-ui/src/repos/containers/ChangesetsRoot.js @@ -1,19 +1,14 @@ // @flow import React from "react"; -import type { Branch, Repository } from "@scm-manager/ui-types"; -import { Route, withRouter } from "react-router-dom"; +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 { - fetchBranches, - getBranches, - getFetchBranchesFailure, - isFetchBranchesPending -} from "../modules/branches"; -import { compose } from "redux"; +import {connect} from "react-redux"; +import {BranchSelector, ErrorNotification, Loading} from "@scm-manager/ui-components"; +import {fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending} from "../modules/branches"; +import {compose} from "redux"; type Props = { repository: Repository, @@ -92,10 +87,11 @@ class BranchRoot extends React.Component { } renderBranchSelector = () => { - const { repository, branches, selected } = this.props; + const { repository, branches, selected, t } = this.props; if (repository._links.branches) { return ( { @@ -133,6 +129,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { export default compose( withRouter, + translate("repos"), connect( mapStateToProps, mapDispatchToProps diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 81de0296fc..d4095ac065 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -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, @@ -198,16 +185,16 @@ class RepositoryRoot extends React.Component { label={t("repository-root.sources")} activeOnlyWhenExact={false} /> - +
diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 890ab595d0..b04bcb990d 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,20 +1,15 @@ // @flow import React from "react"; -import { connect } from "react-redux"; -import { withRouter } from "react-router-dom"; -import type { Branch, Repository } from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +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 { - fetchBranches, - getBranches, - getFetchBranchesFailure, - isFetchBranchesPending -} from "../../modules/branches"; -import { compose } from "redux"; +import {ErrorNotification, Loading} from "@scm-manager/ui-components"; +import BranchSelector from "../../../../../scm-ui-components/packages/ui-components/src/BranchSelector"; +import {fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending} from "../../modules/branches"; +import {compose} from "redux"; import Content from "./Content"; -import { fetchSources, isDirectory } from "../modules/sources"; +import {fetchSources, isDirectory} from "../modules/sources"; type Props = { repository: Repository, From 5cb1a2e0cd8ac95b92cb2716446ccfc8fa5e40b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 14 Dec 2018 17:25:42 +0100 Subject: [PATCH 13/19] Use abstract base class for json enrichment --- .../GitRepositoryConfigEnricher.java | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java index 5aecd4229d..2566fd82f7 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricher.java @@ -6,18 +6,13 @@ import sonia.scm.plugin.Extension; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.RepositoryManager; -import sonia.scm.web.JsonEnricherBase; -import sonia.scm.web.JsonEnricherContext; +import sonia.scm.web.AbstractRepositoryJsonEnricher; import javax.inject.Inject; import javax.inject.Provider; -import static java.util.Collections.singletonMap; -import static sonia.scm.web.VndMediaType.REPOSITORY; -import static sonia.scm.web.VndMediaType.REPOSITORY_COLLECTION; - @Extension -public class GitRepositoryConfigEnricher extends JsonEnricherBase { +public class GitRepositoryConfigEnricher extends AbstractRepositoryJsonEnricher { private final Provider scmPathInfoStore; private final RepositoryManager manager; @@ -30,29 +25,13 @@ public class GitRepositoryConfigEnricher extends JsonEnricherBase { } @Override - public void enrich(JsonEnricherContext context) { - if (resultHasMediaType(REPOSITORY, context)) { - JsonNode repositoryNode = context.getResponseEntity(); - enrichRepositoryNode(repositoryNode); - } else if (resultHasMediaType(REPOSITORY_COLLECTION, context)) { - JsonNode repositoryCollectionNode = context.getResponseEntity().get("_embedded").withArray("repositories"); - repositoryCollectionNode.elements().forEachRemaining(this::enrichRepositoryNode); - } - } - - private void enrichRepositoryNode(JsonNode repositoryNode) { - String namespace = repositoryNode.get("namespace").asText(); - String name = repositoryNode.get("name").asText(); - + 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(); - - JsonNode newPullRequestNode = createObject(singletonMap("href", value(repositoryConfigLink))); - - addPropertyNode(repositoryNode.get("_links"), "configuration", newPullRequestNode); + addLink(repositoryNode, "configuration", repositoryConfigLink); } } } From 161885ff1230f216395c6d574af1951a0208e7ba Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 14 Dec 2018 20:20:00 +0100 Subject: [PATCH 14/19] Fixed issue with branch selector --- .../src/main/js/RepositoryConfig.js | 63 +++++++++++++------ .../ui-components/src/BranchSelector.js | 6 ++ 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js index 520af4a38c..a9f2a27e37 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js +++ b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js @@ -17,7 +17,8 @@ type State = { submitPending: boolean, error: Error, branches: Branch[], - selectedBranchName: string + selectedBranchName: string, + defaultBranchChanged: boolean }; const GIT_CONFIG_CONTENT_TYPE = "application/vnd.scmm-gitConfig+json"; @@ -55,8 +56,10 @@ class RepositoryConfig extends React.Component { } branchSelected = (branch: Branch) => { + console.log(branch) if (!branch) { this.setState({ ...this.state, selectedBranchName: null }); + return; } this.setState({ ...this.state, selectedBranchName: branch.name }); }; @@ -75,7 +78,13 @@ class RepositoryConfig extends React.Component { newConfig, GIT_CONFIG_CONTENT_TYPE ) - .then(() => this.setState({ ...this.state, submitPending: false })) + .then(() => + this.setState({ + ...this.state, + submitPending: false, + defaultBranchChanged: true + }) + ) .catch(error => this.setState({ ...this.state, error })); }; @@ -92,29 +101,47 @@ class RepositoryConfig extends React.Component { /> ); } - if (!(loadingBranches || loadingDefaultBranch)) { + if (!(loadingBranches || loadingDefaultBranch)) { return ( -
- - - + <> + {this.renderBranchChangedNotification()} +
+ + + + ); } else { return ; } } + + renderBranchChangedNotification = () => { + if (this.state.defaultBranchChanged) { + return ( +
+
+ ); + } + return null; + }; } export default translate("plugins")(RepositoryConfig); diff --git a/scm-ui-components/packages/ui-components/src/BranchSelector.js b/scm-ui-components/packages/ui-components/src/BranchSelector.js index 1f17891140..d594af9b21 100644 --- a/scm-ui-components/packages/ui-components/src/BranchSelector.js +++ b/scm-ui-components/packages/ui-components/src/BranchSelector.js @@ -79,6 +79,12 @@ class BranchSelector extends React.Component { 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); From 88e45d5eefc9ab27187632cde805a5877281bfe1 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 17 Dec 2018 11:29:20 +0100 Subject: [PATCH 15/19] Minor fixes --- .../ui-components/src/BranchSelector.js | 4 +-- scm-ui/src/repos/containers/ChangesetsRoot.js | 26 ++++++++++++------ .../src/repos/sources/containers/Sources.js | 27 ++++++++++++------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/BranchSelector.js b/scm-ui-components/packages/ui-components/src/BranchSelector.js index d594af9b21..aa90dec591 100644 --- a/scm-ui-components/packages/ui-components/src/BranchSelector.js +++ b/scm-ui-components/packages/ui-components/src/BranchSelector.js @@ -1,7 +1,7 @@ // @flow import React from "react"; -import type {Branch} from "packages/ui-types/src/index"; +import type {Branch} from "@scm-manager/ui-types"; import injectSheet from "react-jss"; import classNames from "classnames"; import DropDown from "./forms/DropDown"; @@ -35,7 +35,7 @@ class BranchSelector extends React.Component { componentDidMount() { const selectedBranch = this.props.branches.find(branch => branch.name === this.props.selectedBranch); - this.setState({ selectedBranch }) + this.setState({ selectedBranch }); } render() { diff --git a/scm-ui/src/repos/containers/ChangesetsRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js index b914cd8298..2eea64b8e1 100644 --- a/scm-ui/src/repos/containers/ChangesetsRoot.js +++ b/scm-ui/src/repos/containers/ChangesetsRoot.js @@ -1,14 +1,23 @@ // @flow 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 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 {connect} from "react-redux"; -import {BranchSelector, ErrorNotification, Loading} from "@scm-manager/ui-components"; -import {fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending} from "../modules/branches"; -import {compose} from "redux"; +import { connect } from "react-redux"; +import { + BranchSelector, + ErrorNotification, + Loading +} from "@scm-manager/ui-components"; +import { + fetchBranches, + getBranches, + getFetchBranchesFailure, + isFetchBranchesPending +} from "../modules/branches"; +import { compose } from "redux"; type Props = { repository: Repository, @@ -27,7 +36,8 @@ type Props = { // Context props history: any, // TODO flow type - match: any + match: any, + t: string => string }; class BranchRoot extends React.Component { diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index b04bcb990d..0bcdb61f66 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,15 +1,21 @@ // @flow import React from "react"; -import {connect} from "react-redux"; -import {withRouter} from "react-router-dom"; -import type {Branch, Repository} from "@scm-manager/ui-types"; +import { connect } from "react-redux"; +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 { ErrorNotification, Loading } from "@scm-manager/ui-components"; import BranchSelector from "../../../../../scm-ui-components/packages/ui-components/src/BranchSelector"; -import {fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending} from "../../modules/branches"; -import {compose} from "redux"; +import { translate } from "react-i18next"; +import { + fetchBranches, + getBranches, + getFetchBranchesFailure, + isFetchBranchesPending +} from "../../modules/branches"; +import { compose } from "redux"; import Content from "./Content"; -import {fetchSources, isDirectory} from "../modules/sources"; +import { fetchSources, isDirectory } from "../modules/sources"; type Props = { repository: Repository, @@ -27,7 +33,8 @@ type Props = { // Context props history: any, - match: any + match: any, + t: string => string }; class Sources extends React.Component { @@ -104,13 +111,14 @@ class Sources extends React.Component { } renderBranchSelector = () => { - const { branches, revision } = this.props; + const { branches, revision, t } = this.props; if (branches) { return ( { this.branchSelected(b); }} @@ -155,6 +163,7 @@ const mapDispatchToProps = dispatch => { }; export default compose( + translate("repos"), withRouter, connect( mapStateToProps, From 07c34a005e448885187f255b9b7f65d59caf6180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 18 Dec 2018 16:55:13 +0100 Subject: [PATCH 16/19] Peer review --- .../scm-git-plugin/src/main/js/RepositoryConfig.js | 13 ++++--------- .../src/main/resources/locales/en/plugins.json | 3 ++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js index a9f2a27e37..4fa6c09930 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js +++ b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js @@ -8,9 +8,9 @@ import {translate} from "react-i18next"; type Props = { repository: Repository, - t: string => string }; + type State = { loadingBranches: boolean, loadingDefaultBranch: boolean, @@ -24,10 +24,6 @@ type State = { const GIT_CONFIG_CONTENT_TYPE = "application/vnd.scmm-gitConfig+json"; class RepositoryConfig extends React.Component { - state = { - branches: [] - }; - componentDidMount() { const { repository } = this.props; this.setState({ ...this.state, loadingBranches: true }); @@ -56,7 +52,6 @@ class RepositoryConfig extends React.Component { } branchSelected = (branch: Branch) => { - console.log(branch) if (!branch) { this.setState({ ...this.state, selectedBranchName: null }); return; @@ -89,8 +84,8 @@ class RepositoryConfig extends React.Component { }; render() { - const { t, error } = this.props; - const { loadingBranches, loadingDefaultBranch, submitPending } = this.state; + const { t } = this.props; + const { loadingBranches, loadingDefaultBranch, submitPending, error } = this.state; if (error) { return ( @@ -136,7 +131,7 @@ class RepositoryConfig extends React.Component { this.setState({ ...this.state, defaultBranchChanged: false }) } /> - Default branch changed! + {this.props.t("scm-git-plugin.repo-config.success")}
); } diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json index a1ad9764d1..a84f44726f 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json @@ -30,7 +30,8 @@ "error": { "title": "Error", "subtitle": "Something went wrong" - } + }, + "success": "Default branch changed!" } } } From 2df50b833edb3d65ba7252c52b056ebd87107f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Dec 2018 09:30:36 +0100 Subject: [PATCH 17/19] Add constructor --- .../src/main/js/RepositoryConfig.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js index 4fa6c09930..ecb3281ec2 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js +++ b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js @@ -15,15 +15,28 @@ type State = { loadingBranches: boolean, loadingDefaultBranch: boolean, submitPending: boolean, - error: Error, + error?: Error, branches: Branch[], - selectedBranchName: string, + selectedBranchName?: string, defaultBranchChanged: boolean }; const GIT_CONFIG_CONTENT_TYPE = "application/vnd.scmm-gitConfig+json"; class RepositoryConfig extends React.Component { + + 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 }); @@ -53,10 +66,10 @@ class RepositoryConfig extends React.Component { branchSelected = (branch: Branch) => { if (!branch) { - this.setState({ ...this.state, selectedBranchName: null }); + this.setState({ ...this.state, selectedBranchName: null , defaultBranchChanged: false}); return; } - this.setState({ ...this.state, selectedBranchName: branch.name }); + this.setState({ ...this.state, selectedBranchName: branch.name, defaultBranchChanged: false }); }; submit = (event: Event) => { From d9bfddce2df4fa18c8f6242622b07cd90df46be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 20 Dec 2018 10:47:24 +0100 Subject: [PATCH 18/19] Add documentation about status codes --- .../resources/GitRepositoryConfigResource.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java index 277e712b92..7b226186e5 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java @@ -1,5 +1,7 @@ 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; @@ -39,6 +41,13 @@ public class GitRepositoryConfigResource { @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 repositoryConfigStore = getStore(repository); @@ -50,6 +59,13 @@ public class GitRepositoryConfigResource { @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 repositoryConfigStore = getStore(repository); From fdbb37709b1fff7b70964bdae2fb96302e514cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 20 Dec 2018 10:31:10 +0000 Subject: [PATCH 19/19] Close branch feature/git_default_branch