package tinyvm class HashCollisionException(hash: String) : Exception("Different object types with identical hash '$hash'") class CommitTimeComparator : Comparator { override fun compare( o1: Commit, o2: Commit, ): Int = (o1.timestamp.epochSecond - o2.timestamp.epochSecond).toInt() } class Repository { private val commits = sortedSetOf(CommitTimeComparator()) // Store all objects in one map like git does. This would simplify the data persistence implementation (if there was // one) and allows for other objects to be added in the future without modifying the data persistence implementation // at all. private val objects = mutableMapOf() /** * (Deep) get or put a commit object into the repository. * This will also get or put all child trees and blobs. */ fun commit(commit: Commit): Commit { val hash = commit.hash() val obj = findObject(hash) if (obj != null) return obj val newCommit = Commit( tree = addTree(commit.tree), author = commit.author, message = commit.message, timestamp = commit.timestamp, ) objects[hash] = newCommit commits.add(newCommit) return newCommit } /** * Get a commit by its hash. */ fun getCommit(hash: String): Commit? = findObject(hash) /** * List all commits. */ fun listCommits(): List = commits.toList() /** * Find commit. */ fun findCommit(predicate: (Commit) -> Boolean): Commit? = commits.find(predicate) /** * Dump repository objects */ fun dumpObjects(): Map = objects /** * (Deep) get or put a tree object into the repository. * This will also get or put all child trees and blobs. */ private fun addTree(tree: Tree): Tree { val hash = tree.hash() val obj = findObject(hash) if (obj != null) return obj val newTree = Tree( tree.nodes.map { (name, node) -> when (node) { is Tree -> name to addTree(node) is Blob -> name to addObject(node) } }.toMap(), ) objects[hash] = newTree return tree } /** * (Shallow) get or put an object into the repository. */ private inline fun addObject(obj: T): T = objects.getOrPut(obj.hash()) { obj } as? T ?: throw HashCollisionException(obj.hash()) /** * Find an object in the repository by its hash. */ private inline fun findObject(hash: String): T? = objects[hash]?.let { it as? T ?: throw HashCollisionException(hash) } }