diff --git a/src/main/java/net/kemitix/gitdb/GitDB.java b/src/main/java/net/kemitix/gitdb/GitDB.java index d488cde..1182da0 100644 --- a/src/main/java/net/kemitix/gitdb/GitDB.java +++ b/src/main/java/net/kemitix/gitdb/GitDB.java @@ -22,9 +22,7 @@ package net.kemitix.gitdb; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryBuilder; import java.io.IOException; import java.nio.file.Path; @@ -57,22 +55,15 @@ public interface GitDB { * @return a GitDB instance for the local gitdb */ static GitDBLocal openLocal(final Path dbDir) { - if (dbDir.toFile().isFile()) { - throw new GitDBRepoNotFoundException("Not a directory", dbDir); - } - if (!dbDir.toFile().exists()) { - throw new GitDBRepoNotFoundException("Directory not found", dbDir); - } - Repository repository = null; try { - repository = Git.open(dbDir.toFile()).getRepository(); - } catch (Exception e) { - e.printStackTrace(); + return Optional.of(Git.open(dbDir.toFile())) + .map(Git::getRepository) + .filter(Repository::isBare) + .map(GitDBLocal::new) + .orElseThrow(() -> new InvalidRepositoryException("Not a bare repo", dbDir)); + } catch (IOException e) { + throw new InvalidRepositoryException("Error opening repository", dbDir, e); } - if (repository != null && repository.isBare()) { - return new GitDBLocal(repository); - } - throw new InvalidRepositoryException("Not a bare repo", dbDir); } /** @@ -80,6 +71,7 @@ public interface GitDB { * * @param name the branch to select * @return an Optional containing the branch if it exists + * @throws IOException if there is an error accessing the branch name */ - Optional branch(String name); + Optional branch(String name) throws IOException; } diff --git a/src/main/java/net/kemitix/gitdb/GitDBBranch.java b/src/main/java/net/kemitix/gitdb/GitDBBranch.java index 8c50315..bb7f1c9 100644 --- a/src/main/java/net/kemitix/gitdb/GitDBBranch.java +++ b/src/main/java/net/kemitix/gitdb/GitDBBranch.java @@ -21,6 +21,8 @@ package net.kemitix.gitdb; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import org.eclipse.jgit.lib.Ref; /** @@ -28,8 +30,18 @@ import org.eclipse.jgit.lib.Ref; * * @author Paul Campbell (pcampbell@kemitix.net) */ -public interface GitDBBranch { - static GitDBBranch withRef(final Ref ref) { - return null; +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class GitDBBranch { + + private final Ref ref; + + /** + * Create a new instance of GitDBBranch for the Ref. + * + * @param ref the Ref + * @return a GitDBBranch + */ + public static GitDBBranch withRef(final Ref ref) { + return new GitDBBranch(ref); } } diff --git a/src/main/java/net/kemitix/gitdb/GitDBLocal.java b/src/main/java/net/kemitix/gitdb/GitDBLocal.java index 87e447f..f80cebf 100644 --- a/src/main/java/net/kemitix/gitdb/GitDBLocal.java +++ b/src/main/java/net/kemitix/gitdb/GitDBLocal.java @@ -21,13 +21,12 @@ package net.kemitix.gitdb; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.*; import org.eclipse.jgit.util.FS; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.Files; import java.nio.file.NotDirectoryException; @@ -47,7 +46,7 @@ class GitDBLocal implements GitDB { /** * Create a new GitDB instance, while initialising a new git repo. * - * @param dbDir the path to instantiate the git repo in + * @param dbDir the path to instantiate the git repo in * @throws IOException if there {@code dbDir} is a file or a non-empty directory */ GitDBLocal(final File dbDir) throws IOException { @@ -61,14 +60,7 @@ class GitDBLocal implements GitDB { * @param repository the Git repository */ GitDBLocal(final Repository repository) { - this.repository = verifyIsBareRepo(repository); - } - - private static Repository verifyIsBareRepo(final Repository repository) { - if (repository.isBare()) { - return repository; - } - throw new InvalidRepositoryException("Not a bare repo", repository.getDirectory().toPath()); + this.repository = repository; } private void validateDbDir(final File dbDir) throws IOException { @@ -92,129 +84,65 @@ class GitDBLocal implements GitDB { private static Repository initRepo(final File dbDir) throws IOException { dbDir.mkdirs(); - final RepositoryCache.FileKey fileKey = RepositoryCache.FileKey.exact(dbDir, FS.DETECTED); - final Repository repository = fileKey.open(false); + final Repository repository = RepositoryCache.FileKey.exact(dbDir, FS.DETECTED).open(false); repository.create(true); + createInitialBranchOnMaster(repository); return repository; } - @Override - public Optional branch(final String name) { - try { - return Optional.ofNullable(repository.findRef(name)) - .map(GitDBBranch::withRef); - } catch (IOException e) { - return Optional.empty(); - } + private static void createInitialBranchOnMaster(final Repository repository) throws IOException { + // create empty file + final ObjectId objectId = insertAnEmptyBlob(repository); + // create tree + final ObjectId treeId = insertTree(repository, objectId); + // create commit + final ObjectId commitId = insertCommit(repository, treeId); + // create branch + writeBranch(repository, commitId, "master"); } - // @Override - // @SneakyThrows - // public T get(Branch branch, Key key, Class type) { - // //branch - // final RefDatabase refDatabase = repository.getRefDatabase(); - // final String branchValue = branch.getValue(); - // final Ref refDatabaseRef = refDatabase.getRef(branchValue); - // final ObjectId commitId = refDatabaseRef.getObjectId(); - // - // final RevCommit revCommit = repository.parseCommit(commitId); - // final RevTree tree = revCommit.getTree(); - // tree.copyTo(System.out); - // - // final ObjectLoader open = repository.getObjectDatabase().open(objectId, Constants.OBJ_TREE); - // final byte[] bytes = open.getBytes(); - // final String s = new String(bytes); - // System.out.println("s = " + s); - // //key - // return null; - // } + private static void writeBranch( + final Repository repository, + final ObjectId commitId, + final String branchName + ) throws IOException { + final Path branchRefPath = + repository.getDirectory().toPath().resolve("refs/heads/" + branchName).toAbsolutePath(); + final byte[] commitIdBytes = commitId.name().getBytes(StandardCharsets.UTF_8); + Files.write(branchRefPath, commitIdBytes); + } - // @Override - // @SneakyThrows - // public String put(Branch branch, Message message, Document document, Author author) { - //// return document.getValue(); - // - // final ObjectInserter objectInserter = repository.newObjectInserter(); - // final ObjectReader objectReader = repository.newObjectReader(); - // final RevWalk revWalk = new RevWalk(repository); - // - // //blob - // System.out.println("document = " + document.getKey()); - // final ObjectId blobId = objectInserter.insert(Constants.OBJ_BLOB, document.getValue().getBytes(UTF_8)); - // //tree - // final TreeFormatter treeFormatter = new TreeFormatter(); - // treeFormatter.append(document.getKey().getValue(), FileMode.REGULAR_FILE, blobId); - // final ObjectId treeId = objectInserter.insert(treeFormatter); - // //commit - // final CommitBuilder commitBuilder = new CommitBuilder(); - // final PersonIdent ident = new PersonIdent(author.getName(), author.getEmail()); - // commitBuilder.setCommitter(ident); - // commitBuilder.setAuthor(ident); - // commitBuilder.setTreeId(treeId); - // commitBuilder.setMessage(message.getValue()); - // //TODO: setParentId() - // final ObjectId commitId = objectInserter.insert(commitBuilder); - // //branch - // final RevCommit revCommit = revWalk.parseCommit(commitId); - // revCommit.getShortMessage(); - // git.branchCreate() - // .setStartPoint(revCommit) - // .setName(branch.getValue()) - // .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.NOTRACK) - // .call(); - // - // //READ - // - // //block - // final String readBlob = new String(objectReader.open(blobId).getBytes()); - // System.out.println("readBlob = " + readBlob); - // final RevBlob revBlob = revWalk.lookupBlob(blobId); - // System.out.println("revBlob = " + revBlob); - // final String blobName = revBlob.name(); - // System.out.println("blobName = " + blobName); - // //tree - // final RevTree revTree = revWalk.lookupTree(treeId); - // System.out.println("revTree = " + revTree); - // final String treeName = revTree.name(); - // System.out.println("treeName = " + treeName); - // //commit - // System.out.println("revCommit= " + revCommit); - // final String commitName = revCommit.getName(); - // System.out.println("commitName= " + commitName); - // //branch - // final Ref branchRef = repository.getRefDatabase().getRef(branch.getValue()); - // System.out.println("branchRef = " + branchRef.getName()); - // - //// final TreeWalk treeWalk = new TreeWalk(repository); - //// treeWalk.addTree(treeId); - //// treeWalk.next(); - //// final String nameString = treeWalk.getNameString(); - //// System.out.println("name = " + nameString); - //// final ObjectId objectId = treeWalk.getObjectId(0); - //// System.out.println("objectId = " + objectId); - // - //// final ObjectLoader openTree = repository.newObjectReader().open(treeId); - //// final int type = openTree.openStream().getType(); - //// final long size = openTree.openStream().getSize(); - //// final String readTree = new String(openTree.getBytes()); - // - //// - //// //commit - //// final CommitBuilder commitBuilder = new CommitBuilder(); - //// commitBuilder.setAuthor(new PersonIdent(author.getName(), author.getEmail())); - //// commitBuilder.setCommitter(new PersonIdent(author.getName(), author.getEmail())); - //// commitBuilder.setMessage(message.getValue()); - //// findParentCommit(branch) - //// .ifPresent(commitBuilder::setParentId); - //// commitBuilder.setTreeId(treeId); - //// final ObjectId commitId = repository.newObjectInserter().insert(commitBuilder); - //// - //// //branch - //// repository.updateRef(branch.getValue()).setNewObjectId(commitId); - //// - //// //get - //// return get(branch, document.getKey()); - // return document.getValue(); - // } + private static ObjectId insertCommit( + final Repository repository, + final ObjectId treeId + ) throws IOException { + final CommitBuilder commitBuilder = new CommitBuilder(); + commitBuilder.setTreeId(treeId); + commitBuilder.setMessage("Initialise GitDB v1"); + final PersonIdent ident = new PersonIdent("GitDB", "pcampbell@kemitix.net"); + commitBuilder.setAuthor(ident); + commitBuilder.setCommitter(ident); + commitBuilder.setParentId(ObjectId.zeroId()); + return repository.getObjectDatabase().newInserter().insert(commitBuilder); + } + + private static ObjectId insertTree( + final Repository repository, + final ObjectId objectId + ) throws IOException { + final TreeFormatter treeFormatter = new TreeFormatter(); + treeFormatter.append("isGitDB", FileMode.REGULAR_FILE, objectId); + return repository.getObjectDatabase().newInserter().insert(treeFormatter); + } + + private static ObjectId insertAnEmptyBlob(final Repository repository) throws IOException { + return repository.getObjectDatabase().newInserter().insert(Constants.OBJ_BLOB, new byte[0]); + } + + @Override + public Optional branch(final String name) throws IOException { + return Optional.ofNullable(repository.findRef(name)) + .map(GitDBBranch::withRef); + } } diff --git a/src/main/java/net/kemitix/gitdb/GitDBRepoNotFoundException.java b/src/main/java/net/kemitix/gitdb/GitDBRepoNotFoundException.java deleted file mode 100644 index 766f5cd..0000000 --- a/src/main/java/net/kemitix/gitdb/GitDBRepoNotFoundException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - The MIT License (MIT) - - Copyright (c) 2018 Paul Campbell - - 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 net.kemitix.gitdb; - -import java.nio.file.Path; - -/** - * Runtime exception thrown when attempting to open to location that is not a GitDB repo. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ -public class GitDBRepoNotFoundException extends RuntimeException { - - /** - * Constructor. - * - * @param message why the GitDB wasn't found - * @param path the location where a GitDB repo was not found - */ - GitDBRepoNotFoundException(final String message, final Path path) { - super(String.format("GitDB repo not found: %s: %s", message, path)); - } -} diff --git a/src/main/java/net/kemitix/gitdb/InvalidRepositoryException.java b/src/main/java/net/kemitix/gitdb/InvalidRepositoryException.java index 128392f..3c127b6 100644 --- a/src/main/java/net/kemitix/gitdb/InvalidRepositoryException.java +++ b/src/main/java/net/kemitix/gitdb/InvalidRepositoryException.java @@ -21,6 +21,7 @@ package net.kemitix.gitdb; +import java.io.IOException; import java.nio.file.Path; /** @@ -34,9 +35,34 @@ public class InvalidRepositoryException extends RuntimeException { * Constructor. * * @param message the reason the repo is invalid - * @param path the location of the repo + * @param path the location of the repo */ - public InvalidRepositoryException(final String message, final Path path) { - super(String.format("Invalid GitDB repo: %s [%s]", message, path)); + public InvalidRepositoryException( + final String message, + final Path path + ) { + super(message(message, path)); + } + + /** + * Constructor. + * + * @param message the reason the repo is invalid + * @param path the location of the repo + * @param cause the cause + */ + public InvalidRepositoryException( + final String message, + final Path path, + final IOException cause + ) { + super(message(message, path), cause); + } + + private static String message( + final String message, + final Path path + ) { + return String.format("Invalid GitDB repo: %s [%s]", message, path); } } diff --git a/src/main/java/net/kemitix/gitdb/UnexpectedGitDbException.java b/src/main/java/net/kemitix/gitdb/UnexpectedGitDbException.java deleted file mode 100644 index a6a32d8..0000000 --- a/src/main/java/net/kemitix/gitdb/UnexpectedGitDbException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - The MIT License (MIT) - - Copyright (c) 2018 Paul Campbell - - 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 net.kemitix.gitdb; - -/** - * Unchecked exception thrown when JGit throws a an unexpected exception. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ -public class UnexpectedGitDbException extends RuntimeException { - - /** - * Constructs an instance of this class. - * - * @param message the detail message. The detail message is saved for - * later retrieval by the {@link #getMessage()} method. - * @param cause the cause (which is saved for later retrieval by the - * {@link #getCause()} method). (A null value is - * permitted, and indicates that the cause is nonexistent or - * unknown.) - */ - public UnexpectedGitDbException(final String message, final Throwable cause) { - super(message, cause); - } - -} diff --git a/src/test/java/net/kemitix/gitdb/GitDBTest.java b/src/test/java/net/kemitix/gitdb/GitDBTest.java index 4d51067..325769a 100644 --- a/src/test/java/net/kemitix/gitdb/GitDBTest.java +++ b/src/test/java/net/kemitix/gitdb/GitDBTest.java @@ -2,7 +2,6 @@ package net.kemitix.gitdb; import org.assertj.core.api.WithAssertions; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.InitCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; @@ -10,13 +9,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; -import java.io.IOException; +import java.io.*; import java.nio.file.*; import java.util.Optional; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class GitDBTest implements WithAssertions { @@ -115,7 +111,7 @@ class GitDBTest implements WithAssertions { //given final Path dir = fileExists(); //then - assertThatExceptionOfType(GitDBRepoNotFoundException.class) + assertThatExceptionOfType(InvalidRepositoryException.class) .isThrownBy(() -> GitDB.openLocal(dir)) .withMessageContaining(dir.toString()); } @@ -126,7 +122,7 @@ class GitDBTest implements WithAssertions { //given final Path dir = dirDoesNotExist(); //then - assertThatExceptionOfType(GitDBRepoNotFoundException.class) + assertThatExceptionOfType(InvalidRepositoryException.class) .isThrownBy(() -> GitDB.openLocal(dir)) .withMessageContaining(dir.toString()); } @@ -170,20 +166,30 @@ class GitDBTest implements WithAssertions { // Given a valid GitDb handle // When select a branch that doesn't exist then an empty Optional is returned @Test - void selectBranch_branchNotExist_thenEmptyOptional() throws IOException { + void selectBranch_whenBranchNotExist_thenEmptyOptional() throws IOException { //given - final GitDB gitDb = newGitDBRepo(); + final GitDB gitDb = newGitDBRepo(dirDoesNotExist()); //when final Optional branch = gitDb.branch("unknown"); //then assertThat(branch).isEmpty(); } - private GitDB newGitDBRepo() throws IOException { - return GitDB.initLocal(dirDoesNotExist()); + private GitDB newGitDBRepo(final Path dbDir) throws IOException { + return GitDB.initLocal(dbDir); } // When select a valid branch then a GitDbBranch is returned + @Test + void selectBranch_branchExists_thenReturnBranch() throws IOException { + //given + final Path dbDir = dirDoesNotExist(); + final GitDB gitDb = newGitDBRepo(dbDir); + //when + final Optional branch = gitDb.branch("master"); + //then + assertThat(branch).as("Branch master exists").isNotEmpty(); + } // Given a valid GitDbBranch handle // When getting a key that does not exist then return an empty Optional @@ -242,4 +248,15 @@ class GitDBTest implements WithAssertions { // // .map(Path::toFile) // // .forEach(File::delete); // } + + private static void tree(final Path dbDir, final PrintStream out) throws IOException { + final Process treeProcess = new ProcessBuilder("tree", dbDir.toString()).start(); + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(treeProcess.getInputStream()))) { + String line; + while (null != (line = reader.readLine())) { + out.println("line = " + line); + } + } + } + }