Commit Diff


commit - 86002dd96cc41e1d71f2bcd80782da4e6c1f4d0e
commit + f2c271a1a7eb4bc7a6000c5b8ff246338d3ea016
blob - 3a132b68a94c64d99f1a7cfa3f540b599cfc9c73
blob + 62c7c35916c9501165f4cadef5b9539a44a95a67
--- src/main/kotlin/dev/nezzontli/gotvcs/GotCommandLineWrapper.kt
+++ src/main/kotlin/dev/nezzontli/gotvcs/GotCommandLineWrapper.kt
@@ -9,6 +9,10 @@ import java.nio.charset.StandardCharsets
 
 data class GotStatusEntry(val code: Char, val stagedCode: Char, val path: String)
 
+data class GotLogEntry(val commitId: String, val author: String, val date: String, val message: String)
+
+data class GotUpdateEntry(val code: Char, val path: String)
+
 /**
  * Centraliza todas las invocaciones al binario `got`. La ruta de abajo es la
  * del store de Nix resuelta al momento de escribir este código
@@ -57,6 +61,10 @@ class GotCommandLineWrapper {
         run(workDir, "cat", "-c", ":base", "-P", relativePath)
 
     @Throws(VcsException::class)
+    fun catAt(workDir: File, commitId: String, relativePath: String): String =
+        run(workDir, "cat", "-c", commitId, "-P", relativePath)
+
+    @Throws(VcsException::class)
     fun baseCommit(workDir: File): String {
         val output = run(workDir, "info")
         val line = output.lineSequence().firstOrNull { it.trimStart().startsWith("work tree base commit:") }
@@ -87,4 +95,60 @@ class GotCommandLineWrapper {
         if (paths.isEmpty()) return
         run(workDir, *(arrayOf("revert", "-R") + paths))
     }
+
+    /** Historial vía `got log`, formato verbose por defecto (bloques separados por líneas de guiones). */
+    @Throws(VcsException::class)
+    fun log(workDir: File, relativePath: String?, limit: Int = 500): List<GotLogEntry> {
+        val args = mutableListOf("log", "-l", limit.toString())
+        if (relativePath != null) args.add(relativePath)
+        val output = run(workDir, *args.toTypedArray())
+
+        val entries = mutableListOf<GotLogEntry>()
+        var commitId: String? = null
+        var author: String? = null
+        var date: String? = null
+        val message = StringBuilder()
+
+        fun flush() {
+            val id = commitId
+            if (id != null) {
+                entries.add(GotLogEntry(id, author.orEmpty(), date.orEmpty(), message.toString().trim('\n')))
+            }
+            commitId = null
+            author = null
+            date = null
+            message.setLength(0)
+        }
+
+        for (line in output.lineSequence()) {
+            when {
+                line.isBlank() && commitId == null -> Unit
+                line.all { it == '-' } && line.isNotEmpty() -> flush()
+                line.startsWith("commit ") -> commitId = line.removePrefix("commit ").trim().substringBefore(' ')
+                line.startsWith("from: ") -> author = line.removePrefix("from: ").trim()
+                line.startsWith("date: ") -> date = line.removePrefix("date: ").trim()
+                line.startsWith(" ") -> message.appendLine(line.trimStart(' '))
+            }
+        }
+        flush()
+        return entries
+    }
+
+    @Throws(VcsException::class)
+    fun fetch(workDir: File) {
+        run(workDir, "fetch")
+    }
+
+    /** `got update`, formato de estado similar a `got status` (código + 2 espacios + path). */
+    @Throws(VcsException::class)
+    fun update(workDir: File): List<GotUpdateEntry> {
+        val output = run(workDir, "update")
+        return output.lineSequence()
+            .filter { it.isNotBlank() && it[0] != ' ' }
+            .mapNotNull { line ->
+                if (line.length < 3) return@mapNotNull null
+                GotUpdateEntry(line[0], line.substring(3))
+            }
+            .toList()
+    }
 }
