From 39e521a8d4ea028001fac5eb86acce6c8204bd65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 27 Oct 2020 22:05:13 +0100 Subject: [PATCH] Set default branch after initial commit --- .../sonia/scm/repository/spi/GitContext.java | 35 ++++-- .../scm/repository/spi/GitContextFactory.java | 2 +- .../scm/repository/spi/GitModifyCommand.java | 16 +++ .../spi/AbstractGitCommandTestBase.java | 13 +- .../spi/GitIncomingCommandTest.java | 6 +- .../spi/GitModificationsCommandTest.java | 9 +- .../GitModifyCommand_InitialCommitTest.java | 111 ++++++++++++++++++ .../spi/GitOutgoingCommandTest.java | 6 +- .../repository/spi/GitPushCommandTest.java | 3 +- .../spi/scm-git-spi-empty-repo-test.zip | Bin 0 -> 13430 bytes 10 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_InitialCommitTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-empty-repo-test.zip 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 c7f0327312..f623928087 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 @@ -27,6 +27,7 @@ package sonia.scm.repository.spi; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; +import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitUtil; import sonia.scm.repository.Repository; @@ -51,19 +52,7 @@ public class GitContext implements Closeable, RepositoryProvider //~--- constructors --------------------------------------------------------- - /** - * Constructs ... - * - * - * @param directory - * @param repository - */ - public GitContext(File directory, Repository repository, GitRepositoryConfigStoreProvider storeProvider) - { - this.directory = directory; - this.repository = repository; - this.storeProvider = storeProvider; - } + private final GitConfig config; //~--- methods -------------------------------------------------------------- @@ -122,6 +111,22 @@ public class GitContext implements Closeable, RepositoryProvider } } + /** + * Constructs ... + * + * + * @param directory + * @param repository + * @param config + */ + public GitContext(File directory, Repository repository, GitRepositoryConfigStoreProvider storeProvider, GitConfig config) + { + this.directory = directory; + this.repository = repository; + this.storeProvider = storeProvider; + this.config = config; + } + void setConfig(GitRepositoryConfig newConfig) { storeProvider.get(repository).set(newConfig); } @@ -133,6 +138,10 @@ public class GitContext implements Closeable, RepositoryProvider private final Repository repository; private final GitRepositoryConfigStoreProvider storeProvider; + GitConfig getGlobalConfig() { + return config; + } + /** Field description */ private org.eclipse.jgit.lib.Repository gitRepository; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java index 04bb37bed0..214eb090b8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java @@ -42,7 +42,7 @@ class GitContextFactory { } GitContext create(Repository repository) { - return new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider); + return new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider, handler.getConfig()); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java index 95e3af0aa9..3ff65871c1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java @@ -86,6 +86,9 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman @Override String run() throws IOException { getClone().getRepository().getFullBranch(); + + boolean initialCommit = getClone().getRepository().getRefDatabase().getRefs().isEmpty(); + if (!StringUtils.isEmpty(request.getExpectedRevision()) && !request.getExpectedRevision().equals(getCurrentRevision().getName())) { throw new ConcurrentModificationException(ContextEntry.ContextBuilder.entity("Branch", request.getBranch() == null ? "default" : request.getBranch()).in(repository).build()); @@ -95,10 +98,23 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman } failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())); Optional revCommit = doCommit(request.getCommitMessage(), request.getAuthor(), request.isSign()); + + if (initialCommit && StringUtils.isNotBlank(context.getGlobalConfig().getDefaultBranch())) { + createDefaultBranch(); + } + push(); return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name(); } + private void createDefaultBranch() { + try { + getClone().checkout().setName(context.getGlobalConfig().getDefaultBranch()).setCreateBranch(true).call(); + } catch (GitAPIException e) { + throw new InternalRepositoryException(repository, "could not create default branch for initial commit", e); + } + } + @Override public void addFileToScm(String name, Path file) { addToGitWithLfsSupport(name, 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 d593a58678..2c2ee26f80 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 @@ -21,24 +21,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- -import org.eclipse.jgit.transport.ScmTransportProtocol; -import org.eclipse.jgit.transport.Transport; import org.junit.After; -import org.junit.Before; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; +import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryConfig; -import sonia.scm.repository.PreProcessorUtil; -import sonia.scm.repository.api.HookContextFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory; -import static com.google.inject.util.Providers.of; -import static org.mockito.Mockito.mock; - /** * * @author Sebastian Sdorra @@ -69,7 +62,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase { if (context == null) { - context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create())); + context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()), new GitConfig()); } return context; 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 5cfab8de24..270c548120 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 @@ -32,7 +32,7 @@ import org.junit.Ignore; import org.junit.Test; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.GitChangesetConverterFactory; +import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitTestHelper; import sonia.scm.store.InMemoryConfigurationStoreFactory; @@ -99,7 +99,7 @@ public class GitIncomingCommandTest commit(outgoing, "added a"); - GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()))); + GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig())); PullCommandRequest req = new PullCommandRequest(); req.setRemoteRepository(outgoingRepository); pull.pull(req); @@ -177,7 +177,7 @@ public class GitIncomingCommandTest private GitIncomingCommand createCommand() { return new GitIncomingCommand( - new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), + new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()), handler, GitTestHelper.createConverterFactory() ); 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 1657191d21..28ddd0a234 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 @@ -27,6 +27,7 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Before; import org.junit.Test; +import sonia.scm.repository.GitConfig; import sonia.scm.repository.Modifications; import java.io.File; @@ -42,8 +43,8 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase { @Before public void init() { - incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, incomingRepository, null)); - outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, outgoingRepository, null)); + incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, incomingRepository, null, new GitConfig())); + outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig())); } @Test @@ -106,11 +107,11 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase { } void pushOutgoingAndPullIncoming() throws IOException { - GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null)); + GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig())); PushCommandRequest request = new PushCommandRequest(); request.setRemoteRepository(incomingRepository); cmd.push(request); - GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null)); + GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null, new GitConfig())); PullCommandRequest pullRequest = new PullCommandRequest(); pullRequest.setRemoteRepository(incomingRepository); pullCommand.pull(pullRequest); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_InitialCommitTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_InitialCommitTest.java new file mode 100644 index 0000000000..8b8d4fae6c --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_InitialCommitTest.java @@ -0,0 +1,111 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.GpgSigner; +import org.eclipse.jgit.lib.Ref; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import sonia.scm.repository.GitTestHelper; +import sonia.scm.repository.Person; +import sonia.scm.repository.work.NoneCachingWorkingCopyPool; +import sonia.scm.repository.work.WorkdirProvider; +import sonia.scm.web.lfs.LfsBlobStoreFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret") +public class GitModifyCommand_InitialCommitTest extends AbstractGitCommandTestBase { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Rule + public ShiroRule shiro = new ShiroRule(); + @Rule + public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); + + @BeforeClass + public static void setSigner() { + GpgSigner.setDefault(new GitTestHelper.SimpleGpgSigner()); + } + + @Test + public void shouldCreateCommitOnMasterByDkefault() throws IOException, GitAPIException { + createContext().getGlobalConfig().setDefaultBranch(""); + + executeModifyCommand(); + + try (Git git = new Git(createContext().open())) { + List branches = git.branchList().call(); + assertThat(branches).extracting("name").containsExactly("refs/heads/master"); + } + } + + @Test + public void shouldCreateCommitWithConfiguredDefaultBranch() throws IOException, GitAPIException { + createContext().getGlobalConfig().setDefaultBranch("main"); + + executeModifyCommand(); + + try (Git git = new Git(createContext().open())) { + List branches = git.branchList().call(); + assertThat(branches).extracting("name").containsExactly("refs/heads/main"); + } + } + + private void executeModifyCommand() throws IOException { + File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile(); + + GitModifyCommand command = createCommand(); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.setCommitMessage("initial commit"); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("new_file", newFile, false)); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + + command.execute(request); + } + + private GitModifyCommand createCommand() { + return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), mock(LfsBlobStoreFactory.class)); + } + + @Override + protected String getZippedRepositoryResource() { + return "sonia/scm/repository/spi/scm-git-spi-empty-repo-test.zip"; + } +} 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 22ccb94e1c..5609a222f9 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 @@ -31,7 +31,7 @@ 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.repository.GitChangesetConverterFactory; +import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitTestHelper; import sonia.scm.store.InMemoryConfigurationStoreFactory; @@ -99,7 +99,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase commit(outgoing, "added a"); GitPushCommand push = new GitPushCommand(handler, - new GitContext(outgoingDirectory, outgoingRepository, null) + new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig()) ); PushCommandRequest req = new PushCommandRequest(); @@ -154,7 +154,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase private GitOutgoingCommand createCommand() { return new GitOutgoingCommand( - new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), + new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()), handler, GitTestHelper.createConverterFactory() ); 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 1c5796a44f..6deb3afe53 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 @@ -29,6 +29,7 @@ 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.repository.GitConfig; import sonia.scm.repository.api.PushResponse; import java.io.IOException; @@ -89,6 +90,6 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase */ private GitPushCommand createCommand() { - return new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null)); + return new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig())); } } diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-empty-repo-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-empty-repo-test.zip new file mode 100644 index 0000000000000000000000000000000000000000..80f2dbd18b6b2f26af992f60ddd9f721a09e5280 GIT binary patch literal 13430 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 literal 0 HcmV?d00001