Add a modifications provider for hooks

This new modifications provider consistently computes the modifications caused by a push to a branch. In contrast to the changeset provider that often has been used before to check what has changed, this also works for forced updates, rebased branches and fast-forwards.

Because these types of changes are normally only used with git, this provider (for now) has only been implemented for git.

Pushed-by: Rene Pfeuffer<rene.pfeuffer@cloudogu.com>
Pushed-by: Alexander Dammeier<alexander.dammeier@cloudogu.com>
Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
Committed-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
This commit is contained in:
Rene Pfeuffer
2023-11-16 13:18:40 +01:00
parent a8c32b10de
commit 571e6ad92f
32 changed files with 857 additions and 364 deletions

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.api;
import com.google.common.collect.Lists;
@@ -40,7 +40,7 @@ import org.mockito.junit.MockitoJUnitRunner;
/**
* Unit tests for {@link GitHookBranchProvider}.
*
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
@@ -48,9 +48,9 @@ public class GitHookBranchProviderTest {
@Mock
private ReceiveCommand command;
private List<ReceiveCommand> commands;
/**
* Prepare mocks for upcoming test.
*/
@@ -58,37 +58,37 @@ public class GitHookBranchProviderTest {
public void setUpMocks(){
commands = Lists.newArrayList(command);
}
/**
* Tests {@link GitHookBranchProvider#getCreatedOrModified()}.
*/
@Test
public void testGetCreatedOrModified(){
List<ReceiveCommand.Type> types = Arrays.asList(
ReceiveCommand.Type.CREATE, ReceiveCommand.Type.UPDATE, ReceiveCommand.Type.UPDATE_NONFASTFORWARD
List<ReceiveCommand.Type> types = Arrays.asList(
ReceiveCommand.Type.CREATE, ReceiveCommand.Type.UPDATE, ReceiveCommand.Type.UPDATE_NONFASTFORWARD
);
for ( ReceiveCommand.Type type : types ){
checkCreatedOrModified(type);
}
}
private void checkCreatedOrModified(ReceiveCommand.Type type){
GitHookBranchProvider provider = createGitHookBranchProvider(type, "refs/heads/hello");
assertThat(provider.getCreatedOrModified(), Matchers.contains("hello"));
assertThat(provider.getDeletedOrClosed(), empty());
assertThat(provider.getDeletedOrClosed(), empty());
}
/**
* Tests {@link GitHookBranchProvider#getDeletedOrClosed()}.
*/
*/
@Test
public void testGetDeletedOrClosed(){
GitHookBranchProvider provider = createGitHookBranchProvider(ReceiveCommand.Type.DELETE, "refs/heads/hello");
assertThat(provider.getDeletedOrClosed(), Matchers.contains("hello"));
assertThat(provider.getCreatedOrModified(), empty());
}
/**
* Tests {@link GitHookBranchProvider} with a tag instead of a branch.
*/
@@ -98,7 +98,7 @@ public class GitHookBranchProviderTest {
assertThat(provider.getCreatedOrModified(), empty());
assertThat(provider.getDeletedOrClosed(), empty());
}
private GitHookBranchProvider createGitHookBranchProvider(ReceiveCommand.Type type, String refName){
when(command.getType()).thenReturn(type);
when(command.getRefName()).thenReturn(refName);

View File

@@ -0,0 +1,116 @@
/*
* 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.api;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.junit.Test;
import sonia.scm.repository.spi.AbstractGitCommandTestBase;
import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GitHookModificationsProviderTest extends AbstractGitCommandTestBase {
@Test
public void shouldReturnModificationsForNormalUpdate() throws IOException {
GitHookModificationsProvider provider = mockProviderWithChange(ReceiveCommand.Type.UPDATE);
assertThat(provider.getModifications("rename"))
.extracting("modifications")
.asList()
.hasSize(2);
}
@Test
public void shouldReturnModificationsForFastForward() throws IOException {
GitHookModificationsProvider provider = mockProviderWithChange(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
assertThat(provider.getModifications("rename"))
.extracting("modifications")
.asList()
.hasSize(2);
}
@Test
public void shouldReturnEmptyModificationsForBranchWithRevertedCommit() throws IOException {
GitHookModificationsProvider provider = mockProviderWithChange(ReceiveCommand.Type.UPDATE, "03ca33468c2094249973d0ca11b80243a20de368", "592d797cd36432e591416e8b2b98154f4f163411");
assertThat(provider.getModifications("rename"))
.extracting("modifications")
.asList()
.isEmpty();
}
@Test
public void shouldReturnEmptyModificationsForDeletedBranch() throws IOException {
GitHookModificationsProvider provider = mockProviderWithChange(
ReceiveCommand.Type.DELETE,
"0000000000000000000000000000000000000000",
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
assertThat(provider.getModifications("rename"))
.extracting("modifications")
.asList()
.hasSize(5)
.extracting("path")
.contains("a.txt", "b.txt", "c/d.txt", "c/e.txt", "f.txt");
}
@Test
public void shouldReturnEmptyModificationsForCreatedBranch() throws IOException {
GitHookModificationsProvider provider = mockProviderWithChange(
ReceiveCommand.Type.CREATE,
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec",
"0000000000000000000000000000000000000000");
assertThat(provider.getModifications("rename"))
.extracting("modifications")
.asList()
.hasSize(5)
.extracting("path")
.contains("a.txt", "b.txt", "c/d.txt", "c/e.txt", "f.txt");
}
private GitHookModificationsProvider mockProviderWithChange(ReceiveCommand.Type update) throws IOException {
return mockProviderWithChange(
update,
"383b954b27e052db6880d57f1c860dc208795247",
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
}
private GitHookModificationsProvider mockProviderWithChange(ReceiveCommand.Type update, String newObjectId, String oldObjectId) throws IOException {
ReceiveCommand receiveCommand = mock(ReceiveCommand.class);
when(receiveCommand.getRefName()).thenReturn("refs/heads/rename");
when(receiveCommand.getType()).thenReturn(update);
when(receiveCommand.getNewId()).thenReturn(ObjectId.fromString(newObjectId));
when(receiveCommand.getOldId()).thenReturn(ObjectId.fromString(oldObjectId));
return new GitHookModificationsProvider(List.of(receiveCommand), createContext().open());
}
}

View File

@@ -43,6 +43,7 @@ import sonia.scm.repository.PreReceiveRepositoryHookEvent;
import sonia.scm.repository.api.BranchRequest;
import sonia.scm.repository.api.HookChangesetBuilder;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.HookModificationsProvider;
import java.io.IOException;
import java.util.List;
@@ -164,6 +165,11 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).containsExactly("new_branch");
assertThat(event.getContext().getBranchProvider().getDeletedOrClosed()).isEmpty();
HookModificationsProvider modificationsProvider = event.getContext().getModificationsProvider();
assertThat(modificationsProvider.getModifications("new_branch"))
.extracting("modifications")
.asList()
.hasSize(4);
}
@Test
@@ -190,5 +196,10 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
"f360a8738e4a29333786c5817f97a2c912814536",
"d1dfecbfd5b4a2f77fe40e1bde29e640f7f944be"
);
HookModificationsProvider modificationsProvider = event.getContext().getModificationsProvider();
assertThat(modificationsProvider.getModifications("squash"))
.extracting("modifications")
.asList()
.hasSize(8);
}
}

View File

@@ -76,7 +76,7 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
command.getModifications(revision);
Mockito.verify(command.context, times(3)).open();
Mockito.verify(command.context, times(2)).open();
Mockito.verify(repository, never()).close();
}