commit - 1723d2ac5388915746d7cd9c84279116898690a1
commit + f980e76321979183bd8dd6cc67f93d08fced21db
blob - 59801df9877633c858b4d08ab598a6e0a8b2799d
blob + 8e3da498cbe96662aeccc2cac5fc8e9c46a5e20e
--- build.gradle.kts
+++ build.gradle.kts
bundledModule("intellij.platform.vcs.dvcs.impl")
// Referenced by OutgoingResult's commit list type (com.intellij.dvcs.push).
bundledModule("intellij.platform.vcs.log")
+ // Hash/VcsUser (vcs.shared), HashImpl/VcsUserImpl (vcs.impl.shared) and
+ // GraphCommit (vcs.log.graph) back the outgoing-commits VcsFullCommitDetails.
+ bundledModule("intellij.platform.vcs.shared")
+ bundledModule("intellij.platform.vcs.impl.shared")
+ bundledModule("intellij.platform.vcs.log.graph")
}
}
blob - 061b30b9cccd625922be97ff4080fa8d96371d65
blob + 7843f1287cfd7e333e454184ebb7c2696aa9b7c5
--- src/main/kotlin/dev/nezzontli/gotvcs/cli/GotCommandLineWrapper.kt
+++ src/main/kotlin/dev/nezzontli/gotvcs/cli/GotCommandLineWrapper.kt
data class GotUpdateEntry(val code: Char, val path: String)
+data class GotCommitObject(
+ val commitId: String,
+ val parents: List<String>,
+ val authorName: String,
+ val authorEmail: String,
+ /** Seconds since the epoch, as stored in the commit object. */
+ val authorTimestamp: Long,
+ val message: String,
+)
+
/**
* Central entry point for every invocation of the `got` binary. The binary
* path and SSH_AUTH_SOCK are configurable in Settings > Version Control >
.map { line -> GotUpdateEntry(line[0], line.substring(3)) }
.toList()
}
+
+ /** Resolves `refs/remotes/<remote>/<branch>` to a commit ID via `got ref -l`, or null if it doesn't exist locally. */
+ @Throws(VcsException::class)
+ fun remoteBranchHash(workDir: File, remote: String, branch: String): String? {
+ val output = run(workDir, "ref", "-l")
+ val prefix = "refs/remotes/$remote/$branch:"
+ val line = output.lineSequence().firstOrNull { it.trim().startsWith(prefix) } ?: return null
+ return line.substringAfter(":").trim()
+ }
+
+ /**
+ * Commit IDs reachable from the current branch but not yet known to be on
+ * [remoteHash] (via `got log -x`), most recent first. If [remoteHash] is
+ * null (no local knowledge of the remote branch, e.g. before any fetch),
+ * this falls back to the full history up to [limit].
+ */
+ @Throws(VcsException::class)
+ fun outgoingCommitIds(workDir: File, remoteHash: String?, limit: Int = 200): List<String> {
+ val args = mutableListOf("log", "-l", limit.toString())
+ if (remoteHash != null) {
+ args.add("-x")
+ args.add(remoteHash)
+ }
+ val output = run(workDir, *args.toTypedArray())
+ return output.lineSequence()
+ .filter { it.startsWith("commit ") }
+ .map { it.removePrefix("commit ").trim().substringBefore(' ') }
+ .filter { it != remoteHash }
+ .toList()
+ }
+
+ private val commitAuthorPattern = Regex("""^(.*)\s+<(.+)>\s+(\d+)\s+[+-]\d{4}$""")
+
+ /** Parses the raw commit object printed by `got cat <commit-id>` (tree/parent/author/committer/message). */
+ @Throws(VcsException::class)
+ fun catCommit(workDir: File, commitId: String): GotCommitObject {
+ val lines = run(workDir, "cat", commitId).lines()
+ val parents = mutableListOf<String>()
+ var authorName = ""
+ var authorEmail = ""
+ var authorTimestamp = 0L
+ var messageStart = -1
+
+ for ((index, line) in lines.withIndex()) {
+ when {
+ line.startsWith("parent ") -> parents.add(line.removePrefix("parent ").trim())
+ line.startsWith("author ") -> {
+ commitAuthorPattern.find(line.removePrefix("author ").trim())?.let { match ->
+ authorName = match.groupValues[1].trim()
+ authorEmail = match.groupValues[2]
+ authorTimestamp = match.groupValues[3].toLongOrNull() ?: 0L
+ }
+ }
+ line.startsWith("messagelen ") -> messageStart = index + 2
+ }
+ }
+
+ val message = if (messageStart in lines.indices) lines.drop(messageStart).joinToString("\n").trimEnd('\n') else ""
+ return GotCommitObject(commitId, parents, authorName, authorEmail, authorTimestamp, message)
+ }
}
blob - a2df57f27c8576ff4321027caeb61a6e2c2217dc
blob + 632600d4ab0b4ba9d133197386db363c491e6c69
--- src/main/kotlin/dev/nezzontli/gotvcs/push/GotOutgoingCommitsProvider.kt
+++ src/main/kotlin/dev/nezzontli/gotvcs/push/GotOutgoingCommitsProvider.kt
import com.intellij.dvcs.push.OutgoingCommitsProvider
import com.intellij.dvcs.push.OutgoingResult
import com.intellij.dvcs.push.PushSpec
+import com.intellij.dvcs.push.VcsError
+import com.intellij.openapi.vcs.VcsException
+import dev.nezzontli.gotvcs.cli.GotCommandLineWrapper
import dev.nezzontli.gotvcs.repo.GotRepository
+import java.io.File
/**
- * got has no cheap local equivalent of "commits not yet on the remote"
- * without contacting the server first, so this reports an empty preview
- * rather than an inaccurate one. The Push dialog still works; it just
- * doesn't list outgoing commits ahead of time.
+ * Lists commits reachable from the current branch but not yet known to be on
+ * `refs/remotes/<remote>/<branch>` locally (via `got log -x`). This reflects
+ * the last `got fetch`, not a live round-trip to the server, same as Git's
+ * own outgoing-commits preview.
*/
-class GotOutgoingCommitsProvider : OutgoingCommitsProvider<GotRepository, GotPushSource, GotPushTarget>() {
+class GotOutgoingCommitsProvider(private val commandLine: GotCommandLineWrapper) :
+ OutgoingCommitsProvider<GotRepository, GotPushSource, GotPushTarget>() {
+
override fun getOutgoingCommits(
repository: GotRepository,
pushSpec: PushSpec<GotPushSource, GotPushTarget>,
initial: Boolean,
- ): OutgoingResult = OutgoingResult(emptyList(), emptyList())
+ ): OutgoingResult {
+ val workDir = File(repository.root.path)
+ val target = pushSpec.target
+ return try {
+ val remoteHash = commandLine.remoteBranchHash(workDir, target.remoteName, target.branch)
+ val commits = commandLine.outgoingCommitIds(workDir, remoteHash)
+ .map { id -> GotFullCommitDetails(commandLine.catCommit(workDir, id), repository.root) }
+ OutgoingResult(commits, emptyList())
+ } catch (e: VcsException) {
+ OutgoingResult(emptyList(), listOf(VcsError(e.message)))
+ }
+ }
}
blob - /dev/null
blob + 6109655e214c356044f7d12f5641baa37d9d2164 (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/push/GotFullCommitDetails.kt
+package dev.nezzontli.gotvcs.push
+
+import com.intellij.openapi.vcs.changes.Change
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.vcs.log.Hash
+import com.intellij.vcs.log.VcsFullCommitDetails
+import com.intellij.vcs.log.VcsUser
+import com.intellij.vcs.log.impl.HashImpl
+import com.intellij.vcs.log.util.VcsUserUtil
+import dev.nezzontli.gotvcs.cli.GotCommitObject
+
+/**
+ * Adapts a [GotCommitObject] (parsed from `got cat <commit-id>`) to the
+ * Push dialog's outgoing-commits list. got has no per-file change list
+ * cheaply available for an arbitrary historical commit here, so
+ * [getChanges] is empty; the dialog only needs id/author/message/parents
+ * to render its commit list.
+ */
+class GotFullCommitDetails(
+ private val commitObject: GotCommitObject,
+ private val root: VirtualFile,
+) : VcsFullCommitDetails {
+
+ private val hash: Hash = HashImpl.build(commitObject.commitId)
+ private val author: VcsUser = VcsUserUtil.createUser(commitObject.authorName, commitObject.authorEmail)
+
+ override fun getId(): Hash = hash
+ override fun getParents(): List<Hash> = commitObject.parents.map { HashImpl.build(it) }
+ override fun getTimestamp(): Long = commitObject.authorTimestamp * 1000L
+ override fun getRoot(): VirtualFile = root
+ override fun getSubject(): String = commitObject.message.lineSequence().firstOrNull().orEmpty()
+ override fun getAuthor(): VcsUser = author
+ override fun getCommitter(): VcsUser = author
+ override fun getAuthorTime(): Long = commitObject.authorTimestamp * 1000L
+ override fun getCommitTime(): Long = commitObject.authorTimestamp * 1000L
+ override fun getFullMessage(): String = commitObject.message
+ override fun getChanges(): Collection<Change> = emptyList()
+ override fun getChanges(parent: Int): Collection<Change> = emptyList()
+}
blob - ae7f6076ead4322cc0a63a24d017ac0f76b98a8e
blob + f893f92824fea712e2695c5cbf3f47507df21b36
--- src/main/kotlin/dev/nezzontli/gotvcs/push/GotPushSupport.kt
+++ src/main/kotlin/dev/nezzontli/gotvcs/push/GotPushSupport.kt
private val commandLine = GotCommandLineWrapper()
private val pusher = GotPusher(commandLine)
- private val outgoingCommitsProvider = GotOutgoingCommitsProvider()
+ private val outgoingCommitsProvider = GotOutgoingCommitsProvider(commandLine)
override fun getVcs(): AbstractVcs = ProjectLevelVcsManager.getInstance(project).findVcsByName(GotVcs.NAME)!!
blob - ba31c5d498654310fa732f8323f263e9daa97f3b
blob + 602288db61a733ba007e8129fde8ca2afbe9f7ce
--- src/main/kotlin/dev/nezzontli/gotvcs/push/GotPushTarget.kt
+++ src/main/kotlin/dev/nezzontli/gotvcs/push/GotPushTarget.kt
* be a no-op, so [hasSomethingToPush] always returns true: `got send` itself
* reports "nothing to send" if that turns out to be the case.
*/
-class GotPushTarget(private val remoteName: String, private val branch: String) : PushTarget {
+class GotPushTarget(val remoteName: String, val branch: String) : PushTarget {
override fun hasSomethingToPush(): Boolean = true
override fun getPresentation(): String = "$remoteName/$branch"
}