Post receive hook after import (#1544)

Fire post receive repository hook event after pull from remote
and after unbundle (Git, HG and SVN)

Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2021-02-22 09:20:15 +01:00
committed by GitHub
parent eef74e3a50
commit 83a9c90130
31 changed files with 1400 additions and 607 deletions

View File

@@ -28,7 +28,6 @@ package sonia.scm.repository.spi;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.google.inject.Provider;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -39,8 +38,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.GitChangesetConverterFactory;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.GitTestHelper;
import sonia.scm.repository.Repository;
@@ -57,22 +56,18 @@ import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
public class AbstractRemoteCommandTestBase
{
public class AbstractRemoteCommandTestBase {
/**
* Method description
*
*
* @throws GitAPIException
* @throws IOException
*/
@Before
public void setup() throws IOException, GitAPIException
{
public void setup() throws IOException, GitAPIException {
incomingDirectory = tempFolder.newFile("incoming");
incomingDirectory.delete();
outgoingDirectory = tempFolder.newFile("outgoing");
@@ -84,6 +79,9 @@ public class AbstractRemoteCommandTestBase
incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call();
outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call();
eventBus = mock(ScmEventBus.class);
eventFactory = mock(GitPostReceiveRepositoryHookEventFactory.class);
handler = mock(GitRepositoryHandler.class);
when(handler.getDirectory(incomingRepository.getId())).thenReturn(
incomingDirectory);
@@ -93,11 +91,9 @@ public class AbstractRemoteCommandTestBase
/**
* Method description
*
*/
@After
public void tearDownProtocol()
{
public void tearDownProtocol() {
Transport.unregister(proto);
}
@@ -105,11 +101,9 @@ public class AbstractRemoteCommandTestBase
/**
* Method description
*
*/
@Before
public void setUpProtocol()
{
public void setUpProtocol() {
// store reference to handle weak references
proto = new ScmTransportProtocol(GitTestHelper::createConverterFactory, () -> null, () -> null);
@@ -121,12 +115,10 @@ public class AbstractRemoteCommandTestBase
/**
* Method description
*
*
* @param expected
* @param actual
*/
protected void assertCommitsEquals(RevCommit expected, Changeset actual)
{
protected void assertCommitsEquals(RevCommit expected, Changeset actual) {
assertEquals(expected.getId().name(), actual.getId());
assertEquals(expected.getAuthorIdent().getName(),
actual.getAuthor().getName());
@@ -138,16 +130,12 @@ public class AbstractRemoteCommandTestBase
/**
* Method description
*
*
* @param git
* @param message
*
* @return
*
* @throws GitAPIException
*/
protected RevCommit commit(Git git, String message) throws GitAPIException
{
protected RevCommit commit(Git git, String message) throws GitAPIException {
User trillian = UserTestData.createTrillian();
CommitCommand cc = git.commit();
@@ -160,18 +148,15 @@ public class AbstractRemoteCommandTestBase
/**
* Method description
*
*
* @param git
* @param parent
* @param name
* @param content
*
* @throws GitAPIException
* @throws IOException
*/
protected void write(Git git, File parent, String name, String content)
throws IOException, GitAPIException
{
throws IOException, GitAPIException {
File file = new File(parent, name);
Files.write(content, file, Charsets.UTF_8);
@@ -180,31 +165,52 @@ public class AbstractRemoteCommandTestBase
//~--- fields ---------------------------------------------------------------
/** Field description */
/**
* Field description
*/
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
/** Field description */
/**
* Field description
*/
protected GitRepositoryHandler handler;
/** Field description */
/**
* Field description
*/
protected Repository incomingRepository;
/** Field description */
/**
* Field description
*/
protected Git incoming;
/** Field description */
/**
* Field description
*/
protected File incomingDirectory;
/** Field description */
/**
* Field description
*/
protected Git outgoing;
/** Field description */
/**
* Field description
*/
protected File outgoingDirectory;
/** Field description */
/**
* Field description
*/
protected Repository outgoingRepository;
/** Field description */
/**
* Field description
*/
private ScmTransportProtocol proto;
protected ScmEventBus eventBus;
protected GitPostReceiveRepositoryHookEventFactory eventFactory;
}

View File

@@ -44,24 +44,20 @@ import static org.junit.Assert.assertNotNull;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
public class GitIncomingCommandTest
extends AbstractRemoteCommandTestBase
{
extends AbstractRemoteCommandTestBase {
/**
* Method description
*
*
* @throws GitAPIException
* @throws IOException
*/
@Test
public void testGetIncomingChangesets()
throws IOException, GitAPIException
{
throws IOException, GitAPIException {
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
RevCommit c1 = commit(outgoing, "added a");
@@ -84,22 +80,25 @@ public class GitIncomingCommandTest
assertCommitsEquals(c2, cpr.getChangesets().get(1));
}
/**
/**
* Method description
*
*
* @throws GitAPIException
* @throws IOException
*/
@Test
public void testGetIncomingChangesetsWithAllreadyPullChangesets()
throws IOException, GitAPIException
{
public void testGetIncomingChangesetsWithAlreadyPulledChangesets()
throws IOException, GitAPIException {
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
commit(outgoing, "added a");
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()));
GitPullCommand pull = new GitPullCommand(
handler,
new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()),
eventBus,
eventFactory
);
PullCommandRequest req = new PullCommandRequest();
req.setRemoteRepository(outgoingRepository);
pull.pull(req);
@@ -124,13 +123,11 @@ public class GitIncomingCommandTest
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testGetIncomingChangesetsWithEmptyRepository()
throws IOException
{
throws IOException {
GitIncomingCommand cmd = createCommand();
IncomingCommandRequest request = new IncomingCommandRequest();
@@ -146,15 +143,13 @@ public class GitIncomingCommandTest
/**
* Check for correct behaviour
*
*
* @throws GitAPIException
* @throws IOException
*/
@Test
@Ignore
public void testGetIncomingChangesetsWithUnrelatedRepository()
throws IOException, GitAPIException
{
throws IOException, GitAPIException {
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
commit(outgoing, "added a");

View File

@@ -0,0 +1,71 @@
/*
* 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.google.common.collect.Iterables;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
import sonia.scm.repository.api.ImportFailedException;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
public class GitLazyChangesetResolverTest extends AbstractGitCommandTestBase {
@Test
public void shouldResolveChangesets() throws IOException {
GitLazyChangesetResolver changesetResolver = new GitLazyChangesetResolver(repository, Git.wrap(createContext().open()));
Iterable<RevCommit> commits = changesetResolver.call();
RevCommit firstCommit = commits.iterator().next();
assertThat(firstCommit.getId().toString()).isEqualTo("commit a8495c0335a13e6e432df90b3727fa91943189a7 1602078219 -----sp");
assertThat(firstCommit.getCommitTime()).isEqualTo(1602078219);
assertThat(firstCommit.getFullMessage()).isEqualTo("add deeper paths\n");
}
@Test
public void shouldResolveAllChangesets() throws IOException, GitAPIException {
Git git = Git.wrap(createContext().open());
GitLazyChangesetResolver changesetResolver = new GitLazyChangesetResolver(repository, git);
Iterable<RevCommit> allCommits = changesetResolver.call();
int allCommitsCounter = Iterables.size(allCommits);
int singleBranchCommitsCounter = Iterables.size(git.log().call());
assertThat(allCommitsCounter).isGreaterThan(singleBranchCommitsCounter);
}
@Test(expected = ImportFailedException.class)
public void shouldThrowImportFailedException() {
Git git = mock(Git.class);
doThrow(ImportFailedException.class).when(git).log();
new GitLazyChangesetResolver(repository, git).call();
}
}

View File

@@ -28,6 +28,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
import sonia.scm.repository.GitConfig;
import sonia.scm.repository.GitTestHelper;
import sonia.scm.repository.Modifications;
import java.io.File;
@@ -111,7 +112,12 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
PushCommandRequest request = new PushCommandRequest();
request.setRemoteRepository(incomingRepository);
cmd.push(request);
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()));
GitPullCommand pullCommand = new GitPullCommand(
handler,
new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()),
eventBus,
eventFactory
);
PullCommandRequest pullRequest = new PullCommandRequest();
pullRequest.setRemoteRepository(incomingRepository);
pullCommand.pull(pullRequest);

View File

@@ -0,0 +1,82 @@
/*
* 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.google.common.collect.ImmutableList;
import org.eclipse.jgit.api.Git;
import org.junit.Before;
import org.junit.Test;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.GitChangesetConverterFactory;
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
import sonia.scm.repository.Tag;
import sonia.scm.repository.api.HookContext;
import sonia.scm.repository.api.HookContextFactory;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GitPostReceiveRepositoryHookEventFactoryTest extends AbstractGitCommandTestBase {
private HookContext hookContext;
private GitPostReceiveRepositoryHookEventFactory eventFactory;
@Before
public void init() {
HookContextFactory hookContextFactory = mock(HookContextFactory.class);
hookContext = mock(HookContext.class, RETURNS_DEEP_STUBS);
when(hookContextFactory.createContext(any(), eq(repository))).thenReturn(hookContext);
GitChangesetConverterFactory converterFactory = mock(GitChangesetConverterFactory.class);
eventFactory = new GitPostReceiveRepositoryHookEventFactory(hookContextFactory, converterFactory);
}
@Test
public void shouldCreateEvent() throws IOException {
ImmutableList<String> branches = ImmutableList.of("master", "develop");
ImmutableList<Tag> tags = ImmutableList.of(new Tag("1.0", "123"), new Tag("2.0", "456"));
ImmutableList<Changeset> changesets = ImmutableList.of(new Changeset("1", 0L, null, "first"));
when(hookContext.getChangesetProvider().getChangesetList()).thenReturn(changesets);
when(hookContext.getBranchProvider().getCreatedOrModified()).thenReturn(branches);
when(hookContext.getTagProvider().getCreatedTags()).thenReturn(tags);
PostReceiveRepositoryHookEvent event = eventFactory.createEvent(
createContext(),
branches,
tags,
new GitLazyChangesetResolver(repository, Git.wrap(createContext().open()))
);
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).isSameAs(branches);
assertThat(event.getContext().getTagProvider().getCreatedTags()).isSameAs(tags);
assertThat(event.getContext().getChangesetProvider().getChangesetList().get(0).getId()).isEqualTo("1");
}
}

View File

@@ -24,70 +24,79 @@
package sonia.scm.repository.spi;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.Before;
import org.junit.Test;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
import sonia.scm.repository.RepositoryHookEvent;
import sonia.scm.repository.RepositoryHookType;
import sonia.scm.util.Archives;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class GitUnbundleCommandTest extends AbstractGitCommandTestBase {
private GitContext gitContext;
public class GitUnbundleCommandTest extends AbstractGitCommandTestBase {
private ScmEventBus eventBus;
private GitUnbundleCommand unbundleCommand;
private GitPostReceiveRepositoryHookEventFactory eventFactory;
@BeforeEach
void initCommand() {
gitContext = mock(GitContext.class);
unbundleCommand = new GitUnbundleCommand(gitContext);
@Before
public void initUnbundleCommand() {
eventBus = mock(ScmEventBus.class);
eventFactory = mock(GitPostReceiveRepositoryHookEventFactory.class);
unbundleCommand = new GitUnbundleCommand(createContext(), eventBus, eventFactory);
}
@Test
void shouldUnbundleRepositoryFiles(@TempDir Path temp) throws IOException {
public void shouldUnbundleRepositoryFiles() throws IOException {
when(eventFactory.createEvent(eq(createContext()), any(), any(), any()))
.thenReturn(new PostReceiveRepositoryHookEvent(new RepositoryHookEvent(null, repository, RepositoryHookType.POST_RECEIVE)));
String filePath = "test-input";
String fileContent = "HeartOfGold";
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(temp, filePath, fileContent);
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(filePath, fileContent);
unbundleCommand.unbundle(unbundleCommandRequest);
assertFileWithContentWasCreated(temp, filePath, fileContent);
assertFileWithContentWasCreated(createContext().getDirectory(), filePath, fileContent);
verify(eventBus).post(any(PostReceiveRepositoryHookEvent.class));
}
@Test
void shouldUnbundleNestedRepositoryFiles(@TempDir Path temp) throws IOException {
public void shouldUnbundleNestedRepositoryFiles() throws IOException {
String filePath = "objects/pack/test-input";
String fileContent = "hitchhiker";
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(temp, filePath, fileContent);
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(filePath, fileContent);
unbundleCommand.unbundle(unbundleCommandRequest);
assertFileWithContentWasCreated(temp, filePath, fileContent);
assertFileWithContentWasCreated(createContext().getDirectory(), filePath, fileContent);
}
private void assertFileWithContentWasCreated(@TempDir Path temp, String filePath, String fileContent) throws IOException {
File createdFile = temp.resolve(filePath).toFile();
private void assertFileWithContentWasCreated(File temp, String filePath, String fileContent) throws IOException {
File createdFile = temp.toPath().resolve(filePath).toFile();
assertThat(createdFile).exists();
assertThat(Files.readLines(createdFile, StandardCharsets.UTF_8).get(0)).isEqualTo(fileContent);
assertThat(createdFile).hasContent(fileContent);
}
private UnbundleCommandRequest createUnbundleCommandRequestForFile(Path temp, String filePath, String fileContent) throws IOException {
private UnbundleCommandRequest createUnbundleCommandRequestForFile(String filePath, String fileContent) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
TarArchiveOutputStream taos = Archives.createTarOutputStream(baos);
addEntry(taos, filePath, fileContent);
taos.finish();
taos.close();
when(gitContext.getDirectory()).thenReturn(temp.toFile());
ByteSource byteSource = ByteSource.wrap(baos.toByteArray());
UnbundleCommandRequest unbundleCommandRequest = new UnbundleCommandRequest(byteSource);
return unbundleCommandRequest;