/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.connector.git; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsSame.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.jcr.Binary; import javax.jcr.Item; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.Value; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListBranchCommand; import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.transport.RefSpec; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.common.util.IoUtil; import org.modeshape.jcr.MultiUseAbstractTest; import org.modeshape.jcr.RepositoryConfiguration; import org.modeshape.jcr.api.Session; import org.modeshape.jcr.api.federation.FederationManager; /** * Unit test for {@link org.modeshape.connector.git.GitConnector} */ public class GitConnectorTest extends MultiUseAbstractTest { private Node testRoot; @BeforeClass public static void beforeAll() throws Exception { loadGitRepositoryData(); RepositoryConfiguration config = RepositoryConfiguration.read("config/repo-config-git-federation.json"); startRepository(config); Session session = getSession(); Node testRoot = session.getRootNode().addNode("repos"); session.save(); FederationManager fedMgr = session.getWorkspace().getFederationManager(); fedMgr.createProjection(testRoot.getPath(), "remote-git-repo", "/", "git-modeshape-remote"); fedMgr.createProjection(testRoot.getPath(), "local-git-repo", "/", "git-modeshape-local"); } private static void loadGitRepositoryData() throws Exception { try (Git git = Git.open(new File("../.."))) { // the tests expect a series of remote branches and tags from origin, so if they're not present in the clone // where the test is running, we need to load them... List<RefSpec> refsToFetch = new ArrayList<>(); List<String> tagNames = git.tagList().call().stream() .map(ref -> ref.getName().replace("refs/tags/", "")) .collect(Collectors.toList()); Arrays.stream(expectedTagNames()) .filter(tagName -> !tagNames.contains(tagName)) .map(tagName -> new RefSpec("+refs/tags/" + tagName + ":refs/remotes/origin/" + tagName)) .forEach(refsToFetch::add); List<String> branchNames = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call() .stream() .map(ref -> ref.getName() .replace("refs/heads/", "") .replace("refs/remotes/origin/", "")) .collect(Collectors.toList()); Arrays.stream(expectedRemoteBranchNames()) .filter(branchName -> !branchNames.contains(branchName)) .map(branchName -> new RefSpec("+refs/heads/" + branchName + ":refs/remotes/origin/" + branchName)) .forEach(refsToFetch::add); if (!refsToFetch.isEmpty()) { // next fetch all the remote refs which we need for the test git.fetch().setRefSpecs(refsToFetch).call(); } } } @AfterClass public static void afterAll() throws Exception { MultiUseAbstractTest.afterAll(); } @Before public void before() throws Exception { testRoot = getSession().getRootNode().getNode("repos"); } protected Node gitRemoteNode() throws Exception { return testRoot.getNode("git-modeshape-remote"); } protected Node gitLocalNode() throws Exception { return testRoot.getNode("git-modeshape-local"); } @Test public void shouldReadFederatedNodeInProjection() throws Exception { Node git = gitRemoteNode(); assertThat(git, is(notNullValue())); assertThat(git.getParent(), is(sameInstance(testRoot))); assertThat(git.getPath(), is(testRoot.getPath() + "/git-modeshape-remote")); assertThat(git.getName(), is("git-modeshape-remote")); } @Test public void shouldReadTags() throws Exception { Node git = gitRemoteNode(); Node tags = git.getNode("tags"); assertChildrenInclude("Make sure you run <git fetch --tags>", tags, expectedTagNames()); } @Test public void shouldReadRemoteBranches() throws Exception { Node git = gitRemoteNode(); Node branches = git.getNode("branches"); assertChildrenInclude(branches, expectedRemoteBranchNames()); } @Test @FixFor( "MODE-2426" ) public void shouldReadLocalBranches() throws Exception { Node git = gitLocalNode(); Node branches = git.getNode("branches"); assertChildrenInclude(branches, "master"); } @Test public void shouldReadTreeSubgraph() throws Exception { Node git = gitRemoteNode(); Node tree = git.getNode("tree"); navigate(tree, false, 100, 2); } @Test public void shouldReadCommitSubgraph() throws Exception { Node git = gitRemoteNode(); Node commit = git.getNode("commit"); navigate(commit, false, 100, 2); } @FixFor( "MODE-1732" ) @Test public void shouldFollowReferenceFromRecentTagToCommit() throws Exception { Node git = gitRemoteNode(); Node tag = git.getNode("tags/modeshape-3.0.0.Final"); assertThat(tag.getProperty("git:objectId").getString(), is(notNullValue())); assertThat(tag.getProperty("git:tree").getString(), is(notNullValue())); assertThat(tag.getProperty("git:history").getString(), is(notNullValue())); Node tagTree = tag.getProperty("git:tree").getNode(); assertThat(tagTree.getPath(), is(treePathFor(tag))); assertChildrenInclude(tagTree, expectedTopLevelFileAndFolderNames()); // Load some of the child nodes ... Node pomFile = tagTree.getNode("pom.xml"); assertThat(pomFile.getPrimaryNodeType().getName(), is("git:file")); assertNodeHasObjectIdProperty(pomFile); assertNodeHasCommittedProperties(pomFile); Node pomContent = pomFile.getNode("jcr:content"); assertNodeHasCommittedProperties(pomContent); assertThat(pomContent.getProperty("jcr:data").getString(), is(notNullValue())); Node readmeFile = tagTree.getNode("README.md"); assertThat(readmeFile.getPrimaryNodeType().getName(), is("git:file")); assertNodeHasObjectIdProperty(readmeFile); assertNodeHasCommittedProperties(readmeFile); Node readmeContent = readmeFile.getNode("jcr:content"); assertNodeHasCommittedProperties(readmeContent); assertThat(readmeContent.getProperty("jcr:data").getString(), is(notNullValue())); Node parentModule = tagTree.getNode("modeshape-parent"); assertThat(parentModule.getPrimaryNodeType().getName(), is("git:folder")); assertNodeHasObjectIdProperty(parentModule); assertNodeHasCommittedProperties(parentModule); } protected String treePathFor( Node node ) throws Exception { Node git = gitRemoteNode(); String commitId = node.getProperty("git:objectId").getString(); return git.getPath() + "/tree/" + commitId; } @Test public void shouldFollowReferenceFromOldTagToCommit() throws Exception { Node git = gitRemoteNode(); Node tag = git.getNode("tags/dna-0.2"); assertThat(tag.getProperty("git:objectId").getString(), is(notNullValue())); assertThat(tag.getProperty("git:tree").getString(), is(notNullValue())); assertThat(tag.getProperty("git:history").getString(), is(notNullValue())); Node tagTree = tag.getProperty("git:tree").getNode(); assertThat(tagTree.getPath(), is(treePathFor(tag))); assertChildrenInclude(tagTree, "pom.xml", "dna-jcr", "dna-common", ".project"); } @Test public void shouldContainTagsAndBranchNamesAndCommitsUnderTreeNode() throws Exception { Node git = gitRemoteNode(); Node tree = git.getNode("tree"); assertThat(tree.getPrimaryNodeType().getName(), is("git:trees")); assertChildrenInclude(tree, expectedRemoteBranchNames()); assertChildrenInclude("Make sure you run <git fetch --tags>", tree, expectedTagNames()); } @Test public void shouldFindMasterBranchAsPrimaryItemUnderBranchNode() throws Exception { Node git = gitRemoteNode(); Node branches = git.getNode("branches"); Item primaryItem = branches.getPrimaryItem(); assertThat(primaryItem, is(notNullValue())); assertThat(primaryItem, is(instanceOf(Node.class))); Node primaryNode = (Node)primaryItem; assertThat(primaryNode.getName(), is("master")); assertThat(primaryNode.getParent(), is(sameInstance(branches))); assertThat(primaryNode, is(sameInstance(branches.getNode("master")))); } @Test public void shouldFindMasterBranchAsPrimaryItemUnderTreeNode() throws Exception { Node git = gitRemoteNode(); Node tree = git.getNode("tree"); Item primaryItem = tree.getPrimaryItem(); assertThat(primaryItem, is(notNullValue())); assertThat(primaryItem, is(instanceOf(Node.class))); Node primaryNode = (Node)primaryItem; assertThat(primaryNode.getName(), is("master")); assertThat(primaryNode.getParent(), is(sameInstance(tree))); assertThat(primaryNode, is(sameInstance(tree.getNode("master")))); } @Test public void shouldFindTreeBranchAsPrimaryItemUnderGitRoot() throws Exception { Node git = gitRemoteNode(); Node tree = git.getNode("tree"); assertThat(tree, is(notNullValue())); Item primaryItem = git.getPrimaryItem(); assertThat(primaryItem, is(notNullValue())); assertThat(primaryItem, is(instanceOf(Node.class))); Node primaryNode = (Node)primaryItem; assertThat(primaryNode.getName(), is(tree.getName())); assertThat(primaryNode.getParent(), is(sameInstance(git))); assertThat(primaryNode, is(sameInstance(tree))); } @Test public void shouldFindLatestCommitInMasterBranch() throws Exception { Node git = gitRemoteNode(); Node commits = git.getNode("commits"); Node master = commits.getNode("master"); Node commit = master.getNodes().nextNode(); // the first commit in the history of the 'master' branch ... // print = true; printDetails(commit); assertNodeHasObjectIdProperty(commit, commit.getName()); assertNodeHasCommittedProperties(commit); assertThat(commit.getProperty("git:title").getString(), is(notNullValue())); assertThat(commit.getProperty("git:tree").getNode().getPath(), is(git.getPath() + "/tree/" + commit.getName())); assertThat(commit.getProperty("git:detail").getNode().getPath(), is(git.getPath() + "/commit/" + commit.getName())); } @Test public void shouldFindLatestCommitDetailsInMasterBranch() throws Exception { Node git = gitRemoteNode(); Node commits = git.getNode("commit"); Node commit = commits.getNodes().nextNode(); // the first commit ... // print = true; printDetails(commit); assertNodeHasObjectIdProperty(commit); assertNodeHasCommittedProperties(commit); assertThat(commit.getProperty("git:parents").isMultiple(), is(true)); for (Value parentValue : commit.getProperty("git:parents").getValues()) { String identifier = parentValue.getString(); Node parent = getSession().getNodeByIdentifier(identifier); assertThat(parent, is(notNullValue())); } assertThat(commit.getProperty("git:diff").getString(), is(notNullValue())); assertThat(commit.getProperty("git:tree").getNode().getPath(), is(treePathFor(commit))); } @Test @FixFor( "MODE-2352" ) public void shouldReadTreeObjectProperties() throws Exception { Node tree = session.getNode("/repos/git-modeshape-remote/tree/72ea74be3b3a50345a1b2f543f78fd6be00caa35"); assertNotNull(tree); PropertyIterator propertyIterator = tree.getProperties(); while (propertyIterator.hasNext()) { Property property = propertyIterator.nextProperty(); assertNotNull(property.getName()); assertNotNull(property.getValue()); } } @Test @FixFor( "MODE-2352" ) public void shouldReadBranchObjectProperties() throws Exception { Node branch = session.getNode("/repos/git-modeshape-remote/branches/master"); assertNotNull(branch); PropertyIterator propertyIterator = branch.getProperties(); while (propertyIterator.hasNext()) { Property property = propertyIterator.nextProperty(); assertNotNull(property.getName()); assertNotNull(property.getValue()); } } @Test @FixFor( "MODE-2352" ) public void shouldNavigateCommitWithMultiplePages() throws Exception { Node commit = session.getNode("/repos/git-modeshape-remote/commits/d1f7daf32bd67edded7545221cd5c79d94813310"); assertNotNull(commit); NodeIterator childrenIterator = commit.getNodes(); while (childrenIterator.hasNext()) { childrenIterator.nextNode(); } } @Test @FixFor( "MODE-2643") public void shouldReadBinaryNodeAsLargeFile() throws Exception { //use some JGit API magic to reconfigure the default threshold which is around 50MB //so that when we attempt to read a larger binary, it will be seen as a large file by JGit WindowCacheConfig newConfig = new WindowCacheConfig(); newConfig.setStreamFileThreshold(2 * WindowCacheConfig.MB); newConfig.install(); try { readLargeBinary(); } finally { newConfig.setStreamFileThreshold(PackConfig.DEFAULT_BIG_FILE_THRESHOLD); newConfig.install(); } } @Test @FixFor( "MODE-2643") public void shouldReadBinaryNodeAsRegularFile() throws Exception { readLargeBinary(); } private void readLargeBinary() throws Exception { Node commit = session.getNode("/repos/git-modeshape-remote/tree/master/modeshape-jcr/src/test/resources/docs/postgresql-8.4.1-US.pdf"); assertNotNull(commit); Binary data = commit.getNode("jcr:content").getProperty("jcr:data").getBinary(); long size = data.getSize(); assertTrue(size > 0); //simply read the stream to make sure it's valid ByteArrayOutputStream baos = new ByteArrayOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(baos); IoUtil.write(data.getStream(), bos); assertEquals("invalid binary stream", size, baos.toByteArray().length); } protected void assertNodeHasObjectIdProperty( Node node ) throws Exception { assertThat(node.getProperty("git:objectId").getString(), is(notNullValue())); } protected void assertNodeHasObjectIdProperty( Node node, String commitId ) throws Exception { assertThat(node.getProperty("git:objectId").getString(), is(commitId)); } protected void assertNodeHasCommittedProperties( Node node ) throws Exception { assertThat(node.getProperty("git:author").getString(), is(notNullValue())); assertThat(node.getProperty("git:committer").getString(), is(notNullValue())); assertThat(node.getProperty("git:committed").getDate(), is(notNullValue())); assertThat(node.getProperty("git:title").getString(), is(notNullValue())); } /** * The <i>minimal</i> names of the files and/or folders that are expected to exist at the top-level of the Git repository. * Additional file and folder names will be acceptable. * * @return the file and folder names; never null */ protected static String[] expectedTopLevelFileAndFolderNames() { return new String[]{"modeshape-parent", "pom.xml"}; } /** * The <i>minimal</i> names of the branches that are expected to exist. Additional branch names will be acceptable. * Note that if any of these branches do not exist at startup, the tests will attempt to retrieve them from remote/origin * * @return the branch names; never null */ protected static String[] expectedRemoteBranchNames() { return new String[]{"master", "2.x", "3.x", "4.x"}; } /** * The <i>minimal</i> names of the tags that are expected to exist. Additional tag names will be acceptable. * Note that if any of these tags do not exist at startup, the tests will attempt to retrieve them from remote/origin * * @return the tag names; never null */ protected static String[] expectedTagNames() { return new String[]{"modeshape-3.0.0.Final", "dna-0.2"}; } }