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

org.octopusden.octopus.infrastructure.bitbucket.client.BitbucketClient.kt Maven / Gradle / Ivy

There is a newer version: 2.0.55
Show newest version
package org.octopusden.octopus.infrastructure.bitbucket.client

import feign.Headers
import feign.Param
import feign.QueryMap
import feign.RequestLine
import java.util.Date
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BaseBitbucketEntity
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketUser
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketBranch
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCommit
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCommitChange
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreatePrRef
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreateProject
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreatePullRequest
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreatePullRequestReviewer
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreateRepository
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketEntityList
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketJiraCommit
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketProject
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketPullRequest
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketRepository
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketTag
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketUpdateRepository
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.DefaultReviewersQuery
import org.octopusden.octopus.infrastructure.bitbucket.client.exception.InvalidCommitIdException
import org.octopusden.octopus.infrastructure.bitbucket.client.exception.NotFoundException
import org.slf4j.Logger
import org.slf4j.LoggerFactory

private val _log: Logger = LoggerFactory.getLogger(BitbucketClient::class.java)

const val PROJECT_PATH = "rest/api/1.0/projects"
const val REPO_PATH = "rest/api/1.0/repos"
const val JIRA_ISSUES_PATH = "rest/jira/1.0/issues"
const val DEFAULT_REVIEWERS_PATH = "rest/default-reviewers/1.0/projects"
const val ENTITY_LIMIT = 100

interface BitbucketClient {

    @RequestLine("GET $PROJECT_PATH")
    fun getProjects(@QueryMap requestParams: Map): BitbucketEntityList

    @RequestLine("GET $REPO_PATH")
    fun getRepositories(@QueryMap requestParams: Map): BitbucketEntityList

    @RequestLine("POST $PROJECT_PATH")
    @Headers("Content-Type: application/json")
    fun createProject(dto: BitbucketCreateProject)

    @RequestLine("GET $PROJECT_PATH/{projectKey}")
    @Throws(NotFoundException::class)
    fun getProject(@Param("projectKey") projectKey: String): BitbucketProject

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos")
    fun getRepositories(
        @Param("projectKey") projectKey: String,
        @QueryMap requestParams: Map
    ): BitbucketEntityList

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}")
    @Throws(NotFoundException::class)
    fun getRepository(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String
    ): BitbucketRepository

    @RequestLine("POST $PROJECT_PATH/{projectKey}/repos")
    @Headers("Content-Type: application/json")
    fun createRepository(@Param("projectKey") projectKey: String, dto: BitbucketCreateRepository)

    @RequestLine("PUT $PROJECT_PATH/{projectKey}/repos/{repository}")
    @Headers("Content-Type: application/json")
    fun updateRepository(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        dto: BitbucketUpdateRepository
    )

    @RequestLine("DELETE $PROJECT_PATH/{projectKey}/repos/{repository}")
    fun deleteRepository(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
    )

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/commits")
    fun getCommits(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap requestParams: Map
    ): BitbucketEntityList

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/commits/{id}")
    @Throws(NotFoundException::class)
    @Suppress("FunctionName")
    fun _getCommit(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @Param("id", expander = BitbucketCommitIdValidator::class) id: String
    ): BitbucketCommit

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/commits/{id}/changes")
    @Throws(NotFoundException::class)
    @Suppress("FunctionName")
    fun _getCommitChanges(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @Param("id", expander = BitbucketCommitIdValidator::class) id: String
    ): BitbucketEntityList

    @RequestLine("GET $JIRA_ISSUES_PATH/{issueKey}/commits")
    fun getCommits(
        @Param("issueKey") issueKey: String,
        @QueryMap requestParams: Map
    ): BitbucketEntityList

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/tags")
    fun getTags(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap requestParams: Map,
    ): BitbucketEntityList

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/branches")
    fun getBranches(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap requestParams: Map,
    ): BitbucketEntityList

    @RequestLine("GET $DEFAULT_REVIEWERS_PATH/{projectKey}/repos/{repository}/reviewers")
    fun getDefaultReviewers(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap query: DefaultReviewersQuery
    ): Set

    @RequestLine("POST $PROJECT_PATH/{projectKey}/repos/{repository}/pull-requests")
    @Headers("Content-Type: application/json")
    fun createPullRequest(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        dto: BitbucketCreatePullRequest
    ): BitbucketPullRequest

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/pull-requests/{id}")
    @Headers("Content-Type: application/json")
    fun getPullRequest(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @Param("id") id: Long
    ): BitbucketPullRequest
}

fun BitbucketClient.getProjects(): List {
    return execute({ parameters: Map -> getProjects(parameters) })
}

fun BitbucketClient.getRepositories(): List =
    execute({ parameters: Map -> getRepositories(parameters) })

fun BitbucketClient.getRepositories(projectKey: String): List =
    execute({ parameters: Map -> getRepositories(projectKey, parameters) })


fun BitbucketClient.getCommit(projectKey: String, repository: String, commitIdOrRef: String): BitbucketCommit =
    try {
        _getCommit(projectKey, repository, commitIdOrRef)
    } catch (e: InvalidCommitIdException) {
        _log.info("Treat `$commitIdOrRef` as a ref. ${e.message}")
        run {
            val shortBranchName = commitIdOrRef.replace("^refs/heads/".toRegex(), "")
            val fullBranchName = "refs/heads/$shortBranchName"
            getBranches(projectKey, repository)
                .firstOrNull { b -> b.id == fullBranchName }
                ?.latestCommit
                ?.let { commitId -> _getCommit(projectKey, repository, commitId) }
        } ?: throw NotFoundException("Ref '$commitIdOrRef' does not exist in repository '$repository' and ${e.message}")
    }

fun BitbucketClient.getCommitChanges(projectKey: String, repository: String, commitIdOrRef: String): List =
    try {
        _getCommitChanges(projectKey, repository, commitIdOrRef)
    } catch (e: InvalidCommitIdException) {
        _log.info("Treat `$commitIdOrRef` as a ref. ${e.message}")
        run {
            val shortBranchName = commitIdOrRef.replace("^refs/heads/".toRegex(), "")
            val fullBranchName = "refs/heads/$shortBranchName"
            getBranches(projectKey, repository)
                .firstOrNull { b -> b.id == fullBranchName }
                ?.latestCommit
                ?.let { commitId -> _getCommitChanges(projectKey, repository, commitId) }
        } ?: throw NotFoundException("Ref '$commitIdOrRef' does not exist in repository '$repository' and ${e.message}")
    }.values

fun BitbucketClient.getCommits(
    projectKey: String,
    repository: String,
    until: String,
    since: String
): List {
    val toId = getCommit(projectKey, repository, until).id
    val fromId = getCommit(projectKey, repository, since).id
    return if (toId == fromId) {
        emptyList()
    } else {
        execute({ parameters: Map ->
            getCommits(projectKey, repository, parameters + mapOf("until" to toId, "since" to fromId))
        }).also { commits ->
            if (!commits.any { commit -> commit.parents.any { it.id == fromId } }) {
                throw NotFoundException("Cannot find commit '$fromId' in commit graph for commit '$toId' in '$projectKey:$repository'")
            }
        }
    }
}

fun BitbucketClient.getCommits(
    projectKey: String, repository: String, until: String, sinceDate: Date? = null
) = execute({ parameters: Map ->
    getCommits(projectKey, repository, parameters + mapOf("until" to until))
}, { commit: BitbucketCommit ->
    sinceDate == null || commit.authorTimestamp > sinceDate
})

fun BitbucketClient.getCommits(issueKey: String): List =
    execute({ parameters: Map -> getCommits(issueKey, parameters) })

fun BitbucketClient.getTags(
    projectKey: String,
    repository: String
): List = execute({ parameters: Map -> getTags(projectKey, repository, parameters) })

fun BitbucketClient.getBranches(
    projectKey: String,
    repository: String
): List = execute({ parameters: Map -> getBranches(projectKey, repository, parameters) })

fun BitbucketClient.createPullRequestWithDefaultReviewers(
    projectKey: String,
    repository: String,
    sourceBranch: String,
    targetBranch: String,
    title: String,
    description: String
): BitbucketPullRequest {
    val existedRepository = getRepository(projectKey, repository)

    val branches = getBranches(projectKey, repository)
        .associateBy { bitbucketBranch -> bitbucketBranch.displayId }

    fun getRef(type: String, branchName: String): BitbucketCreatePrRef = branches[branchName]
        ?.let { BitbucketCreatePrRef(it.id, existedRepository) }
        ?: throw NotFoundException("$type branch '$branchName' not found in '$projectKey:$repository'")

    val sourceRef = getRef("Source", sourceBranch)
    val targetRef = getRef("Target", targetBranch)

    val defaultReviewers = getDefaultReviewers(
        projectKey,
        repository,
        DefaultReviewersQuery(existedRepository.id, sourceRef.id, existedRepository.id, targetRef.id)
    )
        .map { BitbucketCreatePullRequestReviewer(it) }
        .toSet()

    return createPullRequest(
        projectKey,
        repository,
        BitbucketCreatePullRequest(title, description, sourceRef, targetRef, defaultReviewers)
    )
}

private fun > execute(
    function: (Map) -> BitbucketEntityList,
    filter: (element: T) -> Boolean = { true }
): List {
    var page = 0
    var pageStart = 0
    val entities = mutableListOf()
    val parameters = mutableMapOf("limit" to ENTITY_LIMIT)
    do {
        page++
        parameters["start"] = pageStart
        val currentPartEntities = function.invoke(parameters)
        val inFilter: Boolean = with(currentPartEntities.values.all(filter)) {
            entities += if (this) {
                currentPartEntities.values
            } else {
                currentPartEntities.values.filter(filter)
            }
            this
        }
        pageStart = currentPartEntities.nextPageStart ?: pageStart
    } while (!currentPartEntities.isLastPage && inFilter)
    _log.debug("Pages retrieved: $page")
    return entities
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy