From 02b5ba34bcfc5561c7855a10a0f5cce9beba62f4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 12 Jun 2018 22:32:06 +0100 Subject: [PATCH] When putting a key/value pair then a GitDbBranch is returned --- src/main/java/net/kemitix/gitdb/GitDB.java | 16 +- .../java/net/kemitix/gitdb/GitDBBranch.java | 73 ++++++- .../java/net/kemitix/gitdb/GitDBLocal.java | 92 ++++---- .../java/net/kemitix/gitdb/GitDBRepo.java | 200 ++++++++++++++++++ .../java/net/kemitix/gitdb/GitDBTest.java | 66 +++--- 5 files changed, 357 insertions(+), 90 deletions(-) create mode 100644 src/main/java/net/kemitix/gitdb/GitDBRepo.java diff --git a/src/main/java/net/kemitix/gitdb/GitDB.java b/src/main/java/net/kemitix/gitdb/GitDB.java index 1182da0..83f6fc6 100644 --- a/src/main/java/net/kemitix/gitdb/GitDB.java +++ b/src/main/java/net/kemitix/gitdb/GitDB.java @@ -38,28 +38,32 @@ public interface GitDB { /** * Initialise a new local gitdb. * - * @param dbDir the path to initialise the local repo in + * @param dbDir the path to initialise the local repo in + * @param userName the user name + * @param userEmailAddress the user email address * @return a GitDB instance for the created local gitdb * @throws IOException if there {@code dbDir} is a file or a non-empty directory */ - static GitDB initLocal(final Path dbDir) throws IOException { + static GitDB initLocal(final Path dbDir, final String userName, final String userEmailAddress) throws IOException { return new GitDBLocal( - dbDir.toFile() + dbDir.toFile(), userName, userEmailAddress ); } /** * Open an existing local gitdb. * - * @param dbDir the path to open as a local repo + * @param dbDir the path to open as a local repo + * @param userName the user name + * @param userEmailAddress the user email address * @return a GitDB instance for the local gitdb */ - static GitDBLocal openLocal(final Path dbDir) { + static GitDBLocal openLocal(final Path dbDir, final String userName, final String userEmailAddress) { try { return Optional.of(Git.open(dbDir.toFile())) .map(Git::getRepository) .filter(Repository::isBare) - .map(GitDBLocal::new) + .map(repository -> new GitDBLocal(repository, userName, userEmailAddress)) .orElseThrow(() -> new InvalidRepositoryException("Not a bare repo", dbDir)); } catch (IOException e) { throw new InvalidRepositoryException("Error opening repository", dbDir, e); diff --git a/src/main/java/net/kemitix/gitdb/GitDBBranch.java b/src/main/java/net/kemitix/gitdb/GitDBBranch.java index 6862eaf..38e3d79 100644 --- a/src/main/java/net/kemitix/gitdb/GitDBBranch.java +++ b/src/main/java/net/kemitix/gitdb/GitDBBranch.java @@ -23,8 +23,12 @@ package net.kemitix.gitdb; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; +import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.Optional; /** @@ -35,27 +39,80 @@ import java.util.Optional; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class GitDBBranch { - private final Ref ref; + private static final String KEY_PREFIX = "key:"; + private final Ref branchRef; + private final GitDBRepo gitDBRepo; + private final String userName; + private final String userEmailAddress; /** * Create a new instance of GitDBBranch for the Ref. * - * @param ref the Ref + * @param ref the Ref + * @param gitDBRepo the GitDBRepo + * @param userName the user name + * @param userEmailAddress the user email address * @return a GitDBBranch */ - public static GitDBBranch withRef(final Ref ref) { - return new GitDBBranch(ref); + public static GitDBBranch withRef( + final Ref ref, + final GitDBRepo gitDBRepo, + final String userName, + final String userEmailAddress + ) { + return new GitDBBranch(ref, gitDBRepo, userName, userEmailAddress); } /** * Lookup a value for the key. * * @param key the key to lookup - * @param valueClass the expected class of the value - * @param the Class of the value * @return an Optional containing the value, if it exists, or empty if not + * @throws IOException if there was an error reading the value */ - public Optional get(final String key, final Class valueClass) { - return Optional.empty(); + public Optional get(final String key) throws IOException { + return gitDBRepo.readValue(branchRef, KEY_PREFIX + key) + .map(String::new); } + + /** + * Put a value into the store for the key. + * + * @param key the key to place the value under + * @param value the value (must be Serializable) + * @return an updated branch containing the new key/value + * @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); + } + + 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 { + return gitDBRepo.insertCommit(treeId, message, userName, userEmailAddress, head()); + } + + private AnyObjectId head() { + return branchRef.getObjectId(); + } + + private GitDBBranch updateBranch(final ObjectId commitId) throws IOException { + final Ref updatedRef = gitDBRepo.writeHead(branchRef.getName(), commitId); + return GitDBBranch.withRef(updatedRef, gitDBRepo, userName, userEmailAddress); + } + } diff --git a/src/main/java/net/kemitix/gitdb/GitDBLocal.java b/src/main/java/net/kemitix/gitdb/GitDBLocal.java index f80cebf..ae12c83 100644 --- a/src/main/java/net/kemitix/gitdb/GitDBLocal.java +++ b/src/main/java/net/kemitix/gitdb/GitDBLocal.java @@ -42,32 +42,44 @@ import java.util.Optional; class GitDBLocal implements GitDB { private final Repository repository; + private final String userName; + private final String userEmailAddress; /** * 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 + * @param userName the user name + * @param userEmailAddress the user email address * @throws IOException if there {@code dbDir} is a file or a non-empty directory */ - GitDBLocal(final File dbDir) throws IOException { - validateDbDir(dbDir); - this.repository = initRepo(dbDir); + GitDBLocal( + final File dbDir, + final String userName, + final String userEmailAddress + ) throws IOException { + this(GitDBLocal.initRepo(validDbDir(dbDir)), userName, userEmailAddress); } /** * Create a new GitDB instance using the Git repo. * - * @param repository the Git repository + * @param repository the Git repository + * @param userName the user name + * @param userEmailAddress the user email address */ - GitDBLocal(final Repository repository) { + GitDBLocal(final Repository repository, final String userName, final String userEmailAddress) { this.repository = repository; + this.userName = userName; + this.userEmailAddress = userEmailAddress; } - private void validateDbDir(final File dbDir) throws IOException { + private static File validDbDir(final File dbDir) throws IOException { verifyIsNotAFile(dbDir); if (dbDir.exists()) { verifyIsEmpty(dbDir); } + return dbDir; } private static void verifyIsEmpty(final File dbDir) throws IOException { @@ -82,6 +94,12 @@ class GitDBLocal implements GitDB { } } + @Override + public Optional branch(final String name) throws IOException { + return Optional.ofNullable(repository.findRef(name)) + .map(ref -> GitDBBranch.withRef(ref, GitDBRepo.in(repository), userName, userEmailAddress)); + } + private static Repository initRepo(final File dbDir) throws IOException { dbDir.mkdirs(); final Repository repository = RepositoryCache.FileKey.exact(dbDir, FS.DETECTED).open(false); @@ -91,58 +109,30 @@ class GitDBLocal implements GitDB { } 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"); + final GitDBRepo repo = GitDBRepo.in(repository); + final ObjectId objectId = repo.insertBlob(new byte[0]); + final ObjectId treeId = repo.insertNewTree("isGitDB", objectId); + final ObjectId commitId = repo.insertCommit( + treeId, + "Initialise GitDB v1", + "GitDB", + "pcampbell@kemitix.net", + ObjectId.zeroId()); + createBranch(repository, commitId, "master"); } - private static void writeBranch( + private static void createBranch( final Repository repository, final ObjectId commitId, final String branchName ) throws IOException { - final Path branchRefPath = - repository.getDirectory().toPath().resolve("refs/heads/" + branchName).toAbsolutePath(); + final Path branchRefPath = repository + .getDirectory() + .toPath() + .resolve("refs/heads/" + branchName) + .toAbsolutePath(); final byte[] commitIdBytes = commitId.name().getBytes(StandardCharsets.UTF_8); Files.write(branchRefPath, commitIdBytes); } - 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/GitDBRepo.java b/src/main/java/net/kemitix/gitdb/GitDBRepo.java new file mode 100644 index 0000000..a9d4c90 --- /dev/null +++ b/src/main/java/net/kemitix/gitdb/GitDBRepo.java @@ -0,0 +1,200 @@ +/* + 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.AccessLevel; +import lombok.RequiredArgsConstructor; +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; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +/** + * Wrapper for interacting with the GitDB Repository. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +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. + * + * @param blob content of the blob + * @return the id of the blob + * @throws IOException the blob could not be stored + */ + ObjectId insertBlob(final byte[] blob) throws IOException { + return repository.getObjectDatabase().newInserter().insert(Constants.OBJ_BLOB, blob); + } + + /** + * Insert a new, empty tree into the store, returning its unique id. + * + * @param key the key to insert + * @param valueId id of the value + * @return the id of the inserted tree + * @throws IOException the tree could not be stored + */ + ObjectId insertNewTree( + final String key, + final ObjectId valueId + ) throws IOException { + final TreeFormatter treeFormatter = new TreeFormatter(); + return writeTree(key, valueId, treeFormatter); + } + + /** + * Insert a tree into the store, copying the exiting tree from the branch, returning its new unique id. + * + * @param branchRef the branch to copy the tree from + * @param key the key to insert + * @param valueId id of the value + * @return the id of the inserted tree + * @throws IOException the tree could not be stored + */ + ObjectId insertTree( + final Ref branchRef, + final String key, + final ObjectId valueId + ) throws IOException { + 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. + * + * @param treeId id of the tree + * @param message the message + * @param userName the user name + * @param userEmailAddress the user email address + * @param parent the commit to link to as parent + * @return the id of the commit + * @throws IOException the commit could not be stored + */ + ObjectId insertCommit( + final ObjectId treeId, + final String message, + final String userName, + final String userEmailAddress, + final AnyObjectId parent + ) throws IOException { + final CommitBuilder commitBuilder = new CommitBuilder(); + commitBuilder.setTreeId(treeId); + commitBuilder.setMessage(message); + final PersonIdent ident = new PersonIdent(userName, userEmailAddress); + commitBuilder.setAuthor(ident); + commitBuilder.setCommitter(ident); + commitBuilder.setParentId(parent); + 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( + final String branchName, + final ObjectId commitId + ) throws IOException { + final Path branchRefPath = repository + .getDirectory() + .toPath() + .resolve(branchName) + .toAbsolutePath(); + final byte[] commitIdBytes = commitId.name().getBytes(StandardCharsets.UTF_8); + Files.write(branchRefPath, commitIdBytes); + return repository.findRef(branchName); + } + + /** + * Reads a value from the branch with the given key. + * + * @param branchRef the branch to select from + * @param key the key to get the value for + * @return an Optional containing the value if found, or empty + * @throws IOException if there was an error reading the value + */ + Optional readValue( + final Ref branchRef, + final String key + ) throws IOException { + try (TreeWalk treeWalk = getTreeWalk(branchRef)) { + treeWalk.setFilter(PathFilter.create(key)); + if (treeWalk.next()) { + return Optional.of(repository.open(treeWalk.getObjectId(0), Constants.OBJ_BLOB).getBytes()); + } + } + return Optional.empty(); + } + + 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))); + } + } + 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; + } + +} diff --git a/src/test/java/net/kemitix/gitdb/GitDBTest.java b/src/test/java/net/kemitix/gitdb/GitDBTest.java index 37707ac..df05a74 100644 --- a/src/test/java/net/kemitix/gitdb/GitDBTest.java +++ b/src/test/java/net/kemitix/gitdb/GitDBTest.java @@ -1,11 +1,11 @@ package net.kemitix.gitdb; -import org.assertj.core.api.Assumptions; import org.assertj.core.api.WithAssertions; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -19,10 +19,8 @@ import static org.assertj.core.api.Assumptions.assumeThat; @ExtendWith(MockitoExtension.class) class GitDBTest implements WithAssertions { -// private final Branch master = Branch.name("master"); -// private final Message message = Message.message(UUID.randomUUID().toString()); -// private final Key key = Key.name(UUID.randomUUID().toString()); -// private final Author author = Author.name("junit", "gitdb@kemitix.net"); + private String userName = "user name"; + private String userEmailAddress = "user@email.com"; // When initialising a repo in a dir that doesn't exist then a bare repo is created @Test @@ -30,7 +28,7 @@ class GitDBTest implements WithAssertions { //given final Path dir = dirDoesNotExist(); //when - final GitDB gitDB = GitDB.initLocal(dir); + final GitDB gitDB = GitDB.initLocal(dir, userName, userEmailAddress); //then assertThat(gitDB).isNotNull(); assertThatIsBareRepo(dir); @@ -57,7 +55,7 @@ class GitDBTest implements WithAssertions { final Path dir = fileExists(); //then assertThatExceptionOfType(NotDirectoryException.class) - .isThrownBy(() -> GitDB.initLocal(dir)) + .isThrownBy(() -> GitDB.initLocal(dir, userName, userEmailAddress)) .withMessageContaining(dir.toString()); } @@ -73,7 +71,7 @@ class GitDBTest implements WithAssertions { filesExistIn(dir); //then assertThatExceptionOfType(DirectoryNotEmptyException.class) - .isThrownBy(() -> GitDB.initLocal(dir)) + .isThrownBy(() -> GitDB.initLocal(dir, userName, userEmailAddress)) .withMessageContaining(dir.toString()); } @@ -91,7 +89,7 @@ class GitDBTest implements WithAssertions { //given final Path dir = dirExists(); //when - final GitDB gitDB = GitDB.initLocal(dir); + final GitDB gitDB = GitDB.initLocal(dir, userName, userEmailAddress); //then assertThat(gitDB).isNotNull(); assertThatIsBareRepo(dir); @@ -104,7 +102,7 @@ class GitDBTest implements WithAssertions { final Path dir = dirExists(); //then assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir)) + .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) .withMessageContaining(dir.toString()); } @@ -115,7 +113,7 @@ class GitDBTest implements WithAssertions { final Path dir = fileExists(); //then assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir)) + .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) .withMessageContaining(dir.toString()); } @@ -126,7 +124,7 @@ class GitDBTest implements WithAssertions { final Path dir = dirDoesNotExist(); //then assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir)) + .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) .withMessageContaining(dir.toString()); } @@ -137,7 +135,7 @@ class GitDBTest implements WithAssertions { final Path dir = nonBareRepo(); //then assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir)) + .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) .withMessageContaining("Invalid GitDB repo") .withMessageContaining("Not a bare repo") .withMessageContaining(dir.toString()); @@ -155,14 +153,14 @@ class GitDBTest implements WithAssertions { //given final Path dir = gitDBRepoPath(); //when - final GitDBLocal gitDB = GitDB.openLocal(dir); + final GitDBLocal gitDB = GitDB.openLocal(dir, userName, userEmailAddress); //then assertThat(gitDB).isNotNull(); } private Path gitDBRepoPath() throws IOException { final Path dbDir = dirDoesNotExist(); - GitDB.initLocal(dbDir); + GitDB.initLocal(dbDir, userName, userEmailAddress); return dbDir; } @@ -171,15 +169,15 @@ class GitDBTest implements WithAssertions { @Test void selectBranch_whenBranchNotExist_thenEmptyOptional() throws IOException { //given - final GitDB gitDb = newGitDBRepo(dirDoesNotExist()); + final GitDB gitDb = gitDB(dirDoesNotExist()); //when final Optional branch = gitDb.branch("unknown"); //then assertThat(branch).isEmpty(); } - private GitDB newGitDBRepo(final Path dbDir) throws IOException { - return GitDB.initLocal(dbDir); + private GitDB gitDB(final Path dbDir) throws IOException { + return GitDB.initLocal(dbDir, userName, userEmailAddress); } // When select a valid branch then a GitDbBranch is returned @@ -187,7 +185,7 @@ class GitDBTest implements WithAssertions { void selectBranch_branchExists_thenReturnBranch() throws IOException { //given final Path dbDir = dirDoesNotExist(); - final GitDB gitDb = newGitDBRepo(dbDir); + final GitDB gitDb = gitDB(dbDir); //when final Optional branch = gitDb.branch("master"); //then @@ -197,19 +195,37 @@ class GitDBTest implements WithAssertions { // Given a valid GitDbBranch handle // When getting a key that does not exist then return an empty Optional @Test - void getKey_whenKeyNotExist_thenReturnEmptyOptional() throws IOException { + void getKey_whenKeyNotExist_thenReturnEmptyOptional() throws IOException, ClassNotFoundException { //given - final GitDB gitDB = newGitDBRepo(dirDoesNotExist()); - final Optional branchOptional = gitDB.branch("master"); - assumeThat(branchOptional).isNotEmpty(); - final GitDBBranch master = branchOptional.get(); + final GitDBBranch branch = gitDBBranch(); //when - final Optional value = master.get("unknown", String.class); + final Optional value = branch.get("unknown"); //then assertThat(value).isEmpty(); } + private GitDBBranch gitDBBranch() throws IOException { + final GitDB gitDB = gitDB(dirDoesNotExist()); + final Optional branchOptional = gitDB.branch("master"); + assumeThat(branchOptional).isNotEmpty(); + return branchOptional.get(); + } + // When putting a key/value pair then a GitDbBranch is returned + @Test + void putValue_thenReturnUpdatedGitDBBranch() throws IOException, ClassNotFoundException { + //given + final GitDBBranch originalBranch = gitDBBranch(); + //when + final GitDBBranch updatedBranch = originalBranch.put("key-name", "value"); + //then + assertThat(updatedBranch).isNotNull(); + assertThat(updatedBranch).isNotSameAs(originalBranch); + final Optional optional = updatedBranch.get("key-name"); + assertThat(optional).contains("value"); + //assertThat(originalBranch.get("key", String.class)).isEmpty(); + } + // When getting a key that does exist then the value is returned inside an Optional // When removing a key that does not exist then the GitDbBranch is returned // When removing a key that does exist then a GitDbBranch is returned