Refactoring: extract GitTreeReader

This commit is contained in:
Paul Campbell 2018-06-14 18:42:09 +01:00
parent ffc2b56b05
commit c57a17b937
4 changed files with 174 additions and 78 deletions

View file

@ -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);
}
}

View file

@ -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.
*
* <p>N.B. this creates a tree that has not been committed remains unaware of the update.</p>
*
* @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);
}
}

View file

@ -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<NamedRevBlob> 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<NamedRevBlob> 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());
}
}
}

View file

@ -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());