Commit Diff


commit - f3ca72ddd01412bbbba872218e1fdf805b1cf72d
commit + a3f6382b95b1085b8c2ba724827d033669b9b5b8
blob - /dev/null
blob + 6d217996761e5c7534801c216d48eb1172ee761a (mode 644)
--- /dev/null
+++ .kotlin/errors/errors-1784879603938.log
@@ -0,0 +1,3 @@
+kotlin version: 2.4.10
+error message: Daemon compilation failed
+
blob - 71cbd06edba31dd52ad4b0b3547d8f20376ce481
blob + 25846216f6513e9519a5fe1eabcc603d60f9f99a
--- src/main/kotlin/dev/nezzontli/gotvcs/checkin/GotCheckinEnvironment.kt
+++ src/main/kotlin/dev/nezzontli/gotvcs/checkin/GotCheckinEnvironment.kt
@@ -43,9 +43,21 @@ class GotCheckinEnvironment(
         feedback: MutableSet<in String>,
     ): MutableList<VcsException> = doCommit(changes, commitMessage)
 
-    private fun doCommit(changes: List<Change>, commitMessage: String): MutableList<VcsException> {
+    private fun doCommit(changes: List<Change>, commitMessage: String): MutableList<VcsException> =
+        commitFilePaths(changes.mapNotNull { it.afterRevision?.file ?: it.beforeRevision?.file }, commitMessage)
+
+    /**
+     * Exposed for GotCommitAndSendExecutor: unlike the default "Commit"
+     * button (a VCS_COMMIT session, for which the platform automatically
+     * calls scheduleUnversionedFilesForAddition()/scheduleMissingFileForDeletion()
+     * before committing), a custom CommitExecutor's session is on its own
+     * for that -- confirmed by decompiling ChangesViewCommitWorkflowHandler,
+     * whose addUnversionedFiles() is a no-op unless CommitSessionInfo.isVcsCommit()
+     * is true. This lets that caller commit an explicit path list (tracked
+     * changes plus whatever it scheduled for add/deletion itself) in one go.
+     */
+    fun commitFilePaths(filePaths: List<FilePath>, commitMessage: String): MutableList<VcsException> {
         val exceptions = mutableListOf<VcsException>()
-        val filePaths = changes.mapNotNull { it.afterRevision?.file ?: it.beforeRevision?.file }
         for ((workDir, paths) in groupByRoot(filePaths)) {
             try {
                 commandLine.commit(workDir, commitMessage, paths)
blob - /dev/null
blob + 00dd2062abec9b198fe10c55daef4490c8bc8ce1 (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/checkin/GotCommitAndSendExecutor.kt
@@ -0,0 +1,114 @@
+package dev.nezzontli.gotvcs.checkin
+
+import com.intellij.notification.NotificationGroupManager
+import com.intellij.notification.NotificationType
+import com.intellij.openapi.actionSystem.ActionManager
+import com.intellij.openapi.actionSystem.ActionUiKind
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.actionSystem.Presentation
+import com.intellij.openapi.actionSystem.ex.ActionUtil
+import com.intellij.openapi.actionSystem.impl.SimpleDataContext
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vcs.FilePath
+import com.intellij.openapi.vcs.ProjectLevelVcsManager
+import com.intellij.openapi.vcs.changes.Change
+import com.intellij.openapi.vcs.changes.ChangeListManager
+import com.intellij.openapi.vcs.changes.ChangeListManagerImpl
+import com.intellij.openapi.vcs.changes.CommitContext
+import com.intellij.openapi.vcs.changes.CommitExecutor
+import com.intellij.openapi.vcs.changes.CommitSession
+import com.intellij.vcsUtil.VcsUtil
+import dev.nezzontli.gotvcs.repo.GotRepositoryManager
+
+const val COMMIT_AND_SEND_EXECUTOR_ID = "Got.Commit.And.Send"
+
+/**
+ * Commits exactly like the default "Commit" button (delegates to the same
+ * CheckinEnvironment), then opens the native Push dialog (same action as
+ * Ctrl+Shift+K) so the user can review and send from there.
+ */
+class GotCommitAndSendExecutor(
+    private val project: Project,
+    private val checkinEnvironment: GotCheckinEnvironment,
+) : CommitExecutor {
+
+    override fun getActionText(): String = "Commit and Send"
+
+    override fun getId(): String = COMMIT_AND_SEND_EXECUTOR_ID
+
+    override fun createCommitSession(context: CommitContext): CommitSession = session
+
+    private val session = object : CommitSession {
+        // execute() runs on a background thread (same as the default commit
+        // action), but opening the Push dialog is a UI action and must
+        // happen on the EDT -- see com.intellij.execution.process
+        // .OSProcessHandler#checkEdtAndReadAction-style threading rules.
+        override fun execute(changes: MutableCollection<out Change>, commitMessage: String?) {
+            val message = commitMessage.orEmpty()
+
+            // Unlike the default "Commit" button, a custom CommitExecutor's
+            // session does NOT get scheduleUnversionedFilesForAddition() /
+            // scheduleMissingFileForDeletion() called automatically by the
+            // platform first (verified by decompiling
+            // ChangesViewCommitWorkflowHandler.addUnversionedFiles(), which
+            // is a no-op unless CommitSessionInfo.isVcsCommit() is true) --
+            // and `got commit -m msg <path>` errors with "no changes to
+            // commit" for a path that hasn't been `got add`-ed yet (verified
+            // directly against the CLI). So new/deleted files must be
+            // staged here ourselves before the actual commit.
+            val changeListManager = ChangeListManager.getInstance(project) as ChangeListManagerImpl
+            val unversionedFiles = changeListManager.unversionedFilesPaths.mapNotNull { it.virtualFile }
+            val deletedPaths = changeListManager.deletedFiles.map { it.path }
+
+            if (unversionedFiles.isNotEmpty()) {
+                checkinEnvironment.scheduleUnversionedFilesForAddition(unversionedFiles.toMutableList())
+            }
+            if (deletedPaths.isNotEmpty()) {
+                checkinEnvironment.scheduleMissingFileForDeletion(deletedPaths.toMutableList())
+            }
+
+            val trackedPaths = changes.mapNotNull { it.afterRevision?.file ?: it.beforeRevision?.file }
+            val unversionedFilePaths = unversionedFiles.map { VcsUtil.getFilePath(it) }
+            val allPaths: List<FilePath> = (trackedPaths + unversionedFilePaths + deletedPaths).distinct()
+
+            val errors = checkinEnvironment.commitFilePaths(allPaths, message)
+            if (errors.isNotEmpty()) {
+                val group = NotificationGroupManager.getInstance().getNotificationGroup("got")
+                group.createNotification(
+                    "got commit failed",
+                    errors.joinToString("\n") { it.message.orEmpty() },
+                    NotificationType.ERROR,
+                ).notify(project)
+                return
+            }
+
+            // GotRepository's cached branch/revision (used to resolve the
+            // Push dialog's target) is normally refreshed by the platform's
+            // own post-commit VCS-root-dirty machinery, but that happens
+            // asynchronously and can race with the dialog opening right
+            // below -- refreshing it here ourselves, still on this
+            // background thread, guarantees the dialog sees the commit that
+            // was just made instead of possibly-stale cached state.
+            val vcsManager = ProjectLevelVcsManager.getInstance(project)
+            val repositoryManager = project.getService(GotRepositoryManager::class.java)
+            val affectedRoots = allPaths.mapNotNull { vcsManager.getVcsRootFor(it) }.toSet()
+            for (root in affectedRoots) {
+                repositoryManager.getRepositoryForRoot(root)?.update()
+            }
+
+            ApplicationManager.getApplication().invokeLater {
+                val pushAction = ActionManager.getInstance().getAction("Vcs.Push") ?: return@invokeLater
+                val dataContext = SimpleDataContext.getProjectContext(project)
+                val event = AnActionEvent.createEvent(
+                    dataContext,
+                    Presentation(),
+                    "GotCommitAndSend",
+                    ActionUiKind.NONE,
+                    null,
+                )
+                ActionUtil.performAction(pushAction, event)
+            }
+        }
+    }
+}
blob - /dev/null
blob + 304c9f39bf6f17b49d46dbe001c9067492d285bd (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/checkin/GotCommitAndSendExecutorAction.kt
@@ -0,0 +1,8 @@
+package dev.nezzontli.gotvcs.checkin
+
+import com.intellij.openapi.vcs.changes.actions.BaseCommitExecutorAction
+
+/** Renders as the secondary (gray) toolbar button, next to the primary blue "Commit" button. */
+class GotCommitAndSendExecutorAction : BaseCommitExecutorAction() {
+    override val executorId: String = COMMIT_AND_SEND_EXECUTOR_ID
+}
blob - /dev/null
blob + 4cd0edd93bd88e55d33348212bc95b0946dc5b72 (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/log/GotCommitContentRevision.kt
@@ -0,0 +1,32 @@
+package dev.nezzontli.gotvcs.log
+
+import com.intellij.openapi.vcs.FilePath
+import com.intellij.openapi.vcs.VcsException
+import com.intellij.openapi.vcs.changes.ContentRevision
+import com.intellij.openapi.vcs.history.VcsRevisionNumber
+import dev.nezzontli.gotvcs.changes.GotRevisionNumber
+import dev.nezzontli.gotvcs.cli.GotCommandLineWrapper
+import java.io.File
+
+/**
+ * File content at an arbitrary historical commit (`got cat -c <commit> -P`),
+ * for the Log tab's per-commit diff. Unlike GotContentRevision (which is
+ * pinned to the work tree's current base commit), this one is parametrized
+ * by whichever commit the Log tab is showing -- called from background log
+ * loading, never from EDT-bound UI rendering, so it may shell out lazily.
+ */
+class GotCommitContentRevision(
+    private val filePath: FilePath,
+    private val workDir: File,
+    private val relativePath: String,
+    private val commandLine: GotCommandLineWrapper,
+    private val commitId: String,
+) : ContentRevision {
+
+    @Throws(VcsException::class)
+    override fun getContent(): String = commandLine.catAt(workDir, commitId, relativePath)
+
+    override fun getFile(): FilePath = filePath
+
+    override fun getRevisionNumber(): VcsRevisionNumber = GotRevisionNumber(commitId)
+}
blob - /dev/null
blob + d283e140140370e12759cba3fbc196bfea092aab (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/log/GotFullCommitDetails.kt
@@ -0,0 +1,42 @@
+package dev.nezzontli.gotvcs.log
+
+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
+ * platform's commit-details model, shared by the Push dialog's
+ * outgoing-commits preview (no [changes] needed there) and the Log tab
+ * (which populates [changes] from `got log -P`). [getChanges] returns the
+ * same flat list regardless of [parent] index -- got's `-P` output diffs
+ * against the immediate history parent, so this isn't fully merge-aware
+ * for commits with more than one parent, but is correct for the common case.
+ */
+class GotFullCommitDetails(
+    private val commitObject: GotCommitObject,
+    private val root: VirtualFile,
+    private val changes: List<Change> = emptyList(),
+) : 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> = changes
+    override fun getChanges(parent: Int): Collection<Change> = changes
+}
blob - /dev/null
blob + 773c61207552e102eb1c5d71fe27973802e17e87 (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/log/GotVcsLogProvider.kt
@@ -0,0 +1,167 @@
+package dev.nezzontli.gotvcs.log
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.vcs.VcsException
+import com.intellij.openapi.vcs.VcsKey
+import com.intellij.openapi.vcs.changes.Change
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.Consumer
+import com.intellij.vcs.log.Hash
+import com.intellij.vcs.log.VcsLogProperties
+import com.intellij.vcs.log.VcsLogProvider
+import com.intellij.vcs.log.VcsLogRefManager
+import com.intellij.vcs.log.VcsRef
+import com.intellij.vcs.log.VcsUser
+import com.intellij.vcs.log.impl.HashImpl
+import com.intellij.vcs.log.util.VcsUserUtil
+import com.intellij.vcsUtil.VcsUtil
+import dev.nezzontli.gotvcs.GotVcs
+import dev.nezzontli.gotvcs.cli.GotCommandLineWrapper
+import dev.nezzontli.gotvcs.cli.GotChangedPath
+import java.io.File
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Backs the platform's Log tab entirely by shelling out to `got` (no local
+ * index/cache beyond GotCommandLineWrapper's in-memory commit cache) -- same
+ * philosophy as the rest of this plugin: no daemon, no persisted state.
+ */
+class GotVcsLogProvider(private val commandLine: GotCommandLineWrapper = GotCommandLineWrapper()) : VcsLogProvider {
+
+    override val supportedVcs: VcsKey = GotVcs.getKey()
+
+    override val referenceManager: VcsLogRefManager = GotVcsLogRefManager()
+
+    // getCurrentBranch()/getCurrentUser() are called from EDT-bound UI code
+    // (CurrentBranchHighlighter.update(), on every visible-pack refresh -- and
+    // likely an equivalent "my commits" highlighter for getCurrentUser), so
+    // neither can shell out to `got` synchronously -- same class of bug fixed
+    // earlier for GotRepository/GotBranchWidget. readAllHashes() always runs
+    // off the EDT (it's the Log tab's own background data loading), and
+    // always runs before those highlighters can fire, so precomputing both
+    // here and serving them from cache is safe.
+    private val currentBranchCache = ConcurrentHashMap<String, String>()
+    private val currentUserCache = ConcurrentHashMap<String, VcsUser>()
+
+    override fun readAllHashes(root: VirtualFile, commitConsumer: Consumer<in com.intellij.vcs.log.TimedVcsCommit>): VcsLogProvider.LogData {
+        val workDir = File(root.path)
+        try {
+            val branch = commandLine.currentBranch(workDir)
+            if (branch != null) {
+                currentBranchCache[root.path] = branch
+                val tipId = commandLine.refs(workDir).firstOrNull { !it.isRemote && !it.isTag && it.name == branch }?.hash
+                val commitObject = tipId?.let { commandLine.catCommit(workDir, it) }
+                if (commitObject != null) {
+                    currentUserCache[root.path] = VcsUserUtil.createUser(commitObject.authorName, commitObject.authorEmail)
+                }
+            } else {
+                currentBranchCache.remove(root.path)
+            }
+        } catch (e: VcsException) {
+            currentBranchCache.remove(root.path)
+        }
+        val ids = commandLine.allCommitIds(workDir)
+        val users = mutableSetOf<VcsUser>()
+        for (id in ids) {
+            val commitObject = commandLine.catCommit(workDir, id)
+            users.add(VcsUserUtil.createUser(commitObject.authorName, commitObject.authorEmail))
+            commitConsumer.consume(GotFullCommitDetails(commitObject, root))
+        }
+        val refs = buildRefs(workDir, root)
+        return SimpleLogData(refs, users)
+    }
+
+    override fun readMetadata(
+        root: VirtualFile,
+        hashes: List<String>,
+        consumer: Consumer<in com.intellij.vcs.log.VcsCommitMetadata>,
+    ) {
+        val workDir = File(root.path)
+        for (id in hashes) {
+            consumer.consume(GotFullCommitDetails(commandLine.catCommit(workDir, id), root))
+        }
+    }
+
+    override fun readFullDetails(
+        root: VirtualFile,
+        hashes: List<String>,
+        commitConsumer: Consumer<in com.intellij.vcs.log.VcsFullCommitDetails>,
+    ) {
+        val workDir = File(root.path)
+        for (id in hashes) {
+            val commitObject = commandLine.catCommit(workDir, id)
+            val parentId = commitObject.parents.firstOrNull()
+            val changes = try {
+                commandLine.changedPaths(workDir, id).map { toChange(workDir, id, parentId, it) }
+            } catch (e: VcsException) {
+                emptyList()
+            }
+            commitConsumer.consume(GotFullCommitDetails(commitObject, root, changes))
+        }
+    }
+
+    private fun toChange(workDir: File, commitId: String, parentId: String?, changed: GotChangedPath): Change {
+        val filePath = VcsUtil.getFilePath(File(workDir, changed.path), false)
+        val before = parentId?.let { GotCommitContentRevision(filePath, workDir, changed.path, commandLine, it) }
+        val after = GotCommitContentRevision(filePath, workDir, changed.path, commandLine, commitId)
+        return when (changed.code) {
+            'A' -> Change(null, after)
+            'D' -> Change(before, null)
+            else -> Change(before, after)
+        }
+    }
+
+    private fun buildRefs(workDir: File, root: VirtualFile): Set<VcsRef> {
+        val entries = try {
+            commandLine.refs(workDir)
+        } catch (e: VcsException) {
+            emptyList()
+        }
+        return entries.map { entry ->
+            val type = when {
+                entry.isTag -> GotRefType.TAG
+                entry.isRemote -> GotRefType.REMOTE_BRANCH
+                else -> GotRefType.LOCAL_BRANCH
+            }
+            SimpleVcsRef(HashImpl.build(entry.hash), entry.name, type, root)
+        }.toSet()
+    }
+
+    /** Best-effort: the tip commit's author, precomputed by readAllHashes(). got repos in practice have a single author, so this is accurate enough. */
+    override fun getCurrentUser(root: VirtualFile): VcsUser =
+        currentUserCache[root.path] ?: VcsUserUtil.createUser("", "")
+
+    override fun getContainingBranches(root: VirtualFile, commitHash: Hash): Collection<String> = emptyList()
+
+    override fun <T> getPropertyValue(property: VcsLogProperties.VcsLogProperty<T>): T? {
+        @Suppress("UNCHECKED_CAST")
+        return when (property) {
+            VcsLogProperties.LIGHTWEIGHT_BRANCHES -> true as T
+            else -> false as T
+        }
+    }
+
+    override fun getCurrentBranch(root: VirtualFile): String? = currentBranchCache[root.path]
+
+    override fun subscribeToRootRefreshEvents(
+        roots: Collection<VirtualFile>,
+        refresher: com.intellij.vcs.log.VcsLogRefresher,
+    ): Disposable = Disposable { }
+
+    private class SimpleLogData(
+        override val refs: Set<VcsRef>,
+        override val users: Set<VcsUser>,
+    ) : VcsLogProvider.LogData
+
+    private class SimpleVcsRef(
+        private val hash: Hash,
+        private val name: String,
+        private val type: com.intellij.vcs.log.VcsRefType,
+        private val root: VirtualFile,
+    ) : VcsRef {
+        override fun getCommitHash(): Hash = hash
+        override fun getName(): String = name
+        override fun getType(): com.intellij.vcs.log.VcsRefType = type
+        override fun getRoot(): VirtualFile = root
+    }
+}
blob - /dev/null
blob + 92899efdaeb5aaf2334dd2dc3eaf9d31ce18c13c (mode 644)
--- /dev/null
+++ src/main/kotlin/dev/nezzontli/gotvcs/log/GotVcsLogRefManager.kt
@@ -0,0 +1,47 @@
+package dev.nezzontli.gotvcs.log
+
+import com.intellij.vcs.log.RefGroup
+import com.intellij.vcs.log.VcsLogRefManager
+import com.intellij.vcs.log.VcsRef
+import com.intellij.vcs.log.VcsRefType
+import java.io.DataInput
+import java.io.DataOutput
+
+/**
+ * got has no notion of ref grouping/favorites beyond plain
+ * branches/remote-branches/tags, so this is a flat, ungrouped manager: one
+ * [RefGroup] per ref, ordered by name.
+ */
+class GotVcsLogRefManager : VcsLogRefManager {
+
+    private val nameComparator = Comparator.comparing<VcsRef, String> { it.name }
+
+    override fun getBranchLayoutComparator(): Comparator<VcsRef> = nameComparator
+
+    override fun getLabelsOrderComparator(): Comparator<VcsRef> = nameComparator
+
+    override fun groupForBranchFilter(refs: MutableCollection<out VcsRef>): List<RefGroup> =
+        refs.sortedWith(nameComparator).map { SingleRefGroup(it) }
+
+    override fun groupForTable(refs: MutableCollection<out VcsRef>, compact: Boolean, showTagNames: Boolean): List<RefGroup> =
+        refs.sortedWith(nameComparator).map { SingleRefGroup(it) }
+
+    override fun serialize(out: DataOutput, type: VcsRefType) {
+        out.writeByte((type as? GotRefType)?.ordinal ?: GotRefType.LOCAL_BRANCH.ordinal)
+    }
+
+    override fun deserialize(input: DataInput): VcsRefType {
+        val ordinal = input.readByte().toInt()
+        return GotRefType.entries.getOrElse(ordinal) { GotRefType.LOCAL_BRANCH }
+    }
+
+    override fun isFavorite(ref: VcsRef): Boolean = false
+
+    override fun setFavorite(ref: VcsRef, favorite: Boolean) = Unit
+
+    private class SingleRefGroup(private val ref: VcsRef) : RefGroup {
+        override fun getName(): String = ref.name
+        override fun getRefs(): List<VcsRef> = listOf(ref)
+        override fun getColors(): List<java.awt.Color> = listOf(ref.type.backgroundColor)
+    }
+}
blob - 6109655e214c356044f7d12f5641baa37d9d2164 (mode 644)
blob + /dev/null
--- src/main/kotlin/dev/nezzontli/gotvcs/push/GotFullCommitDetails.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-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()
-}