Java tutorial
/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * 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.uberfire.java.nio.fs.jgit; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.commons.io.IOUtils; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.hooks.PostCommitHook; import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.TreeWalk; import org.junit.Test; import org.uberfire.java.nio.fs.jgit.util.Git; import org.uberfire.java.nio.fs.jgit.util.commands.CreateRepository; import org.uberfire.java.nio.fs.jgit.util.commands.ListRefs; import org.uberfire.java.nio.fs.jgit.util.commands.SubdirectoryClone; public class JGitSubdirectoryCloneTest extends AbstractTestInfra { private static final String TARGET_GIT = "target/target", SOURCE_GIT = "source/source"; @Test public void cloneSubdirectorySingleBranch() throws Exception { final File parentFolder = createTempDirectory(); final File sourceDir = new File(parentFolder, SOURCE_GIT + ".git"); final File targetDir = new File(parentFolder, TARGET_GIT + ".git"); final Git origin = gitRepo(sourceDir); commit(origin, "master", "first", content("dir1/file.txt", "foo")); commit(origin, "master", "second", content("dir2/file2.txt", "bar")); commit(origin, "master", "third", content("file3.txt", "moogah")); final Git cloned = new SubdirectoryClone(targetDir, sourceDir.getAbsoluteFile().toURI().toString(), "dir1", singletonList("master"), CredentialsProvider.getDefault(), null, null).execute(); assertThat(origin.getRepository().getRemoteNames()).isEmpty(); assertThat(cloned).isNotNull(); assertThat(listRefs(cloned)).hasSize(1); final List<RevCommit> cloneCommits = getCommits(cloned, "master"); assertThat(cloneCommits).hasSize(1); final RevCommit clonedCommit = cloneCommits.get(0); final RevCommit originCommit = getCommits(origin, "master").get(2); // Ordered children first assertClonedCommitData(origin, "dir1", clonedCommit, originCommit); } @Test public void cloneSubdirectoryMultipleBranches() throws Exception { final File parentFolder = createTempDirectory(); final File sourceDir = new File(parentFolder, SOURCE_GIT + ".git"); final File targetDir = new File(parentFolder, TARGET_GIT + ".git"); final Git origin = gitRepo(sourceDir); commit(origin, "master", "first", content("dir1/file.txt", "foo"), content("dir2/file2.txt", "bar"), content("file3.txt", "moogah")); branch(origin, "master", "dev"); commit(origin, "dev", "second", content("dir1/file.txt", "foo1"), content("file3.txt", "bar1")); branch(origin, "master", "ignored"); commit(origin, "ignored", "third", content("dir1/file.txt", "foo2")); final Git cloned = new SubdirectoryClone(targetDir, sourceDir.getAbsoluteFile().toURI().toString(), "dir1", asList("master", "dev"), CredentialsProvider.getDefault(), null, null).execute(); assertThat(cloned).isNotNull(); final Set<String> clonedRefs = listRefs(cloned).stream().map(ref -> ref.getName()).collect(toSet()); assertThat(clonedRefs).hasSize(2); assertThat(clonedRefs).containsExactly("refs/heads/master", "refs/heads/dev"); // Check master commits { final List<RevCommit> cloneCommits = getCommits(cloned, "master"); assertThat(cloneCommits).hasSize(1); assertClonedCommitData(origin, "dir1", cloneCommits.get(0), getCommits(origin, "master").get(0)); } // Check dev commits { final List<RevCommit> cloneCommits = getCommits(cloned, "dev"); assertThat(cloneCommits).hasSize(2); final List<RevCommit> originCommits = getCommits(origin, "dev"); assertClonedCommitData(origin, "dir1", cloneCommits.get(0), originCommits.get(0)); assertClonedCommitData(origin, "dir1", cloneCommits.get(1), originCommits.get(1)); } } @Test public void cloneSubdirectoryWithMergeCommit() throws Exception { final File parentFolder = createTempDirectory(); final File sourceDir = new File(parentFolder, SOURCE_GIT + ".git"); final File targetDir = new File(parentFolder, TARGET_GIT + ".git"); final Git origin = gitRepo(sourceDir); commit(origin, "master", "first", content("dir1/file.txt", "foo"), content("dir2/file2.txt", "bar"), content("file3.txt", "moogah")); branch(origin, "master", "dev"); commit(origin, "dev", "second", content("dir1/file.txt", "foo1"), content("file3.txt", "bar1")); commit(origin, "master", "another", content("dir1/file2.txt", "blah")); mergeCommit(origin, "master", "dev", content("dir1/file.txt", "merged value!"), content("dir2/file2.txt", "merged value!"), content("file3.txt", "merged value!")); final Git cloned = new SubdirectoryClone(targetDir, sourceDir.getAbsoluteFile().toURI().toString(), "dir1", asList("master", "dev"), CredentialsProvider.getDefault(), null, null).execute(); assertThat(cloned).isNotNull(); final Set<String> clonedRefs = listRefs(cloned).stream().map(ref -> ref.getName()).collect(toSet()); assertThat(clonedRefs).hasSize(2); assertThat(clonedRefs).containsExactly("refs/heads/master", "refs/heads/dev"); // Check master commits { final List<RevCommit> cloneCommits = getCommits(cloned, "master"); assertThat(cloneCommits).hasSize(4); // 2 on master + 1 on dev + 1 merge commit final List<RevCommit> originCommits = getCommits(origin, "master"); assertClonedCommitData(origin, "dir1", cloneCommits.get(0), originCommits.get(0)); assertClonedCommitData(origin, "dir1", cloneCommits.get(1), originCommits.get(1)); assertClonedCommitData(origin, "dir1", cloneCommits.get(2), originCommits.get(2)); assertClonedCommitData(origin, "dir1", cloneCommits.get(3), originCommits.get(3)); // Check that we preserved the topology of commits. assertThat(cloneCommits.get(0).getParentCount()).isEqualTo(2); assertThat(cloneCommits.get(1).getParentCount()).isEqualTo(1); assertThat(cloneCommits.get(2).getParentCount()).isEqualTo(1); assertThat(cloneCommits.get(3).getParentCount()).isEqualTo(0); } // Check dev commits { final List<RevCommit> cloneCommits = getCommits(cloned, "dev"); assertThat(cloneCommits).hasSize(2); final List<RevCommit> originCommits = getCommits(origin, "dev"); assertClonedCommitData(origin, "dir1", cloneCommits.get(0), originCommits.get(0)); assertClonedCommitData(origin, "dir1", cloneCommits.get(1), originCommits.get(1)); } } @Test public void cloneSubdirectoryWithHookDir() throws Exception { final File hooksDir = createTempDirectory(); writeMockHook(hooksDir, PostCommitHook.NAME); writeMockHook(hooksDir, PreCommitHook.NAME); final File parentFolder = createTempDirectory(); final File sourceDir = new File(parentFolder, SOURCE_GIT + ".git"); final File targetDir = new File(parentFolder, TARGET_GIT + ".git"); final Git origin = gitRepo(sourceDir); commit(origin, "master", "first", content("dir1/file.txt", "foo")); commit(origin, "master", "second", content("dir2/file2.txt", "bar")); commit(origin, "master", "third", content("file3.txt", "moogah")); final Git cloned = new SubdirectoryClone(targetDir, sourceDir.getAbsoluteFile().toURI().toString(), "dir1", singletonList("master"), CredentialsProvider.getDefault(), null, hooksDir).execute(); assertThat(origin.getRepository().getRemoteNames()).isEmpty(); assertThat(cloned).isNotNull(); assertThat(listRefs(cloned)).hasSize(1); final List<RevCommit> cloneCommits = getCommits(cloned, "master"); assertThat(cloneCommits).hasSize(1); final RevCommit clonedCommit = cloneCommits.get(0); final RevCommit originCommit = getCommits(origin, "master").get(2); // Ordered children first assertClonedCommitData(origin, "dir1", clonedCommit, originCommit); boolean foundPreCommitHook = false; boolean foundPostCommitHook = false; File[] hooks = new File(cloned.getRepository().getDirectory(), "hooks").listFiles(); assertThat(hooks).isNotEmpty().isNotNull(); assertThat(hooks.length).isEqualTo(2); for (File hook : hooks) { if (hook.getName().equals(PreCommitHook.NAME)) { foundPreCommitHook = hook.canExecute(); } else if (hook.getName().equals(PostCommitHook.NAME)) { foundPostCommitHook = hook.canExecute(); } } assertThat(foundPreCommitHook).isTrue(); assertThat(foundPostCommitHook).isTrue(); } private void assertClonedCommitData(final Git origin, final String subdirectory, final RevCommit clonedCommit, final RevCommit originCommit) throws Exception { assertThat(clonedCommit.getFullMessage()).isEqualTo(originCommit.getFullMessage()); final PersonIdent authorIdent = clonedCommit.getAuthorIdent(); final PersonIdent commiterIdent = clonedCommit.getCommitterIdent(); assertThat(authorIdent).isEqualTo(commiterIdent); assertThat(authorIdent.getName()).isEqualTo("name"); assertThat(authorIdent.getEmailAddress()).isEqualTo("name@example.com"); final ObjectId originDirId = findIdForPath(origin, originCommit, subdirectory); final ObjectId clonedTreeId = clonedCommit.getTree().getId(); assertThat(clonedTreeId).isEqualTo(originDirId); } private ObjectId findIdForPath(final Git origin, final RevCommit originMasterTip, final String searchPath) throws Exception { try (TreeWalk treeWalk = new TreeWalk(origin.getRepository())) { final int treeId = treeWalk.addTree(originMasterTip.getTree()); treeWalk.setRecursive(false); final CanonicalTreeParser treeParser = treeWalk.getTree(treeId, CanonicalTreeParser.class); while (treeWalk.next()) { final String path = treeParser.getEntryPathString(); if (path.equals(searchPath)) { return treeParser.getEntryObjectId(); } } } throw new AssertionError( String.format("Could not find path [%s] in commit [%s].", searchPath, originMasterTip.name())); } private List<RevCommit> getCommits(final Git git, String branch) throws Exception { List<RevCommit> commits = new ArrayList<>(); try (RevWalk revWalk = new RevWalk(git.getRepository())) { final RevCommit branchTip = revWalk.parseCommit(git.getRepository().resolve(branch)); revWalk.markStart(branchTip); revWalk.sort(RevSort.TOPO); final Iterator<RevCommit> iter = revWalk.iterator(); while (iter.hasNext()) { commits.add(iter.next()); } } return commits; } private Git gitRepo(File gitSource) { return new CreateRepository(gitSource).execute().get(); } /* * Unfortunately there is no easier way to write a commit with multiple parents. */ private void mergeCommit(final Git origin, final String targetBranchName, final String sourceBranchName, final TestFile... testFiles) throws Exception { final Repository repo = origin.getRepository(); final org.eclipse.jgit.api.Git git = org.eclipse.jgit.api.Git.wrap(repo); final ObjectId targetId = repo.resolve(targetBranchName); final ObjectId sourceId = repo.resolve(sourceBranchName); final DirCache dc = DirCache.newInCore(); final DirCacheEditor editor = dc.editor(); try (ObjectInserter inserter = repo.newObjectInserter()) { final ObjectId treeId = writeTestFilesToTree(dc, editor, inserter, testFiles); final ObjectId commitId = writeCommit(inserter, treeId, targetId, sourceId); updateBranch(targetBranchName, git, commitId); } } private void updateBranch(final String targetBranchName, final org.eclipse.jgit.api.Git git, final ObjectId commitId) throws Exception { git.branchCreate().setName(targetBranchName).setStartPoint(commitId.name()).setForce(true).call(); } private ObjectId writeCommit(final ObjectInserter inserter, final ObjectId commitTreeId, final ObjectId... parentIds) throws IOException { final CommitBuilder builder = new CommitBuilder(); builder.setAuthor(new PersonIdent("name", "name@example.com")); builder.setCommitter(new PersonIdent("name", "name@example.com")); builder.setTreeId(commitTreeId); builder.setMessage("merge commit"); builder.setParentIds(parentIds); final ObjectId commitId = inserter.insert(builder); return commitId; } private ObjectId writeTestFilesToTree(final DirCache dc, final DirCacheEditor editor, ObjectInserter inserter, final TestFile... testFiles) throws Exception { for (TestFile data : testFiles) { writeBlob(editor, inserter, data); } editor.finish(); final ObjectId commitTreeId = dc.writeTree(inserter); return commitTreeId; } private void writeBlob(final DirCacheEditor editor, ObjectInserter inserter, TestFile data) throws IOException { final ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, data.content.length(), IOUtils.toInputStream(data.content, "UTF-8")); editor.add(new PathEdit(data.path) { @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.REGULAR_FILE); ent.setObjectId(blobId); } }); } }