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 deleted file mode 100644 index 23e6156297..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -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 index f6597e3958..d6613ad6da 100644 --- 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 @@ -1,7 +1,101 @@ +/** + * 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, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 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 + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * 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; -public interface GitHeadModifier { +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; - void modify(Repository repository, String head); +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.util.Objects; +/** + * The GitHeadModifier is able to modify the head of a git repository. + * + * @author Sebastian Sdorra + * @since 1.61 + */ +public class GitHeadModifier { + + private static final Logger LOG = LoggerFactory.getLogger(GitHeadModifier.class); + + private final GitRepositoryHandler repositoryHandler; + + @Inject + public GitHeadModifier(GitRepositoryHandler repositoryHandler) { + this.repositoryHandler = repositoryHandler; + } + + /** + * Ensures that the repositories head points to the given branch. The method will return {@code false} if the + * repositories head points already to the given branch. + * + * @param repository repository to modify + * @param newHead branch which should be the new head of the repository + * + * @return {@code true} if the head has changed + */ + public boolean ensure(Repository repository, String newHead) { + try (org.eclipse.jgit.lib.Repository gitRepository = open(repository)) { + String currentHead = resolve(gitRepository); + if (!Objects.equals(currentHead, newHead)) { + return modify(gitRepository, newHead); + } + } catch (IOException ex) { + LOG.warn("failed to change head of repository", ex); + } + return false; + } + + private String resolve(org.eclipse.jgit.lib.Repository gitRepository) throws IOException { + Ref ref = gitRepository.getRefDatabase().getRef(Constants.HEAD); + if ( ref.isSymbolic() ) { + ref = ref.getTarget(); + } + return GitUtil.getBranch(ref); + } + + private boolean modify(org.eclipse.jgit.lib.Repository gitRepository, String newHead) throws IOException { + RefUpdate refUpdate = gitRepository.getRefDatabase().newUpdate(Constants.HEAD, true); + refUpdate.setForceUpdate(true); + RefUpdate.Result result = refUpdate.link(Constants.R_HEADS + newHead); + return result == RefUpdate.Result.FORCED; + } + + private org.eclipse.jgit.lib.Repository open(Repository repository) throws IOException { + File directory = repositoryHandler.getDirectory(repository); + return GitUtil.open(directory); + } } 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 deleted file mode 100644 index c9f3dce609..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHeadResolver.java +++ /dev/null @@ -1,7 +0,0 @@ -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 64f93d45de..c0ab21af9d 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,7 +32,6 @@ 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; @@ -58,12 +57,10 @@ public class 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; + public GitRepositoryModifyListener(GitHeadModifier headModifier) { this.headModifier = headModifier; } @@ -85,8 +82,8 @@ public class GitRepositoryModifyListener { } String defaultBranch = repository.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH); - if (defaultBranch != null && ! defaultBranch.equals(headResolver.resolve(repository))) { - headModifier.modify(repository, defaultBranch); + if (defaultBranch != null) { + headModifier.ensure(repository, defaultBranch); } } } 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 deleted file mode 100644 index e3ae85baa2..0000000000 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadHandlerTest.java +++ /dev/null @@ -1,60 +0,0 @@ -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/GitHeadModifierTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java new file mode 100644 index 0000000000..7a205d2584 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java @@ -0,0 +1,100 @@ +/** + * 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, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 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 + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * 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 com.google.common.base.Charsets; +import com.google.common.io.Files; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +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 GitHeadModifierTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Mock + private GitRepositoryHandler repositoryHandler; + + @InjectMocks + private GitHeadModifier modifier; + + @Test + public void testEnsure() throws IOException, GitAPIException { + Repository repository = RepositoryTestData.createHeartOfGold("git"); + File headFile = create(repository, "master"); + + boolean result = modifier.ensure(repository, "develop"); + + assertEquals("ref: refs/heads/develop", Files.readFirstLine(headFile, Charsets.UTF_8)); + assertTrue(result); + } + + @Test + public void testEnsureWithSameBranch() throws IOException, GitAPIException { + Repository repository = RepositoryTestData.createHeartOfGold("git"); + create(repository, "develop"); + + boolean result = modifier.ensure(repository, "develop"); + + assertFalse(result); + } + + private File create(Repository repository, String head) throws IOException, GitAPIException { + File directory = temporaryFolder.newFolder(); + + Git.init() + .setBare(true) + .setDirectory(directory) + .call(); + + 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 008000daef..5f0f0aca29 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 @@ -52,9 +52,6 @@ import sonia.scm.HandlerEvent; @RunWith(MockitoJUnitRunner.class) public class GitRepositoryModifyListenerTest { - @Mock - private GitHeadResolver headResolver; - @Mock private GitHeadModifier headModifier; @@ -162,34 +159,19 @@ public class GitRepositoryModifyListenerTest { 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"); + verify(headModifier).ensure(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); + public GitRepositoryModifyTestListener(GitHeadModifier headModifier) { + super(headModifier); } @Override