Set default branch for repository on init

This commit is contained in:
René Pfeuffer
2020-10-30 12:52:07 +01:00
parent 8429b62cc3
commit c3baf274d1
7 changed files with 144 additions and 145 deletions

View File

@@ -30,15 +30,16 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.attributes.FilterCommandRegistry;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ConcurrentModificationException; import sonia.scm.ConcurrentModificationException;
import sonia.scm.ContextEntry; import sonia.scm.ContextEntry;
import sonia.scm.NoChangesMadeException; import sonia.scm.NoChangesMadeException;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.GitWorkingCopyFactory; import sonia.scm.repository.GitWorkingCopyFactory;
import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.web.lfs.LfsBlobStoreFactory; import sonia.scm.web.lfs.LfsBlobStoreFactory;
import javax.inject.Inject; import javax.inject.Inject;
@@ -50,21 +51,22 @@ import java.util.concurrent.locks.Lock;
public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand { public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand {
private static final Logger LOG = LoggerFactory.getLogger(GitModifyCommand.class);
private static final Striped<Lock> REGISTER_LOCKS = Striped.lock(5); private static final Striped<Lock> REGISTER_LOCKS = Striped.lock(5);
private final GitWorkingCopyFactory workingCopyFactory; private final GitWorkingCopyFactory workingCopyFactory;
private final LfsBlobStoreFactory lfsBlobStoreFactory; private final LfsBlobStoreFactory lfsBlobStoreFactory;
private final GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider;
@Inject @Inject
GitModifyCommand(GitContext context, GitRepositoryHandler repositoryHandler, LfsBlobStoreFactory lfsBlobStoreFactory) { GitModifyCommand(GitContext context, GitRepositoryHandler repositoryHandler, LfsBlobStoreFactory lfsBlobStoreFactory, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
this(context, repositoryHandler.getWorkingCopyFactory(), lfsBlobStoreFactory); this(context, repositoryHandler.getWorkingCopyFactory(), lfsBlobStoreFactory, gitRepositoryConfigStoreProvider);
} }
GitModifyCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory, LfsBlobStoreFactory lfsBlobStoreFactory) { GitModifyCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory, LfsBlobStoreFactory lfsBlobStoreFactory, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
super(context); super(context);
this.workingCopyFactory = workingCopyFactory; this.workingCopyFactory = workingCopyFactory;
this.lfsBlobStoreFactory = lfsBlobStoreFactory; this.lfsBlobStoreFactory = lfsBlobStoreFactory;
this.gitRepositoryConfigStoreProvider = gitRepositoryConfigStoreProvider;
} }
@Override @Override
@@ -112,9 +114,15 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
if (StringUtils.isNotBlank(branch)) { if (StringUtils.isNotBlank(branch)) {
try { try {
getClone().checkout().setName(branch).setCreateBranch(true).call(); getClone().checkout().setName(branch).setCreateBranch(true).call();
ConfigurationStore<GitRepositoryConfig> store = gitRepositoryConfigStoreProvider
.get(repository);
GitRepositoryConfig gitRepositoryConfig = store
.getOptional()
.orElse(new GitRepositoryConfig());
gitRepositoryConfig.setDefaultBranch(branch);
store.set(gitRepositoryConfig);
} catch (GitAPIException e) { } catch (GitAPIException e) {
throw new InternalRepositoryException(repository, "could not create default branch for initial commit", e); throw new InternalRepositoryException(repository, "could not create default branch for initial commit", e);
} }
} }
} }

View File

@@ -27,23 +27,17 @@ package sonia.scm.repository.spi;
import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware; import com.github.sdorra.shiro.SubjectAware;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.GpgSignature;
import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.GpgSigner;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import sonia.scm.AlreadyExistsException; import sonia.scm.AlreadyExistsException;
import sonia.scm.BadRequestException; import sonia.scm.BadRequestException;
@@ -53,7 +47,6 @@ import sonia.scm.repository.GitTestHelper;
import sonia.scm.repository.Person; import sonia.scm.repository.Person;
import sonia.scm.repository.work.NoneCachingWorkingCopyPool; import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
import sonia.scm.repository.work.WorkdirProvider; import sonia.scm.repository.work.WorkdirProvider;
import sonia.scm.security.PublicKey;
import sonia.scm.web.lfs.LfsBlobStoreFactory; import sonia.scm.web.lfs.LfsBlobStoreFactory;
import java.io.File; import java.io.File;
@@ -62,6 +55,7 @@ import java.nio.file.Files;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static sonia.scm.repository.spi.GitRepositoryConfigStoreProviderTestUtil.createGitRepositoryConfigStoreProvider;
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret") @SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
public class GitModifyCommandTest extends AbstractGitCommandTestBase { public class GitModifyCommandTest extends AbstractGitCommandTestBase {
@@ -381,7 +375,11 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase {
} }
private GitModifyCommand createCommand() { private GitModifyCommand createCommand() {
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory); return new GitModifyCommand(
createContext(),
new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())),
lfsBlobStoreFactory,
createGitRepositoryConfigStoreProvider());
} }
@FunctionalInterface @FunctionalInterface

View File

