3 Commits

Author SHA1 Message Date
7ba3d69e9f Lint and test job
All checks were successful
Main Workflow / Lint and test library (pull_request) Successful in 9m31s
2023-12-01 20:17:28 +00:00
d5deef82c1 Linting 2023-12-01 20:16:08 +00:00
6c948c9630 Initial library implementations and tests 2023-12-01 19:24:26 +00:00
8 changed files with 26 additions and 190 deletions

View File

@@ -1,4 +1,4 @@
name: Test Workflow name: Main Workflow
on: on:
pull_request: pull_request:
branches: branches:
@@ -16,7 +16,7 @@ jobs:
- name: Set up Java - name: Set up Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: adopt distribution: 'adopt'
java-version: 17 java-version: 17
- name: Verify Gradle wrapper - name: Verify Gradle wrapper

View File

@@ -1,40 +0,0 @@
name: Publish Workflow
on:
push:
tags:
- v*
jobs:
publish:
name: Publish library
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: 17
- name: Verify Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run checks
run: ./gradlew check
- name: Parse parameters
id: parse
run: |
export VERSION="$(echo ${{ github.ref_name }} | cut -c2-)"
echo "Parsed version: '$VERSION'"
echo "tinyvm_version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Publish to Gitea package repository
env:
TINYVM_VERSION: ${{ steps.parse.outputs.tinyvm_version }}
GITEA_USERNAME: ${{ github.repository_owner }}
GITEA_TOKEN: ${{ secrets.deploy_token }}
run: ./gradlew publishAllPublicationsToGiteaRepository

View File

