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/30] 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/30] 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 cab797fd83244025494ba5dfeab047c95a978297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Wed, 12 Dec 2018 15:40:24 +0100 Subject: [PATCH 03/30] show nothing if no diff is there --- .../packages/ui-components/src/repos/LoadingDiff.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js index 5f6330f0e5..3abf971168 100644 --- a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js +++ b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js @@ -52,9 +52,12 @@ class LoadingDiff extends React.Component { const { diff, loading, error } = this.state; if (error) { return ; - } else if (loading || !diff) { + } else if (loading) { return ; - } else { + } else if(!diff){ + return null; + } + else { return ; } } 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 04/30] 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 05/30] 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 06/30] 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 07/30] 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 08/30] 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 09/30] 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 10/30] 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 11/30] 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 12/30] 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 13/30] 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 14/30] 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 15/30] 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 16/30] 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 17/30] 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 18/30] 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 3681936e5ced6aa3f4a88d0112deb24b0c2ea7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Wed, 19 Dec 2018 11:30:05 +0100 Subject: [PATCH 19/30] reload diff component if url changes --- .../packages/ui-components/src/repos/LoadingDiff.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js index 3abf971168..2ebc4a170b 100644 --- a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js +++ b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js @@ -30,6 +30,16 @@ class LoadingDiff extends React.Component { } componentDidMount() { + this.fetchDiff(); + } + + componentDidUpdate(prevProps: Props) { + if(prevProps.url !== this.props.url){ + this.fetchDiff(); + } + } + + fetchDiff = () => { const { url } = this.props; apiClient .get(url) @@ -46,7 +56,7 @@ class LoadingDiff extends React.Component { error }); }); - } + }; render() { const { diff, loading, error } = this.state; From a9300df7f4ed63c6615444db5d5b079b7c37d574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Dec 2018 13:33:57 +0100 Subject: [PATCH 20/30] Fix incoming diff with merges from target branch. Taking the git history example below, the previous version only computed commits 'b3' upward for log of 'b' with ancestor 'master' and missed commits 'b1', 'b2' and the first merge. * 86e9ca0 (HEAD -> b) b5 * d69edb3 Merge branch 'master' into b |\ | * 946a8db (master) f | * b19b9cc e * | 3d6109c b4 * | 6330653 b3 * | a49a28e Merge branch 'master' into b |\ \ | |/ | * 0235584 d | * 20251c5 c * | 5023b85 b2 * | 201ecc1 b1 |/ * 36b19e4 b * c2190a9 a --- .../scm/repository/spi/GitLogCommand.java | 9 +- .../spi/GitLogCommandAncestorTest.java | 102 ++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index 2ea25126cf..a6f74d24eb 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -205,7 +205,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand ObjectId ancestorId = null; if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) { - ancestorId = computeCommonAncestor(request, repository, startId, branch); + ancestorId = repository.resolve(request.getAncestorChangeset()); } revWalk = new RevWalk(repository); @@ -225,16 +225,15 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand revWalk.markStart(revWalk.lookupCommit(branch.getObjectId())); } + if (ancestorId != null) { + revWalk.markUninteresting(revWalk.lookupCommit(ancestorId)); + } Iterator iterator = revWalk.iterator(); while (iterator.hasNext()) { RevCommit commit = iterator.next(); - if (commit.getId().equals(ancestorId)) { - break; - } - if ((counter >= start) && ((limit < 0) || (counter < start + limit))) { changesetList.add(converter.createChangeset(commit)); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java new file mode 100644 index 0000000000..d36922f941 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java @@ -0,0 +1,102 @@ + +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository.spi; + +import org.junit.Test; +import sonia.scm.repository.ChangesetPagingResult; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Unit tests for {@link GitLogCommand} with an ancestor commit. This test uses the following git repository: + * + *
+ * * 86e9ca0 (HEAD -> b) b5
+ * *   d69edb3 Merge branch 'master' into b
+ * |\
+ * | * 946a8db (master) f
+ * | * b19b9cc e
+ * * | 3d6109c b4
+ * * | 6330653 b3
+ * * |   a49a28e Merge branch 'master' into b
+ * |\ \
+ * | |/
+ * | * 0235584 d
+ * | * 20251c5 c
+ * * | 5023b85 b2
+ * * | 201ecc1 b1
+ * |/
+ * * 36b19e4 b
+ * * c2190a9 a
+ * 
+ * @author Sebastian Sdorra + */ +public class GitLogCommandAncestorTest extends AbstractGitCommandTestBase +{ + @Override + protected String getZippedRepositoryResource() + { + return "sonia/scm/repository/spi/scm-git-ancestor-test.zip"; + } + + @Test + public void testGetAncestor() + { + LogCommandRequest request = new LogCommandRequest(); + + request.setBranch("b"); + request.setAncestorChangeset("master"); + + ChangesetPagingResult result = createCommand().getChangesets(request); + + assertNotNull(result); + assertEquals(7, result.getTotal()); + assertEquals(7, result.getChangesets().size()); + + assertEquals("86e9ca012202b36865373a63c12ef4f4353506cd", result.getChangesets().get(0).getId()); + assertEquals("d69edb314d07ab20ad626e3101597702d3510b5d", result.getChangesets().get(1).getId()); + assertEquals("3d6109c4c830e91eaf12ac6a331a5fccd670fe3c", result.getChangesets().get(2).getId()); + assertEquals("63306538d06924d6b254f86541c638021c001141", result.getChangesets().get(3).getId()); + assertEquals("a49a28e0beb0ab55f985598d05b8628c2231c9b6", result.getChangesets().get(4).getId()); + assertEquals("5023b850c2077db857593a3c0269329c254a370d", result.getChangesets().get(5).getId()); + assertEquals("201ecc1131e6b99fb0a0fe9dcbc8c044383e1a07", result.getChangesets().get(6).getId()); + } + + private GitLogCommand createCommand() + { + return new GitLogCommand(createContext(), repository); + } +} From 547947a1b3024792d176e0c3e94fe3f05987d80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Dec 2018 14:38:29 +0100 Subject: [PATCH 21/30] Add zip for test --- .../repository/spi/scm-git-ancestor-test.zip | Bin 0 -> 20917 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-ancestor-test.zip diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-ancestor-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-ancestor-test.zip new file mode 100644 index 0000000000000000000000000000000000000000..d740de767439c78f17c58ba9c0df2b34339b8b4c GIT binary patch literal 20917 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 literal 0 HcmV?d00001 From 3774b5abb3140d307ec96eb4c62aa6694e07118f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 20 Dec 2018 10:05:17 +0100 Subject: [PATCH 22/30] Do not send basic auth for /repo requests from web browser This fixes the basic auth popups in browsers after session timeout and reload in browsers "inside" of repositories. --- .../scm/web/filter/AuthenticationFilter.java | 3 +- .../main/java/sonia/scm/ScmServletModule.java | 4 -- ...tpProtocolServletAuthenticationFilter.java | 50 +++++++++++++++++++ .../web/security/ApiAuthenticationFilter.java | 5 ++ 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java index ffe6ecc787..81afa04a67 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java @@ -70,7 +70,6 @@ import javax.servlet.http.HttpServletResponse; * @author Sebastian Sdorra * @since 2.0.0 */ -@Singleton public class AuthenticationFilter extends HttpFilter { @@ -128,7 +127,7 @@ public class AuthenticationFilter extends HttpFilter } else if (subject.isAuthenticated()) { - logger.trace("user is allready authenticated"); + logger.trace("user is already authenticated"); processChain(request, response, chain, subject); } else if (isAnonymousAccessEnabled()) diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index d7846dbac5..7c7dec47ff 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -112,9 +112,7 @@ import sonia.scm.util.ScmConfigurationUtil; import sonia.scm.web.UserAgentParser; import sonia.scm.web.cgi.CGIExecutorFactory; import sonia.scm.web.cgi.DefaultCGIExecutorFactory; -import sonia.scm.web.filter.AuthenticationFilter; import sonia.scm.web.filter.LoggingFilter; -import sonia.scm.web.protocol.HttpProtocolServlet; import sonia.scm.web.security.AdministrationContext; import sonia.scm.web.security.DefaultAdministrationContext; @@ -315,8 +313,6 @@ public class ScmServletModule extends ServletModule bind(TemplateEngineFactory.class); bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class); - filter(HttpProtocolServlet.PATTERN).through(AuthenticationFilter.class); - // bind events // bind(LastModifiedUpdateListener.class); diff --git a/scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java new file mode 100644 index 0000000000..77683bd6be --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java @@ -0,0 +1,50 @@ +package sonia.scm.web.filter; + +import sonia.scm.Priority; +import sonia.scm.PushStateDispatcher; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.filter.Filters; +import sonia.scm.filter.WebElement; +import sonia.scm.util.HttpUtil; +import sonia.scm.web.UserAgent; +import sonia.scm.web.UserAgentParser; +import sonia.scm.web.WebTokenGenerator; +import sonia.scm.web.protocol.HttpProtocolServlet; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +import static sonia.scm.util.HttpUtil.AUTHENTICATION_REALM; +import static sonia.scm.util.HttpUtil.HEADER_WWW_AUTHENTICATE; + +@Priority(Filters.PRIORITY_AUTHENTICATION) +@WebElement(value = HttpProtocolServlet.PATTERN) +public class HttpProtocolServletAuthenticationFilter extends AuthenticationFilter { + + private final PushStateDispatcher dispatcher; + private final UserAgentParser userAgentParser; + + @Inject + public HttpProtocolServletAuthenticationFilter( + ScmConfiguration configuration, + Set tokenGenerators, + PushStateDispatcher dispatcher, + UserAgentParser userAgentParser) { + super(configuration, tokenGenerators); + this.dispatcher = dispatcher; + this.userAgentParser = userAgentParser; + } + + @Override + protected void sendUnauthorizedError(HttpServletRequest request, HttpServletResponse response) throws IOException { + UserAgent userAgent = userAgentParser.parse(request); + if (userAgent.isBrowser()) { + dispatcher.dispatch(request, response, request.getRequestURI()); + } else { + HttpUtil.sendUnauthorized(request, response); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java index c2444b43f5..b0d492be60 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java @@ -127,4 +127,9 @@ public class ApiAuthenticationFilter extends AuthenticationFilter { chain.doFilter(request, response); } + + @Override + protected void sendUnauthorizedError(HttpServletRequest request, HttpServletResponse response) throws IOException { + + } } From ca54b31c9da01c93a67800505d121e9b94f9ac61 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 20 Dec 2018 09:29:36 +0000 Subject: [PATCH 23/30] Close branch feature/diff_always_loading_when_no_diff From 667accdf939b131c8f8c93be0bc3692b98e4db26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 20 Dec 2018 10:30:20 +0100 Subject: [PATCH 24/30] Undo unnecessary change --- .../java/sonia/scm/web/security/ApiAuthenticationFilter.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java index b0d492be60..c2444b43f5 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java @@ -127,9 +127,4 @@ public class ApiAuthenticationFilter extends AuthenticationFilter { chain.doFilter(request, response); } - - @Override - protected void sendUnauthorizedError(HttpServletRequest request, HttpServletResponse response) throws IOException { - - } } From f87c09334eb9308bcb304807222cc0ef3e7edb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 20 Dec 2018 10:31:29 +0100 Subject: [PATCH 25/30] Undo unnecessary change --- .../src/main/java/sonia/scm/web/filter/AuthenticationFilter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java index 81afa04a67..c6a8463998 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java @@ -70,6 +70,7 @@ import javax.servlet.http.HttpServletResponse; * @author Sebastian Sdorra * @since 2.0.0 */ +@Singleton public class AuthenticationFilter extends HttpFilter { From 0eb5bf8117df2ac8be2df78277ed5d1555e2fa0e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 20 Dec 2018 09:46:43 +0000 Subject: [PATCH 26/30] Close branch bugfix/incoming_diff_with_merge 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 27/30] 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 1a0f3c860f5354a77e0e822465a0a1f1c68eb4f6 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 20 Dec 2018 09:49:42 +0000 Subject: [PATCH 28/30] Close branch feature/no_basic_auth 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 29/30] Close branch feature/git_default_branch From a9f54d3c69f65527424e609bece3014968bd374c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 20 Dec 2018 13:04:28 +0100 Subject: [PATCH 30/30] remove max-height css property, because it breaks pr detail view --- scm-ui/styles/scm.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 84aeec444a..9d65bbe26f 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -115,7 +115,6 @@ $fa-font-path: "webfonts"; .media { .media-content { width: calc(50% - 0.75rem); - max-height: 120px; .shorten-text { overflow: hidden; text-overflow: ellipsis;