blob - /dev/null
blob + 16059e116fbd373039f1808615f71b732dd0aef5 (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/GotFileRevision.kt
@@ -0,0 +1,44 @@
+package dev.nezzontli.gotvcs
+
+import com.intellij.openapi.vcs.RepositoryLocation
+import com.intellij.openapi.vcs.VcsException
+import com.intellij.openapi.vcs.history.VcsFileRevision
+import com.intellij.openapi.vcs.history.VcsRevisionNumber
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/** Una entrada de `got log` adaptada al modelo de historial de IntelliJ. */
+class GotFileRevision(
+    private val workDir: File,
+    private val relativePath: String,
+    private val commandLine: GotCommandLineWrapper,
+    private val entry: GotLogEntry,
+) : VcsFileRevision {
+
+    // Formato real de `got log`: "Thu Jul 23 05:30:12 2026 UTC".
+    private val dateFormat = SimpleDateFormat("EEE MMM d HH:mm:ss yyyy zzz", Locale.US)
+
+    override fun getRevisionNumber(): VcsRevisionNumber = GotRevisionNumber(entry.commitId)
+
+    override fun getRevisionDate(): Date = try {
+        dateFormat.parse(entry.date)
+    } catch (e: Exception) {
+        Date(0)
+    }
+
+    override fun getAuthor(): String = entry.author
+
+    override fun getCommitMessage(): String = entry.message
+
+    override fun getBranchName(): String? = null
+
+    override fun getChangedRepositoryPath(): RepositoryLocation? = null
+
+    @Throws(VcsException::class)
+    override fun loadContent(): ByteArray = getContent()
+
+    @Throws(VcsException::class)
+    override fun getContent(): ByteArray = commandLine.catAt(workDir, entry.commitId, relativePath).toByteArray()
+}
blob - 361c64c88ee0ab4cdf957b1ed9fc7d775b12f158
blob + b433f08749a4b14d51e376f06d541a91dca5ac13
--- src/main/kotlin/dev/nezzontli/gotvcs/GotVcs.kt
+++ src/main/kotlin/dev/nezzontli/gotvcs/GotVcs.kt
@@ -6,7 +6,9 @@ import com.intellij.openapi.vcs.VcsKey
 import com.intellij.openapi.vcs.changes.ChangeProvider
 import com.intellij.openapi.vcs.checkin.CheckinEnvironment
 import com.intellij.openapi.vcs.diff.DiffProvider
+import com.intellij.openapi.vcs.history.VcsHistoryProvider
 import com.intellij.openapi.vcs.rollback.RollbackEnvironment
+import com.intellij.openapi.vcs.update.UpdateEnvironment
 
 class GotVcs(project: Project) : AbstractVcs(project, NAME) {
 
@@ -15,6 +17,8 @@ class GotVcs(project: Project) : AbstractVcs(project, 
     private val diffProvider = GotDiffProvider(project, commandLine)
     private val checkinEnvironment = GotCheckinEnvironment(project, commandLine)
     private val rollbackEnvironment = GotRollbackEnvironment(project, commandLine)
+    private val historyProvider = GotVcsHistoryProvider(project, commandLine)
+    private val updateEnvironment = GotUpdateEnvironment(commandLine)
 
     override fun getDisplayName(): String = NAME
 
@@ -26,6 +30,10 @@ class GotVcs(project: Project) : AbstractVcs(project, 
 
     override fun getRollbackEnvironment(): RollbackEnvironment = rollbackEnvironment
 
+    override fun getVcsHistoryProvider(): VcsHistoryProvider = historyProvider
+
+    override fun getUpdateEnvironment(): UpdateEnvironment = updateEnvironment
+
     companion object {
         const val NAME = "got"
 
blob - /dev/null
blob + 603a30cd5a03c463712bb3314ece4eb4115a207d (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/GotUpdateEnvironment.kt
@@ -0,0 +1,66 @@
+package dev.nezzontli.gotvcs
+
+import com.intellij.openapi.options.Configurable
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.util.Ref
+import com.intellij.openapi.vcs.FilePath
+import com.intellij.openapi.vcs.VcsException
+import com.intellij.openapi.vcs.update.FileGroup
+import com.intellij.openapi.vcs.update.SequentialUpdatesContext
+import com.intellij.openapi.vcs.update.UpdateEnvironment
+import com.intellij.openapi.vcs.update.UpdateSession
+import com.intellij.openapi.vcs.update.UpdatedFiles
+import java.io.File
+
+/**
+ * "Update Project" en IntelliJ ejecuta `got fetch` (best-effort: got.conf
+ * puede no tener un remoto configurado) seguido de `got update` sobre cada
+ * raíz seleccionada.
+ */
+class GotUpdateEnvironment(private val commandLine: GotCommandLineWrapper) : UpdateEnvironment {
+
+    override fun fillGroups(updatedFiles: UpdatedFiles) = Unit
+
+    override fun updateDirectories(
+        contentRoots: Array<out FilePath>,
+        updatedFiles: UpdatedFiles,
+        progressIndicator: ProgressIndicator,
+        context: Ref<SequentialUpdatesContext>,
+    ): UpdateSession {
+        val exceptions = mutableListOf<VcsException>()
+        val workDirs = contentRoots.map { File(it.path) }.distinct()
+
+        for (workDir in workDirs) {
+            try {
+                commandLine.fetch(workDir)
+            } catch (e: VcsException) {
+                exceptions.add(e)
+            }
+
+            try {
+                for (entry in commandLine.update(workDir)) {
+                    val groupId = when (entry.code) {
+                        'U', 'G' -> FileGroup.UPDATED_ID
+                        'C', '#' -> FileGroup.MERGED_WITH_CONFLICT_ID
+                        'D' -> FileGroup.REMOVED_FROM_REPOSITORY_ID
+                        'A', '!' -> FileGroup.CREATED_ID
+                        else -> continue
+                    }
+                    updatedFiles.getGroupById(groupId)?.add(
+                        File(workDir, entry.path).path,
+                        GotVcs.getKey(),
+                        null,
+                    )
+                }
+            } catch (e: VcsException) {
+                exceptions.add(e)
+            }
+        }
+
+        return GotUpdateSession(exceptions)
+    }
+
+    override fun createConfigurable(files: MutableCollection<FilePath>): Configurable? = null
+
+    override fun validateOptions(files: MutableCollection<FilePath>): Boolean = true
+}
blob - /dev/null
blob + 6896e018526e2496a89dedb5757f2667ef065a50 (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/GotUpdateSession.kt
@@ -0,0 +1,13 @@
+package dev.nezzontli.gotvcs
+
+import com.intellij.openapi.vcs.VcsException
+import com.intellij.openapi.vcs.update.UpdateSession
+
+class GotUpdateSession(private val exceptions: MutableList<VcsException>) : UpdateSession {
+
+    override fun getExceptions(): MutableList<VcsException> = exceptions
+
+    override fun onRefreshFilesCompleted() = Unit
+
+    override fun isCanceled(): Boolean = false
+}
blob - /dev/null
blob + 1a42e9637c573a2648e53d28c8aa02ff6bd317fc (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/GotVcsHistoryProvider.kt
@@ -0,0 +1,63 @@
+package dev.nezzontli.gotvcs
+
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vcs.FilePath
+import com.intellij.openapi.vcs.ProjectLevelVcsManager
+import com.intellij.openapi.vcs.VcsException
+import com.intellij.openapi.vcs.history.DiffFromHistoryHandler
+import com.intellij.openapi.vcs.history.VcsAbstractHistorySession
+import com.intellij.openapi.vcs.history.VcsAppendableHistorySessionPartner
+import com.intellij.openapi.vcs.history.VcsDependentHistoryComponents
+import com.intellij.openapi.vcs.history.VcsFileRevision
+import com.intellij.openapi.vcs.history.VcsHistoryProvider
+import com.intellij.openapi.vcs.history.VcsHistorySession
+import com.intellij.openapi.vfs.VfsUtilCore
+import com.intellij.util.ui.ColumnInfo
+import java.io.File
+
+class GotVcsHistoryProvider(
+    private val project: Project,
+    private val commandLine: GotCommandLineWrapper,
+) : VcsHistoryProvider {
+
+    override fun getUICustomization(session: VcsHistorySession, forShortcutRegistration: javax.swing.JComponent) =
+        VcsDependentHistoryComponents.createOnlyColumns(emptyArray<ColumnInfo<*, *>>())
+
+    override fun getAdditionalActions(refresher: Runnable): Array<AnAction> = emptyArray()
+
+    override fun isDateOmittable(): Boolean = false
+
+    override fun getHelpId(): String? = null
+
+    override fun supportsHistoryForDirectories(): Boolean = true
+
+    override fun getHistoryDiffHandler(): DiffFromHistoryHandler? = null
+
+    override fun canShowHistoryFor(virtualFile: com.intellij.openapi.vfs.VirtualFile): Boolean = true
+
+    @Throws(VcsException::class)
+    override fun createSessionFor(filePath: FilePath): VcsHistorySession {
+        val vcsRoot = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(filePath)
+            ?: throw VcsException("$filePath no pertenece a ningún work tree got")
+        val workDir = File(vcsRoot.path)
+        val relativePath = VfsUtilCore.getRelativePath(filePath.virtualFile ?: vcsRoot, vcsRoot)
+            ?: throw VcsException("No se pudo resolver la ruta relativa de $filePath en $workDir")
+
+        val entries = commandLine.log(workDir, relativePath.ifEmpty { null })
+        val revisions: List<VcsFileRevision> = entries.map { GotFileRevision(workDir, relativePath, commandLine, it) }
+        val baseRevision = GotRevisionNumber(commandLine.baseCommit(workDir))
+        return GotVcsHistorySession(revisions, baseRevision)
+    }
+
+    @Throws(VcsException::class)
+    override fun reportAppendableHistory(filePath: FilePath, partner: VcsAppendableHistorySessionPartner) {
+        try {
+            val session = createSessionFor(filePath) as VcsAbstractHistorySession
+            partner.reportCreatedEmptySession(session)
+            session.revisionList.forEach { partner.acceptRevision(it) }
+        } catch (e: VcsException) {
+            partner.reportException(e)
+        }
+    }
+}
blob - /dev/null
blob + bbb281713e03b2340649d2950a97f3d43e1df48b (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/GotVcsHistorySession.kt
@@ -0,0 +1,16 @@
+package dev.nezzontli.gotvcs
+
+import com.intellij.openapi.vcs.history.VcsAbstractHistorySession
+import com.intellij.openapi.vcs.history.VcsFileRevision
+import com.intellij.openapi.vcs.history.VcsHistorySession
+import com.intellij.openapi.vcs.history.VcsRevisionNumber
+
+class GotVcsHistorySession(
+    revisions: List<VcsFileRevision>,
+    private val baseRevision: VcsRevisionNumber,
+) : VcsAbstractHistorySession(revisions, baseRevision) {
+
+    override fun calcCurrentRevisionNumber(): VcsRevisionNumber = baseRevision
+
+    override fun copy(): VcsHistorySession = GotVcsHistorySession(revisionList, baseRevision)
+}