@@ -10,25 +10,6 @@ The package is named `tinyvm` for 'tiny version manager'.
Since this is an internship application project, I have assumed that a minimal usage of external libraries is preferred, Since this is an internship application project, I have assumed that a minimal usage of external libraries is preferred,
so that a greater technical understanding can be demonstrated. so that a greater technical understanding can be demonstrated.
## Usage
### Gradle
```kotlin
repositories {
// other repositories
maven { url "https://git.koval.net/api/packages/cyclane/maven" }
}
dependencies {
implementation("net.koval.teamcity-gitea-test-task:tinyvm:0.1.0")
}
```
### Documentation
Use autocompletion and hover menus in your IDE, or download the
[generated HTML documentation](https://git.koval.net/cyclane/teamcity-gitea-test-task/releases/download/v0.1.0/tinyvm-0.1.0-javadoc.zip)
from the [latest release](https://git.koval.net/cyclane/teamcity-gitea-test-task/releases).
## Instructions ## Instructions
Create a library that implements simple Git functionality. You need to implement at least three entities: Create a library that implements simple Git functionality. You need to implement at least three entities:

View File

@@ -1,12 +1,10 @@
plugins { plugins {
kotlin("jvm") version "1.9.21" kotlin("jvm") version "1.9.21"
id("org.jmailen.kotlinter") version "4.1.0" id("org.jmailen.kotlinter") version "4.1.0"
id("org.jetbrains.dokka") version "1.9.10"
`maven-publish`
} }
group = "net.koval.teamcity-gitea-test-task" group = "net.koval"
version = System.getenv("TINYVM_VERSION") version = "1.0-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
@@ -23,46 +21,3 @@ tasks.test {
kotlin { kotlin {
jvmToolchain(17) jvmToolchain(17)
} }
val dokkaHtml by tasks.getting(org.jetbrains.dokka.gradle.DokkaTask::class)
val javadocJar: TaskProvider<Jar> by tasks.registering(Jar::class) {
dependsOn(dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaHtml.outputDirectory)
}
publishing {
publications.register<MavenPublication>("gpr") {
artifactId = "tinyvm"
from(components["java"])
artifact(javadocJar)
pom {
name.set("TeamCity support for Gitea - Test Task - tiny version manager")
description.set("This is a small project to implement a subset of git's functionality in Kotlin and was " +
"created using the instructions below as part of my application to the JetBrains internship project " +
"\"TeamCity support for Gitea\".")
url.set("https://git.koval.net/cyclane/teamcity-gitea-test-task")
developers {
developer {
id.set("cyclane")
name.set("Gleb Koval")
email.set("gleb@koval.net")
}
}
scm {
url.set("https://git.koval.net/cyclane/teamcity-gitea-test-task")
}
}
}
repositories {
maven {
name = "Gitea"
url = uri("https://git.koval.net/api/packages/cyclane/maven")
credentials {
username = System.getenv("GITEA_USERNAME")
password = System.getenv("GITEA_TOKEN")
}
}
}
}

View File

@@ -2,36 +2,28 @@ package tinyvm
import java.security.MessageDigest import java.security.MessageDigest
import java.time.Instant import java.time.Instant
import java.util.HexFormat
/** abstract class Object(
* Represents an arbitrary version manager object. val type: String,
*/ ) {
abstract class Object(val type: String) {
abstract val data: String abstract val data: String
fun hash(): String = fun hash(): String =
HexFormat.of().formatHex(
MessageDigest MessageDigest
.getInstance("SHA-1") .getInstance("SHA-1")
.digest("$type ${data.length}\u0000$data".toByteArray()), .digest("$type ${data.length}\u0000$data".toByteArray())
) .toHex()
} }
/**
* Commits are a pointer to a 'head' tree with some metadata.
*/
class Commit( class Commit(
val tree: Tree, val tree: Tree,
val author: Author, val author: Author,
val message: String, val message: String,
val timestamp: Instant, val timestamp: Instant,
) : Object("commit") { ) : Object("commit") {
override val data: String
// Use \n\n for end of header in-case additional metadata is implemented in the future. // Use \n\n for end of header in-case additional metadata is implemented in the future.
override val data: String
get() = "tree ${tree.hash()}\nauthor $author\ntimestamp ${timestamp.epochSecond}\n\n$message" get() = "tree ${tree.hash()}\nauthor $author\ntimestamp ${timestamp.epochSecond}\n\n$message"
override fun toString(): String = "commit ${hash()}\n$data"
} }
data class Author( data class Author(

View File

@@ -0,0 +1,5 @@
package tinyvm
import java.util.HexFormat
fun ByteArray.toHex(): String = HexFormat.of().formatHex(this)

View File

@@ -1,13 +1,7 @@
package tinyvm package tinyvm
/**
* Tree nodes are either trees or blobs, represented by this sealed class.
*/
sealed class Node(type: String) : Object(type) sealed class Node(type: String) : Object(type)
/**
* A tree is a set of named nodes.
*/
class Tree(val nodes: Map<String, Node>) : Node("tree") { class Tree(val nodes: Map<String, Node>) : Node("tree") {
// For simplicity just use the hex-formatted hash, not the actual value like git does. // For simplicity just use the hex-formatted hash, not the actual value like git does.
override val data: String override val data: String
@@ -15,22 +9,6 @@ class Tree(val nodes: Map<String, Node>) : Node("tree") {
nodes.map { (name, node) -> nodes.map { (name, node) ->
"${node.type} $name\u0000${node.hash()}" "${node.type} $name\u0000${node.hash()}"
}.sorted().joinToString() }.sorted().joinToString()
override fun toString(): String =
"tree ${hash()}\n" +
nodes.map { (name, node) ->
when (node) {
is Tree -> "+$name/\n"
is Blob -> "+$name\n"
} + node.toString().leftMargin()
}.sorted().joinToString("\n")
} }
/** class Blob(override val data: String) : Node("blob")
* A blob is a data container.
*/
class Blob(override val data: String) : Node("blob") {
override fun toString(): String = "blob ${hash()}\n${data.leftMargin()}"
}
private fun String.leftMargin(): String = split('\n').joinToString("\n") { "| $it" }

View File

@@ -18,22 +18,22 @@ internal class RepositoryTest {
tree = tree =
Tree( Tree(
mapOf( mapOf(
"dir2" to
Tree(
mapOf(
"test1.txt" to Blob("This is a second file"),
),
),
"dir1" to "dir1" to
Tree( Tree(
mapOf( mapOf(
"test1.txt" to Blob("Hello World!"), "test1.txt" to Blob("Hello World!"),
), ),
), ),
"dir2" to
Tree(
mapOf(
"test2.txt" to Blob("This is a second file"),
),
),
), ),
), ),
author = Author("Gleb Koval", "gleb@koval.net"), author = Author("Gleb Koval", "gleb@koval.net"),
message = "Move test1.txt to dir1 and add dir2/test1.txt", message = "Move test1.txt and add dir2/test2.txt",
timestamp = Instant.ofEpochSecond(50), timestamp = Instant.ofEpochSecond(50),
), ),
Commit( Commit(
@@ -49,7 +49,7 @@ internal class RepositoryTest {
"dir2" to "dir2" to
Tree( Tree(
mapOf( mapOf(
"test1.txt" to Blob("This is a second file"), "test2.txt" to Blob("This is a second file"),
), ),
), ),
"README.md" to Blob("# This is a test repo!"), "README.md" to Blob("# This is a test repo!"),
@@ -117,39 +117,4 @@ internal class RepositoryTest {
assertEquals(committed, repository.findCommit { it.message.matches("Move.*".toRegex()) }) assertEquals(committed, repository.findCommit { it.message.matches("Move.*".toRegex()) })
assertEquals(null, repository.getCommit("00000000000000000000")) assertEquals(null, repository.getCommit("00000000000000000000"))
} }
@Test
fun `can display commit`() {
assertEquals(
"""
commit 804c6f0c66da0ea0eab8ac29f23a627e03a962b2
tree aaebbb258bce30749fc302cbd161b78462252a32
author Gleb Koval <gleb@koval.net>
timestamp 0
Add test1.txt
""".trimIndent(),
commits[0].toString(),
)
}
@Test
fun `can display tree`() {
assertEquals(
"""
tree 048b8e267dd7a6c5d4849d59f6ff82e6ae948f13
+dir1/
| tree aaebbb258bce30749fc302cbd161b78462252a32
| +test1.txt
| | blob c57eff55ebc0c54973903af5f72bac72762cf4f4
| | | Hello World!
+dir2/
| tree 0e14cbdba02924cd63a58c2492fc3dd05dc682bd
| +test1.txt
| | blob da3fcc31cbc16fcbb7526d68771f77e7e7f02fb1
| | | This is a second file
""".trimIndent(),
commits[1].tree.toString(),
)
}
} }