diff --git a/src/main/java/net/kemitix/gitdb/GitDBBranch.java b/src/main/java/net/kemitix/gitdb/GitDBBranch.java index 7feb860..823bd6e 100644 --- a/src/main/java/net/kemitix/gitdb/GitDBBranch.java +++ b/src/main/java/net/kemitix/gitdb/GitDBBranch.java @@ -28,7 +28,6 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import java.io.*; -import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.function.Function; @@ -58,8 +57,8 @@ public class GitDBBranch { /** * Initialise the creation of new GitDBBranch instances. * - * @param repository the Git Repository - * @param userName the user name to record against changes + * @param repository the Git Repository + * @param userName the user name to record against changes * @param userEmailAddress the user's email address to record against changes * @return a Function for creating a GitDBBranch when supplied with a Ref for a branch */ @@ -68,7 +67,7 @@ public class GitDBBranch { final String userName, final String userEmailAddress ) { - return ref -> select(ref, GitDBRepo.in(repository), userName, userEmailAddress); + return ref -> select(ref, new GitDBRepo(repository), userName, userEmailAddress); } /** @@ -91,11 +90,14 @@ public class GitDBBranch { * @throws IOException if there was an error writing the value */ public GitDBBranch put(final String key, final String value) throws IOException { - final ObjectId objectId = insertBlob(value.getBytes(StandardCharsets.UTF_8)); - final ObjectId treeId = insertTree(KEY_PREFIX + key, objectId); - final String commitMessage = String.format("Add key [%s] = [%s]", key, value); - final ObjectId commitId = insertCommit(treeId, commitMessage); - return updateBranch(commitId); + final ObjectId newTree = gitDBRepo.writeValue(branchRef, KEY_PREFIX + key, value); + final Ref newBranch = + gitDBRepo.writeCommit(branchRef, newTree, commitMessageForAdd(key, value), userName, userEmailAddress); + return select(newBranch, gitDBRepo, userName, userEmailAddress); + } + + private String commitMessageForAdd(final String key, final String value) { + return String.format("Add key [%s] = [%s]", key, value); } /** @@ -108,21 +110,4 @@ public class GitDBBranch { return this; } - private ObjectId insertBlob(final byte[] blob) throws IOException { - return gitDBRepo.insertBlob(blob); - } - - private ObjectId insertTree(final String key, final ObjectId valueId) throws IOException { - return gitDBRepo.insertTree(branchRef, key, valueId); - } - - private ObjectId insertCommit(final ObjectId treeId, final String message) throws IOException { - final ObjectId headCommitId = branchRef.getObjectId(); - return gitDBRepo.insertCommit(treeId, message, userName, userEmailAddress, headCommitId); - } - - private GitDBBranch updateBranch(final ObjectId commitId) throws IOException { - final Ref updatedRef = gitDBRepo.writeHead(branchRef.getName(), commitId); - return select(updatedRef, gitDBRepo, userName, userEmailAddress); - } } diff --git a/src/main/java/net/kemitix/gitdb/GitDBRepo.java b/src/main/java/net/kemitix/gitdb/GitDBRepo.java index d5ae37c..8b0e6ed 100644 --- a/src/main/java/net/kemitix/gitdb/GitDBRepo.java +++ b/src/main/java/net/kemitix/gitdb/GitDBRepo.java @@ -21,12 +21,9 @@ package net.kemitix.gitdb; -import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import lombok.val; import org.eclipse.jgit.lib.*; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.filter.PathFilter; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -39,21 +36,11 @@ import java.util.*; * * @author Paul Campbell (pcampbell@kemitix.net) */ -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@RequiredArgsConstructor class GitDBRepo { private final Repository repository; - /** - * Create a GitDBRepo wrapper for the Repository. - * - * @param repository the repository to wrap - * @return the GitDBRepo wrapper - */ - public static GitDBRepo in(final Repository repository) { - return new GitDBRepo(repository); - } - /** * Insert a blob into the store, returning its unique id. * @@ -98,15 +85,6 @@ class GitDBRepo { return writeTree(key, valueId, treeFormatterForBranch(branchRef)); } - private ObjectId writeTree( - final String key, - final ObjectId valueId, - final TreeFormatter treeFormatter - ) throws IOException { - treeFormatter.append(key, FileMode.REGULAR_FILE, valueId); - return repository.getObjectDatabase().newInserter().insert(treeFormatter); - } - /** * Insert a commit into the store, returning its unique id. * @@ -135,15 +113,7 @@ class GitDBRepo { return repository.getObjectDatabase().newInserter().insert(commitBuilder); } - /** - * Updates the branch to point to the new commit. - * - * @param branchName the branch to update - * @param commitId the commit to point the branch at - * @return the Ref of the updated branch - * @throws IOException if there was an error writing the branch - */ - Ref writeHead( + private Ref writeHead( final String branchName, final ObjectId commitId ) throws IOException { @@ -169,33 +139,68 @@ class GitDBRepo { final Ref branchRef, final String key ) throws IOException { - try (TreeWalk treeWalk = getTreeWalk(branchRef)) { - treeWalk.setFilter(PathFilter.create(key)); - if (treeWalk.next()) { - return Optional.of(new String( - repository.open(treeWalk.getObjectId(0), Constants.OBJ_BLOB).getBytes())); - } + val blob = new GitTreeReader(repository) + .treeFilter(key) + .stream(branchRef) + .findFirst(); + if (blob.isPresent()) { + return Optional.of(blob.get().blobAsString()); } return Optional.empty(); } + private ObjectId writeTree( + final String key, + final ObjectId valueId, + final TreeFormatter treeFormatter + ) throws IOException { + treeFormatter.append(key, FileMode.REGULAR_FILE, valueId); + return repository.getObjectDatabase().newInserter().insert(treeFormatter); + } + private TreeFormatter treeFormatterForBranch(final Ref branchRef) throws IOException { final TreeFormatter treeFormatter = new TreeFormatter(); - try (TreeWalk treeWalk = getTreeWalk(branchRef)) { - while (treeWalk.next()) { - treeFormatter.append( - treeWalk.getNameString(), - new RevWalk(repository).lookupBlob(treeWalk.getObjectId(0))); - } - } + final GitTreeReader gitTreeReader = new GitTreeReader(repository); + gitTreeReader.stream(branchRef) + .forEach(item -> treeFormatter.append(item.getName(), item.getRevBlob())); return treeFormatter; } - private TreeWalk getTreeWalk(final Ref branchRef) throws IOException { - final TreeWalk treeWalk = new TreeWalk(repository); - treeWalk.addTree(new RevWalk(repository).parseCommit(branchRef.getObjectId()).getTree()); - treeWalk.setRecursive(false); - return treeWalk; + /** + * Add the key/value to the repo, returning the tree containing the update. + * + *

N.B. this creates a tree that has not been committed remains unaware of the update.

+ * + * @param branchRef the branch to start from + * @param key the key to place the value under + * @param value the value (must be Serializable) + * @return the id of the updated tree containing the update + * @throws IOException if there was an error writing the value + */ + ObjectId writeValue(final Ref branchRef, final String key, final String value) throws IOException { + final ObjectId blob = insertBlob(value.getBytes(StandardCharsets.UTF_8)); + return insertTree(branchRef, key, blob); } + /** + * Updates the branch to point to the new commit. + * + * @param branchRef the branch to update + * @param tree the tree to commit onto the branch + * @param message the commit message + * @param userName the user name + * @param userEmailAddress the use email address + * @return the Ref of the updated branch + * @throws IOException if there was an error writing the branch + */ + Ref writeCommit( + final Ref branchRef, + final ObjectId tree, + final String message, + final String userName, + final String userEmailAddress + ) throws IOException { + final ObjectId commitId = insertCommit(tree, message, userName, userEmailAddress, branchRef.getObjectId()); + return writeHead(branchRef.getName(), commitId); + } } diff --git a/src/main/java/net/kemitix/gitdb/GitTreeReader.java b/src/main/java/net/kemitix/gitdb/GitTreeReader.java new file mode 100644 index 0000000..f55a8af --- /dev/null +++ b/src/main/java/net/kemitix/gitdb/GitTreeReader.java @@ -0,0 +1,106 @@ +/* + 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 lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +import java.io.IOException; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Reads the entries in a Git Tree object. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +@RequiredArgsConstructor +class GitTreeReader { + + private final Repository repository; + + private TreeFilter treeFilter; + + /** + * Opens a stream of entries found on the branch. + * + * @param branchRef the branch to read + * @return a stream of key/value pairs as NamedRevBlobs + * @throws IOException if there is an error reading the commit or walking the tree + */ + Stream stream(final Ref branchRef) throws IOException { + final TreeWalk treeWalk = new TreeWalk(repository); + treeWalk.addTree(new RevWalk(repository).parseCommit(branchRef.getObjectId()).getTree()); + treeWalk.setRecursive(false); + Optional.ofNullable(treeFilter) + .ifPresent(treeWalk::setFilter); + final Stream.Builder builder = Stream.builder(); + while (treeWalk.next()) { + builder.add(new NamedRevBlob( + treeWalk.getNameString(), + new RevWalk(repository).lookupBlob(treeWalk.getObjectId(0)))); + } + return builder.build(); + } + + /** + * Sets a path filter to limit the stream by. + * + * @param path the path to filter by + * @return the GitTreeReader + */ + GitTreeReader treeFilter(final String path) { + this.treeFilter = PathFilter.create(path); + return this; + } + + /** + * Represents the key/value pairs read from the tree. + */ + @Getter + @RequiredArgsConstructor + class NamedRevBlob { + + private final String name; + private final RevBlob revBlob; + + /** + * Converts the blob to a String. + * + * @return a string + * @throws IOException of there was an error reading the blob + */ + String blobAsString() throws IOException { + return new String(repository.open(revBlob.getId(), Constants.OBJ_BLOB).getBytes()); + } + + } + +} diff --git a/src/main/java/net/kemitix/gitdb/InitGitDBRepo.java b/src/main/java/net/kemitix/gitdb/InitGitDBRepo.java index e63ce37..4569e3a 100644 --- a/src/main/java/net/kemitix/gitdb/InitGitDBRepo.java +++ b/src/main/java/net/kemitix/gitdb/InitGitDBRepo.java @@ -64,7 +64,7 @@ class InitGitDBRepo { } private void createInitialBranchOnMaster(final Repository repository) throws IOException { - final GitDBRepo repo = GitDBRepo.in(repository); + final GitDBRepo repo = new GitDBRepo(repository); final ObjectId objectId = repo.insertBlob(new byte[0]); final ObjectId treeId = repo.insertNewTree(IS_GIT_DB, objectId); final ObjectId commitId = repo.insertCommit(treeId, INIT_MESSAGE, INIT_USER, INIT_EMAIL, ObjectId.zeroId());