All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.zegreatrob.tools.adapter.git.GitAdapter.kt Maven / Gradle / Ivy

There is a newer version: 1.5.26
Show newest version
package com.zegreatrob.tools.adapter.git

import kotlinx.datetime.Instant

class GitAdapter(private val workingDirectory: String) {

    fun headCommitId(): String = runProcess(
        listOf(
            "git",
            "--no-pager",
            "rev-parse",
            "HEAD",
        ),
        workingDirectory,
    ).trim()

    fun newAnnotatedTag(name: String, ref: String, userName: String?, userEmail: String?) {
        val command = listOf("git") + inlineConfig("user.name", userName) + inlineConfig("user.email", userEmail) +
            listOf(
                "tag",
                "--annotate",
                "--message=$name",
                name,
                ref,
            )
        runProcess(
            command,
            workingDirectory,
        )
    }

    private fun inlineConfig(property: String, value: String?) =
        if (value != null) listOf("-c", "$property=$value") else emptyList()

    fun pushTags() {
        runProcess(listOf("git", "push", "--tags"), workingDirectory)
    }

    fun listTags(): List {
        val outputText = runProcess(
            listOf(
                "git",
                "--no-pager",
                "tag",
                "--list",
                "--sort=-taggerdate",
                "--format=%(refname:strip=2),%(*objectname),%(creatordate:iso-strict)",
            ),
            workingDirectory,
        )
        val output = outputText.split("\n").mapNotNull {
            val commaSplit = it.split(",")
            if (commaSplit.size >= 3) {
                TagRef(
                    name = commaSplit.subList(0, commaSplit.size - 2).joinToString(""),
                    commitId = commaSplit.reversed()[1],
                    dateTime = Instant.parse(commaSplit.last()),
                )
            } else {
                null
            }
        }
        return output
    }

    fun log(): List = parseLog(
        runProcess(
            listOf(
                "git",
                "--no-pager",
                "log",
                "--format=$gitLogFormat",
            ),
            workingDirectory,
        ),
    )

    fun showTag(ref: String): String? = runProcess(
        listOf(
            "git",
            "show",
            ref,
            "--format=%D",
            "--no-patch",
        ),
        workingDirectory,
    ).split(", ")
        .findByPrefix("tag: ")

    fun logWithRange(begin: String, end: String? = null): List = parseLog(
        runProcess(
            listOf(
                "git",
                "--no-pager",
                "log",
                "--format=$gitLogFormat",
                begin,
            ) + if (end != null) listOf(end) else emptyList(),
            workingDirectory,
        ),
    )

    private val commitSeparator = "--------!--------"
    private val gitLogFormat = "%H%n%ae%n%ce%n%aI%n%P%n%B%n$commitSeparator"

    private fun parseLog(outputText: String) = outputText
        .split("$commitSeparator\n")
        .filter { it.isNotEmpty() }
        .map { commitEntry ->
            val elements = commitEntry.split("\n")
            CommitRef(
                id = elements[0],
                authorEmail = elements[1],
                committerEmail = elements[2],
                dateTime = Instant.parse(elements[3]),
                parents = elements[4].split(" ").filter { it.isNotEmpty() },
                fullMessage = elements.subList(5, elements.size).joinToString("\n"),
            )
        }

    fun status(): GitStatus = runProcess(
        listOf(
            "git",
            "status",
            "--porcelain=2",
            "--branch",
            "--ahead-behind",
        ),
        workingDirectory,
    )
        .let { output ->
            val lines = output.split("\n")
            val head = lines.findByPrefix("# branch.head ")
            val upstream = lines.findByPrefix("# branch.upstream ")
            val (a, b) = lines.findByPrefix("# branch.ab ")
                ?.split(" ")
                ?.map { it.substring(1) }
                ?: listOf("-1", "-1")
            GitStatus(
                isClean = lines
                    .filterNot { it.startsWith("#") }
                    .filterNot(String::isBlank)
                    .isEmpty(),
                ahead = a.toInt(),
                behind = b.toInt(),
                head = head ?: "",
                upstream = upstream ?: "",
            )
        }

    private fun List.findByPrefix(prefix: String) =
        find { it.startsWith(prefix) }?.substring(prefix.length)

    fun describe(abbrev: Int): String? = runCatching {
        runProcess(listOf("git", "describe", "--abbrev=$abbrev"), workingDirectory)
            .trim()
    }.getOrNull()

    fun init() {
        runProcess(listOf("git", "init"), workingDirectory)
    }

    fun config(name: String, value: String) {
        runProcess(listOf("git", "config", name, value), workingDirectory)
    }

    fun add(vararg files: String) {
        runProcess(listOf("git", "add") + files, workingDirectory)
    }

    fun commit(
        message: String,
        authorName: String,
        authorEmail: String,
        committerName: String,
        committerEmail: String,
    ) {
        runProcess(
            args = listOf(
                "git",
                "commit",
                "--message=$message",
                "--allow-empty",
            ),
            env = mapOf(
                "GIT_AUTHOR_NAME" to authorName,
                "GIT_AUTHOR_EMAIL" to authorEmail,
                "GIT_COMMITTER_NAME" to committerName,
                "GIT_COMMITTER_EMAIL" to committerEmail,
            ),
            workingDirectory = workingDirectory,
        )
    }

    fun addRemote(name: String, url: String) {
        runProcess(listOf("git", "remote", "add", name, url), workingDirectory)
    }

    fun fetch() {
        runProcess(listOf("git", "fetch"), workingDirectory)
    }
}

data class GitStatus(
    val isClean: Boolean,
    val ahead: Int,
    val behind: Int,
    val head: String,
    val upstream: String,
)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy