1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | package com.gluecron
import com.gluecron.settings.GluecronSettingsState
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.project.Project
import git4idea.repo.GitRepositoryManager
import java.awt.Desktop
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
/**
* Shared utilities for Gluecron actions.
*
* - [detectOwnerRepo] — infers `owner/repo` from the project's git remote URL
* - [openBrowser] — opens a URL in the system's default browser
* - [apiPost] — synchronous POST helper used by MergePrAction
* - [notify] — show a balloon notification
*/
object GluecronUtil {
/**
* Detects the owner and repository name from the first git remote URL
* in the project that contains the configured Gluecron host, or falls
* back to any remote named "origin".
*
* Handles both HTTPS and SSH remote formats:
* https://gluecron.com/owner/repo.git
* git@gluecron.com:owner/repo.git
*
* Returns null if no usable git remote is found.
*/
fun detectOwnerRepo(project: Project): Pair<String, String>? {
val settings = GluecronSettingsState.getInstance()
val manager = GitRepositoryManager.getInstance(project)
val repos = manager.repositories
if (repos.isEmpty()) return null
// Prefer remotes whose URL contains the configured host
val hostDomain = settings.host
.removePrefix("https://")
.removePrefix("http://")
.trimEnd('/')
fun parseRemoteUrl(url: String): Pair<String, String>? {
// Normalise: strip protocol + host prefix or git@ prefix
val cleaned = url
.replace(Regex("^https?://[^/]+/"), "")
.replace(Regex("^git@[^:]+:"), "")
.removeSuffix(".git")
val parts = cleaned.split("/").filter { it.isNotBlank() }
if (parts.size < 2) return null
return Pair(parts[0], parts[1])
}
for (repo in repos) {
val remotes = repo.remotes
// Try host-matching remote first
for (remote in remotes) {
val url = remote.firstUrl ?: continue
if (url.contains(hostDomain)) {
return parseRemoteUrl(url)
}
}
// Fall back to any remote named "origin"
val origin = remotes.firstOrNull { it.name == "origin" }
if (origin != null) {
val url = origin.firstUrl ?: continue
return parseRemoteUrl(url)
}
}
return null
}
/**
* Returns the name of the currently checked-out branch in the first
* git repository found in the project, or null if none.
*/
fun currentBranch(project: Project): String? {
val manager = GitRepositoryManager.getInstance(project)
return manager.repositories.firstOrNull()
?.currentBranchName
}
/**
* Opens [url] in the system's default browser.
* Falls back gracefully if Desktop is not supported.
*/
fun openBrowser(url: String) {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(URI(url))
}
}
/**
* Performs a synchronous HTTP POST to [path] (relative to the configured host)
* with a JSON [body]. Returns the response body as a string, or throws on error.
*
* This is intentionally minimal — used only by MergePrAction which needs a
* blocking call so we can surface the result in the same action invocation.
*/
fun apiPost(path: String, body: String): String {
val settings = GluecronSettingsState.getInstance()
val url = "${settings.host}$path"
val token = settings.token
val client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build()
val requestBuilder = HttpRequest.newBuilder()
.uri(URI(url))
.timeout(Duration.ofSeconds(30))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
if (token.isNotBlank()) {
requestBuilder.header("Authorization", "Bearer $token")
}
val response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
if (response.statusCode() !in 200..299) {
throw RuntimeException("HTTP ${response.statusCode()}: ${response.body().take(200)}")
}
return response.body()
}
/**
* Shows a balloon notification attached to [project].
*/
fun notify(project: Project, message: String, type: NotificationType = NotificationType.INFORMATION) {
NotificationGroupManager.getInstance()
.getNotificationGroup("Gluecron Notifications")
.createNotification("Gluecron", message, type)
.notify(project)
}
}
|