From 24e2885524769bbf784b70d02e8229f94ceb3589 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 14 Oct 2018 19:11:56 +0200 Subject: [PATCH] #998 git: set repository head to default branch --- .../sonia/scm/repository/GitHeadHandler.java | 53 ++++++++ .../sonia/scm/repository/GitHeadModifier.java | 7 ++ .../sonia/scm/repository/GitHeadResolver.java | 7 ++ .../GitRepositoryModifyListener.java | 51 +++++--- .../scm/repository/GitHeadHandlerTest.java | 60 +++++++++ .../GitRepositoryModifyListenerTest.java | 116 ++++++++++++------ 6 files changed, 239 insertions(+), 55 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadHandler.java 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/GitHeadResolver.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadHandlerTest.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadHandler.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadHandler.java new file mode 100644 index 0000000000..23e6156297 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadHandler.java @@ -0,0 +1,53 @@ +package sonia.scm.repository; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.io.Files; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.File; +import java.io.IOException; + +@Singleton +public class GitHeadHandler implements GitHeadResolver, GitHeadModifier { + + private final GitRepositoryHandler repositoryHandler; + + @Inject + public GitHeadHandler(GitRepositoryHandler repositoryHandler) { + this.repositoryHandler = repositoryHandler; + } + + @Override + public String resolve(Repository repository) { + File headFile = findHeadFile(repository); + try { + String line = Files.readFirstLine(headFile, Charsets.UTF_8); + // TODO handle invalid head file + int index = line.indexOf(GitUtil.REF_HEAD_PREFIX); + return line.substring(index + GitUtil.REF_HEAD_PREFIX.length()); + } catch (IOException e) { + // TODO + throw Throwables.propagate(e); + } + } + + @Override + public void modify(Repository repository, String head) { + File headFile = findHeadFile(repository); + try { + String line = "ref: " + GitUtil.REF_HEAD_PREFIX + head + "\n"; + Files.write(line, headFile, Charsets.UTF_8); + } catch (IOException e) { + // TODO + throw Throwables.propagate(e); + } + } + + private File findHeadFile(Repository repository) { + // TODO handle non bare repositories + File directory = repositoryHandler.getDirectory(repository); + return new File(directory, "HEAD"); + } +} 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..f6597e3958 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadModifier.java @@ -0,0 +1,7 @@ +package sonia.scm.repository; + +public interface GitHeadModifier { + + void modify(Repository repository, String head); + +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadResolver.java new file mode 100644 index 0000000000..c9f3dce609 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadResolver.java @@ -0,0 +1,7 @@ +package sonia.scm.repository; + +public interface GitHeadResolver { + + String resolve(Repository repository); + +} 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 index 4309257350..64f93d45de 100644 --- 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 @@ -32,6 +32,7 @@ package sonia.scm.repository; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; +import com.google.common.base.Strings; import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,58 +41,74 @@ import sonia.scm.HandlerEvent; import sonia.scm.event.ScmEventBus; import sonia.scm.plugin.ext.Extension; +import javax.inject.Inject; + /** * 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); - + + private final GitHeadResolver headResolver; + private final GitHeadModifier headModifier; + + @Inject + public GitRepositoryModifyListener(GitHeadResolver headResolver, GitHeadModifier headModifier) { + this.headResolver = headResolver; + this.headModifier = headModifier; + } + /** * 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)) + + if ( isModifyEvent(event) && isGitRepository(event.getItem()) ) { - logger.info("git default branch of repository {} has changed, sending clear cache event", repository.getId()); - sendClearRepositoryCacheEvent(repository); + if (hasDefaultBranchChanged(event.getItemBeforeModification(), repository)) { + logger.info("git default branch of repository {} has changed, sending clear cache event", repository.getId()); + sendClearRepositoryCacheEvent(repository); + } + + String defaultBranch = repository.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH); + if (defaultBranch != null && ! defaultBranch.equals(headResolver.resolve(repository))) { + headModifier.modify(repository, defaultBranch); + } } } - + @VisibleForTesting protected void sendClearRepositoryCacheEvent(Repository repository) { - ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository)); + ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository)); } - + private boolean isModifyEvent(RepositoryEvent event) { return event.getEventType() == HandlerEvent.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), + old.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH), current.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH) ); } - + } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadHandlerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadHandlerTest.java new file mode 100644 index 0000000000..e3ae85baa2 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadHandlerTest.java @@ -0,0 +1,60 @@ +package sonia.scm.repository; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; +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 GitHeadHandlerTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Mock + private GitRepositoryHandler repositoryHandler; + + @InjectMocks + private GitHeadHandler headHandler; + + @Test + public void testResolve() throws IOException { + Repository repository = RepositoryTestData.createHeartOfGold("git"); + create(repository, "master"); + + String head = headHandler.resolve(repository); + assertEquals("master", head); + } + + @Test + public void testModify() throws IOException { + Repository repository = RepositoryTestData.createHeartOfGold("git"); + File file = create(repository, "master"); + + headHandler.modify(repository, "develop"); + + assertEquals("ref: refs/heads/develop", Files.readFirstLine(file, Charsets.UTF_8)); + } + + private File create(Repository repository, String head) throws IOException { + File directory = temporaryFolder.newFolder(); + File headFile = new File(directory, "HEAD"); + Files.write(String.format("ref: refs/heads/%s\n", head), headFile, Charsets.UTF_8); + + when(repositoryHandler.getDirectory(repository)).thenReturn(directory); + + return headFile; + } + +} 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 index 9f6768aeac..008000daef 100644 --- 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 @@ -1,10 +1,10 @@ /** * 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, @@ -13,7 +13,7 @@ * 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 @@ -24,34 +24,42 @@ * 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 static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.HandlerEvent; /** * Unit tests for {@link GitRepositoryModifyListener}. - * + * * @author Sebastian Sdorra */ +@RunWith(MockitoJUnitRunner.class) public class GitRepositoryModifyListenerTest { + @Mock + private GitHeadResolver headResolver; + + @Mock + private GitHeadModifier headModifier; + + @InjectMocks private GitRepositoryModifyTestListener repositoryModifyListener; - - /** - * Set up test object. - */ - @Before - public void setUpObjectUnderTest(){ - repositoryModifyListener = new GitRepositoryModifyTestListener(); - } /** * Tests happy path. @@ -62,14 +70,14 @@ public class GitRepositoryModifyListenerTest { old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master"); Repository current = RepositoryTestData.createHeartOfGold("git"); current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop"); - + RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY); repositoryModifyListener.handleEvent(event); - + assertNotNull(repositoryModifyListener.repository); assertSame(current, repositoryModifyListener.repository); } - + /** * Tests with new default branch. */ @@ -78,14 +86,14 @@ public class GitRepositoryModifyListenerTest { Repository old = RepositoryTestData.createHeartOfGold("git"); Repository current = RepositoryTestData.createHeartOfGold("git"); current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop"); - + RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY); repositoryModifyListener.handleEvent(event); - + assertNotNull(repositoryModifyListener.repository); assertSame(current, repositoryModifyListener.repository); } - + /** * Tests with non git repositories. */ @@ -95,13 +103,13 @@ public class GitRepositoryModifyListenerTest { old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master"); Repository current = RepositoryTestData.createHeartOfGold("hg"); current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop"); - + RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY); repositoryModifyListener.handleEvent(event); - + assertNull(repositoryModifyListener.repository); } - + /** * Tests without default branch. */ @@ -109,13 +117,13 @@ public class GitRepositoryModifyListenerTest { public void testWithoutDefaultBranch(){ Repository old = RepositoryTestData.createHeartOfGold("git"); Repository current = RepositoryTestData.createHeartOfGold("git"); - + RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY); repositoryModifyListener.handleEvent(event); - + assertNull(repositoryModifyListener.repository); } - + /** * Tests with non modify event. */ @@ -125,13 +133,13 @@ public class GitRepositoryModifyListenerTest { old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master"); Repository current = RepositoryTestData.createHeartOfGold("git"); current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop"); - + RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.CREATE); repositoryModifyListener.handleEvent(event); - + assertNull(repositoryModifyListener.repository); } - + /** * Tests with non git repositories. */ @@ -144,20 +152,52 @@ public class GitRepositoryModifyListenerTest { RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY); repositoryModifyListener.handleEvent(event); - + assertNull(repositoryModifyListener.repository); } - + + @Test + public void testModifyRepositoryHead() { + Repository old = RepositoryTestData.createHeartOfGold("git"); + Repository current = RepositoryTestData.createHeartOfGold("git"); + current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop"); + + when(headResolver.resolve(current)).thenReturn("master"); + + RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY); + repositoryModifyListener.handleEvent(event); + + verify(headModifier).modify(current, "develop"); + } + + @Test + public void testWithEqualHeads() { + Repository old = RepositoryTestData.createHeartOfGold("git"); + Repository current = RepositoryTestData.createHeartOfGold("git"); + current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop"); + + when(headResolver.resolve(current)).thenReturn("develop"); + + RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY); + repositoryModifyListener.handleEvent(event); + + verify(headModifier, never()).modify(current, "develop"); + } + private static class GitRepositoryModifyTestListener extends GitRepositoryModifyListener { - + private Repository repository; - + + public GitRepositoryModifyTestListener(GitHeadResolver headResolver, GitHeadModifier headModifier) { + super(headResolver, headModifier); + } + @Override protected void sendClearRepositoryCacheEvent(Repository repository) { this.repository = repository; - } - - } - + } -} \ No newline at end of file + } + + +}