@@ -1,126 +0,0 @@
/*
* 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 shouldCreateCommitOnMasterByDefault() throws IOException, GitAPIException {
createContext().getGlobalConfig().setDefaultBranch("");
executeModifyCommand();
try (Git git = new Git(createContext().open())) {
List<Ref> 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<Ref> branches = git.branchList().call();
assertThat(branches).extracting("name").containsExactly("refs/heads/main");
}
}
@Test
public void shouldCreateCommitWithBranchFromRequestIfPresent() throws IOException, GitAPIException {
createContext().getGlobalConfig().setDefaultBranch("main");
ModifyCommandRequest request = createRequest();
request.setBranch("different");
createCommand().execute(request);
try (Git git = new Git(createContext().open())) {
List<Ref> branches = git.branchList().call();
assertThat(branches).extracting("name").containsExactly("refs/heads/different");
}
}
private void executeModifyCommand() throws IOException {
createCommand().execute(createRequest());
}
private ModifyCommandRequest createRequest() throws IOException {
File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile();
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"));
return 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";
}
}

View File

@@ -50,6 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sonia.scm.repository.spi.GitRepositoryConfigStoreProviderTestUtil.createGitRepositoryConfigStoreProvider;
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret") @SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase { public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase {
@@ -131,7 +132,11 @@ public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase {
} }
private GitModifyCommand createCommand() { private GitModifyCommand createCommand() {
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory); return new GitModifyCommand(
createContext(),
new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())),
lfsBlobStoreFactory,
createGitRepositoryConfigStoreProvider());
} }
@Override @Override

View File

@@ -29,14 +29,18 @@ import com.github.sdorra.shiro.SubjectAware;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.lib.GpgSigner;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import sonia.scm.repository.GitTestHelper;
import sonia.scm.repository.Person; import sonia.scm.repository.Person;
import sonia.scm.repository.work.NoneCachingWorkingCopyPool; import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
import sonia.scm.repository.work.WorkdirProvider; import sonia.scm.repository.work.WorkdirProvider;
@@ -45,9 +49,11 @@ import sonia.scm.web.lfs.LfsBlobStoreFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static sonia.scm.repository.spi.GitRepositoryConfigStoreProviderTestUtil.createGitRepositoryConfigStoreProvider;
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret") @SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommandTestBase { public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommandTestBase {
@@ -61,6 +67,11 @@ public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommand
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class); private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
@BeforeClass
public static void setSigner() {
GpgSigner.setDefault(new GitTestHelper.SimpleGpgSigner());
}
@Test @Test
public void shouldCreateNewFileInEmptyRepository() throws IOException, GitAPIException { public void shouldCreateNewFileInEmptyRepository() throws IOException, GitAPIException {
File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile(); File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile();
@@ -79,6 +90,44 @@ public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommand
assertInTree(assertions); assertInTree(assertions);
} }
@Test
public void shouldCreateCommitOnMasterByDefault() throws IOException, GitAPIException {
createContext().getGlobalConfig().setDefaultBranch("");
executeModifyCommand();
try (Git git = new Git(createContext().open())) {
List<Ref> 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<Ref> branches = git.branchList().call();
assertThat(branches).extracting("name").containsExactly("refs/heads/main");
}
}
@Test
public void shouldCreateCommitWithBranchFromRequestIfPresent() throws IOException, GitAPIException {
createContext().getGlobalConfig().setDefaultBranch("main");
ModifyCommandRequest request = createRequest();
request.setBranch("different");
createCommand().execute(request);
try (Git git = new Git(createContext().open())) {
List<Ref> branches = git.branchList().call();
assertThat(branches).extracting("name").containsExactly("refs/heads/different");
}
}
@Override @Override
protected String getZippedRepositoryResource() { protected String getZippedRepositoryResource() {
return "sonia/scm/repository/spi/scm-git-empty-repo.zip"; return "sonia/scm/repository/spi/scm-git-empty-repo.zip";
@@ -97,12 +146,30 @@ public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommand
} }
} }
private RevCommit getLastCommit(Git git) throws GitAPIException { private RevCommit getLastCommit(Git git) throws GitAPIException, IOException {
return git.log().setMaxCount(1).call().iterator().next(); return git.log().setMaxCount(1).all().call().iterator().next();
}
private void executeModifyCommand() throws IOException {
createCommand().execute(createRequest());
}
private ModifyCommandRequest createRequest() throws IOException {
File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile();
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"));
return request;
} }
private GitModifyCommand createCommand() { private GitModifyCommand createCommand() {
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory); return new GitModifyCommand(
createContext(),
new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())),
lfsBlobStoreFactory,
createGitRepositoryConfigStoreProvider());
} }
@FunctionalInterface @FunctionalInterface

View File

@@ -0,0 +1,47 @@
/*
* 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 sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.Repository;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.InMemoryConfigurationStore;
import java.util.HashMap;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GitRepositoryConfigStoreProviderTestUtil {
static GitRepositoryConfigStoreProvider createGitRepositoryConfigStoreProvider() {
GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider = mock(GitRepositoryConfigStoreProvider.class);
HashMap<String, ConfigurationStore<GitRepositoryConfig>> storeMap = new HashMap<>();
when(gitRepositoryConfigStoreProvider.get(any())).thenAnswer(invocation -> storeMap.computeIfAbsent(invocation.getArgument(0, Repository.class).getId(), id -> new InMemoryConfigurationStore<>()));
return gitRepositoryConfigStoreProvider;
}
}