Add auto installation support and cache content of ContentRevision

[?]
Apr 4, 2021, 3:15 AM
2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC

Dependencies

  • [2] ISO7J5ZH More caches, better and generic cache code. Now Dracon listen to file changes to drop cached data. Implemented caches: - File contents in specific revision (never dropped) - Pijul ls and Pijul diff results - File Revision and File changes by patch - some others.. Dracon is incredible fast now, but still will take some time for bigger repos.
  • [3] Q7FXTHVU First record support, YEAAAH, RECOOORD
  • [4] B43WNBLF - Add Show History to Pijul menu - Always ignore .idea and .pijul in tracking. - Make findPijul a generic function to allow to find editor-server. - Only show one revision for directories. - Add `Hunk::resolvePath(Path)` to resolve the affected file to a Java NIO Path. - Fix StringOutOfBounds in Change Parsering Algorithm - Use editor-server instead of copie for interfacing with `pijul record` file. - Fix FileStatus provider not returning correctly for untracked files. - Add CommittedChangesProvider for Pijul.
  • [5] FRFFQV7V Basic show history support.
  • [6] GGYFPXND Initial plugin
  • [7] MTPTFTHG Initial plugin 2
  • [8] FNNW5IEA Added more plugin files to Pijul
  • [9] ZCRW57C5 Improved support for revisions
  • [10] 5AUENX2Y Add support to view files affected by a revision
  • [11] 6CR2EFUN First ChangeProvider implementation!!! Wheehooo
  • [12] Q35OTML2 Remove use of coroutines, which was blocking IntelliJ UI in larger repositories Improvements for bigger repositories, now Dracon caches the changes that happened in a revision in a file, so everytime Dracon needs to query the changes of a revision, it loads directly from memory instead of doing a full-scan in Pijul repository. For tiny projects it is not a problem, but for medium ones it takes more than five minutes to scan the entire repository (and it was tested with a repo of only 700 records, however there was changes that had more than 60.000 lines). The cache file is saved in IntelliJ Data Path (project specific) and is compressed with gzip, so it will not use so much disk space (the cost worths the gains).
  • [13] QXUEMZ3B Initial CahngeProvider

Change contents

  • edit in src/main/resources/messages/DraconBundle.properties at line 19
    [3.55]
    [3.82]
    action.Pijul.InvalidateCaches.text=Invalidate Caches
  • replacement in src/main/resources/messages/DraconBundle.properties at line 42
    [3.383][3.383:499]()
    expert.mode.notification.failure=Failed to record changes with exit code <bold>{0}</bold> and message: <tt>{1}</tt>.
    [3.383]
    expert.mode.notification.failure=Failed to record changes with exit code <bold>{0}</bold> and message: <tt>{1}</tt>.
    cache.invalidate.text=Invalidating caches...
    cache.invalidate.current.text=Invalidating <em>{0}</em>...
    cache.invalidate.finish.text=Invalidated caches
    install.pijul.title=Install pijul
    install.pijul.title2=Install Pijul
    install.pijul.text=Cannot find pijul binaries.\n\
    Do you want to install pijul using rust cargo?
    install.editor.server.title=Install editor-server
    install.editor.server.title2=Install Editor Server
    install.editor.server.text=Cannot find editor-server binaries.\n\
    Dracon depends on editor-server for interfacing with Pijul.\n\
    Do you want to install editor-server using rust cargo?
    install.cargo.title=Install Rustup and Cargo
    install.cargo.text=In order to install <tt>{0}</tt> you need to have cargo installed.\n\
    However we cannot find cargo installed in this machine.\n\
    Do you want to install rustup with cargo?\n\
    If you agree, we will download rustup and launch the installer, you take from there.
    install.pijul.warning.text=You won't be able to use Dracon plugin since some dependencies could not be installed.
    install.cargo.running.text=Running cargo install...
  • edit in src/main/resources/META-INF/plugin.xml at line 31
    [3.680]
    [3.2261]
    <postStartupActivity implementation="com.github.jonathanxd.dracon.activity.PijulPostStartupActivity"/>
  • edit in src/main/resources/META-INF/plugin.xml at line 42
    [3.1228]
    [3.1228]
    </action>
    <action id="Pijul.InvalidateCaches" class="com.github.jonathanxd.dracon.actions.PijulInvalidateCaches">
  • edit in src/main/resources/META-INF/plugin.xml at line 71
    [3.2184]
    [3.2184]
    <reference ref="Pijul.InvalidateCaches"/>
    <separator/>
  • edit in src/main/resources/META-INF/plugin.xml at line 75
    [3.2349][3.2349:2350]()
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/util/Application.kt at line 9
    [3.387]
    [3.387]
    return findBinaryOrNull(app) ?: app
    }
    fun findBinaryOrNull(app: String): String? {
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/util/Application.kt at line 41
    [3.1811][3.1811:1826]()
    ?: app
  • file move: listeners (----------)listeners (d--r------)
    [3.107]
    [2.7108]
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/content/PijulContentRevision.kt at line 3
    [3.5082]
    [3.5130]
    import com.github.jonathanxd.dracon.cache.FileRevisionCache
  • replacement in src/main/kotlin/com/github/jonathanxd/dracon/content/PijulContentRevision.kt at line 5
    [3.5195][3.14860:14925]()
    import com.github.jonathanxd.dracon.revision.loadStateInRevision
    [3.5195]
    [3.5241]
    import com.intellij.openapi.components.service
  • replacement in src/main/kotlin/com/github/jonathanxd/dracon/content/PijulContentRevision.kt at line 21
    [3.5842][3.5842:5863]()
    ): ContentRevision {
    [3.5842]
    [3.5863]
    ) : ContentRevision {
  • replacement in src/main/kotlin/com/github/jonathanxd/dracon/content/PijulContentRevision.kt at line 24
    [3.5064][3.5064:5175](),[3.5175][3.6188:6279](),[3.5283][3.5864:5904](),[3.6279][3.5864:5904](),[3.5864][3.5864:5904](),[3.5904][3.14926:15016](),[3.15016][3.5941:5947](),[3.5941][3.5941:5947]()
    constructor(root: Path,
    filePath: FilePath,
    revision: PijulRevisionNumber,
    project: Project): this(root, Paths.get(filePath.path), revision, project)
    override fun getContent(): String {
    return loadStateInRevision(this.revision.hash, this.project, root, this.filePath)
    }
    [3.5064]
    [3.5947]
    constructor(
    root: Path,
    filePath: FilePath,
    revision: PijulRevisionNumber,
    project: Project
    ) : this(root, Paths.get(filePath.path), revision, project)
    private val content = this.project.service<FileRevisionCache>().loadAsync(this)
    override fun getContent(): String = String(this.content.get(), Charsets.UTF_8)
  • replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 61
    [3.2187][3.2187:2257]()
    private val pijulLogEntryCache = PijulLogEntryCache(this.project)
    [3.2187]
    [3.2257]
    private val pijulLogEntryCache by lazy {
    this.project.service<PijulLogEntryCache>()
    }
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 256
    [2.13228]
    [3.10608]
    }
    val reset = this.reset(project, root)
    if (reset.statusCode !is SuccessStatusCode) {
    return reset
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/PijulLogEntryChangeCache.kt at line 54
    [2.16706]
    [2.16706]
    fun invalidate() {
    this.dataCache.invalidate()
    this.revisionDataCache.invalidate()
    }
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/PijulLogEntryCache.kt at line 6
    [3.11764]
    [3.11764]
    import com.intellij.openapi.components.Service
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/PijulLogEntryCache.kt at line 10
    [3.11967]
    [3.11967]
    @Service
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/PijulLogEntryCache.kt at line 21
    [2.17147]
    [3.13103]
    }
    fun invalidate() {
    this.dataCache.invalidate()
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/FileStatusCache.kt at line 64
    [2.19595]
    [2.19595]
    }
    fun invalidate() {
    this.cache.invalidate()
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/FileRevisionCache.kt at line 3
    [2.20201]
    [2.20201]
    import com.github.jonathanxd.dracon.content.PijulContentRevision
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/FileRevisionCache.kt at line 32
    [2.21339]
    [2.21339]
    fun loadAsync(rev: PijulContentRevision): CompletableFuture<ByteArray> {
    val root = project.service<PijulVcsContext>().root
    val fileRev = FileRevisionRef(rev.filePath.toAbsolutePath().toString(), rev.revision.hash)
    return this.cache.queryOrLoadAsync(fileRev) {
    loadStateInRevision(
    rev.revision.hash,
    this.project,
    root,
    rev.filePath
    ).toByteArray(Charsets.UTF_8)
    }
    }
    fun invalidate() {
    this.cache.invalidate()
    }
  • edit in src/main/kotlin/com/github/jonathanxd/dracon/cache/DataCache.kt at line 138
    [2.25774]
    [2.25774]
    }
    }
    fun invalidate() {
    this.lock.lock()
    try {
    this.inMemory.clear()
    this.manager.write(this.inMemory)
    } finally {
    this.lock.unlock();
  • file addition: activity (----------)
    [3.107]
  • file addition: PijulPostStartupActivity.kt (----------)
    [0.3639]
    package com.github.jonathanxd.dracon.activity
    import com.github.jonathanxd.dracon.i18n.BUNDLE
    import com.github.jonathanxd.dracon.i18n.DraconBundle
    import com.github.jonathanxd.dracon.pijul.NonZeroExitStatusCode
    import com.github.jonathanxd.dracon.pijul.Pijul
    import com.github.jonathanxd.dracon.pijul.pijul
    import com.github.jonathanxd.dracon.util.escapeXml
    import com.github.jonathanxd.dracon.util.findBinary
    import com.github.jonathanxd.dracon.util.findBinaryOrNull
    import com.github.jonathanxd.dracon.util.wrapInHml
    import com.github.jonathanxd.dracon.vcs.DraconVcsUtil
    import com.github.jonathanxd.dracon.vcs.NotificationIds
    import com.intellij.execution.configurations.GeneralCommandLine
    import com.intellij.execution.util.ExecUtil
    import com.intellij.openapi.progress.ProgressIndicator
    import com.intellij.openapi.progress.Task
    import com.intellij.openapi.project.Project
    import com.intellij.openapi.startup.StartupActivity
    import com.intellij.openapi.ui.Messages
    import com.intellij.openapi.util.SystemInfo
    import com.intellij.openapi.vcs.VcsNotifier
    import com.intellij.util.download.DownloadableFileService
    import com.intellij.util.download.FileDownloader
    import com.intellij.util.io.IdeUtilIoBundle
    import com.intellij.vcsUtil.VcsUtil
    import org.jetbrains.annotations.PropertyKey
    import java.nio.file.Paths
    import java.util.concurrent.CompletableFuture
    class PijulPostStartupActivity : StartupActivity {
    override fun runActivity(project: Project) {
    val path = project.basePath?.let { Paths.get(it) } ?: return
    if (Pijul.isUnderPijul(path)) {
    val hasPijul = findBinaryOrNull("pijul") != null
    val hasEditorServer = findBinaryOrNull("editor-server") != null
    if (!hasPijul) {
    val hasCargo = findBinaryOrNull("cargo") != null
    if (!hasCargo) {
    this.askForCargoInstallation(project, "pijul").handle { t, u ->
    if (t != null && checkForCargo(project)) {
    cargoInstallPijul(project)
    }
    if (u != null) {
    warnDraconWillNotWork(project)
    u.printStackTrace()
    }
    }
    } else {
    cargoInstallPijul(project)
    }
    }
    if (!hasEditorServer) {
    val hasCargo = findBinaryOrNull("cargo") != null
    if (!hasCargo) {
    this.askForCargoInstallation(project, "editor-server").handle { t, u ->
    if (t != null && checkForCargo(project)) {
    cargoInstallEditor(project)
    }
    if (u != null) {
    warnDraconWillNotWork(project)
    u.printStackTrace()
    }
    }
    } else {
    cargoInstallEditor(project)
    }
    }
    }
    }
    fun cargoInstallPijul(project: Project) {
    val dialog = Messages.showYesNoDialog(
    project,
    DraconBundle.message("install.pijul.text").wrapInHml(),
    DraconBundle.message("install.pijul.title2"),
    Messages.getWarningIcon()
    )
    if (dialog == Messages.YES) {
    object : Task.Backgroundable(project, DraconBundle.message("install.pijul.title")) {
    override fun run(indicator: ProgressIndicator) {
    indicator.isIndeterminate = true
    indicator.text = DraconBundle.message("install.pijul.title")
    indicator.text2 = DraconBundle.message("install.cargo.running.text")
    execInTerminal("install.pijul.title", "cargo install pijul --version '~1.0.0-alpha'")
    }
    }.queue()
    } else {
    warnDraconWillNotWork(project)
    }
    }
    fun cargoInstallEditor(project: Project) {
    val dialog = Messages.showYesNoDialog(
    project,
    DraconBundle.message("install.editor.server.text").wrapInHml(),
    DraconBundle.message("install.editor.server.title2"),
    Messages.getWarningIcon()
    )
    if (dialog == Messages.YES) {
    object : Task.Backgroundable(project, DraconBundle.message("install.editor.server.title")) {
    override fun run(indicator: ProgressIndicator) {
    indicator.isIndeterminate = true
    indicator.text = DraconBundle.message("install.editor.server.title")
    indicator.text2 = DraconBundle.message("install.cargo.running.text")
    execInTerminal("install.editor.server.title", "cargo install editor-server")
    }
    }.queue()
    } else {
    warnDraconWillNotWork(project)
    }
    }
    fun checkForCargo(project: Project): Boolean {
    if (findBinaryOrNull("cargo") == null) {
    warnDraconWillNotWork(project)
    return false
    }
    return true
    }
    fun warnDraconWillNotWork(project: Project) {
    Messages.showWarningDialog(
    project,
    DraconBundle.message("install.pijul.warning.text").wrapInHml(),
    DraconBundle.message("install.pijul.title")
    )
    }
    fun askForCargoInstallation(project: Project, sub: String): CompletableFuture<Unit> {
    val dialog = Messages.showYesNoDialog(
    project,
    DraconBundle.message("install.cargo.text", sub.escapeXml()).wrapInHml(),
    DraconBundle.message("install.cargo.title"),
    Messages.getWarningIcon()
    )
    if (dialog == Messages.YES) {
    val download = if (SystemInfo.isWindows) {
    DownloadableFileService.getInstance().createDownloader(listOf(
    DownloadableFileService.getInstance()
    .createFileDescription(
    "https://win.rustup.rs/x86_64",
    "rustup-init.exe"
    )
    ), "Rustup")
    } else {
    DownloadableFileService.getInstance().createDownloader(listOf(
    DownloadableFileService.getInstance()
    .createFileDescription(
    "https://sh.rustup.rs",
    "rustup-init.sh"
    )
    ), "Rustup")
    }
    return download.downloadWithBackgroundProgress(null, project)
    .handle { t, u ->
    if (t != null && t.isNotEmpty()) {
    val file = Paths.get(VcsUtil.getFilePath(t.single().first).path).toAbsolutePath()
    if (SystemInfo.isWindows) {
    this.execInTerminal("install.cargo.title", file.toString())
    } else {
    this.execInTerminal("install.cargo.title", "sh $file")
    }
    }
    u?.printStackTrace()
    }
    } else {
    return CompletableFuture.failedFuture(IllegalStateException("User not accepted cargo installation."))
    }
    }
    fun execInTerminal(
    @PropertyKey(resourceBundle = BUNDLE) name: String,
    command: String
    ) {
    ExecUtil.execAndGetOutput(GeneralCommandLine(ExecUtil.getTerminalCommand(DraconBundle.message(name), command)))
    }
    }
  • file addition: PijulInvalidateCaches.kt (----------)
    [3.18106]
    /**
    * Dracon - An IntelliJ-Pijul integration.
    * Copyright 2021 JonathanxD <jhrldev@gmail.com>
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    *
    * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
    package com.github.jonathanxd.dracon.actions
    import com.github.jonathanxd.dracon.cache.FileRevisionCache
    import com.github.jonathanxd.dracon.cache.FileStatusCache
    import com.github.jonathanxd.dracon.cache.PijulLogEntryCache
    import com.github.jonathanxd.dracon.cache.PijulLogEntryChangeCache
    import com.github.jonathanxd.dracon.i18n.DraconBundle
    import com.github.jonathanxd.dracon.pijul.NonZeroExitStatusCode
    import com.github.jonathanxd.dracon.pijul.Pijul
    import com.github.jonathanxd.dracon.pijul.pijul
    import com.github.jonathanxd.dracon.util.wrapInHml
    import com.github.jonathanxd.dracon.vcs.DraconVcsUtil
    import com.github.jonathanxd.dracon.vcs.NotificationIds
    import com.github.jonathanxd.dracon.vfs.DraconVfsUtil
    import com.intellij.openapi.actionSystem.AnActionEvent
    import com.intellij.openapi.actionSystem.CommonDataKeys
    import com.intellij.openapi.components.service
    import com.intellij.openapi.fileChooser.FileChooser
    import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
    import com.intellij.openapi.progress.ProgressIndicator
    import com.intellij.openapi.progress.Task
    import com.intellij.openapi.project.DumbAwareAction
    import com.intellij.openapi.project.ProjectManager
    import com.intellij.openapi.ui.Messages
    import com.intellij.openapi.vcs.ProjectLevelVcsManager
    import com.intellij.openapi.vcs.VcsNotifier
    import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager
    import com.intellij.vcsUtil.VcsUtil
    class PijulInvalidateCaches: DumbAwareAction() {
    override fun actionPerformed(e: AnActionEvent) {
    val project = e.getData(CommonDataKeys.PROJECT) ?: ProjectManager.getInstance().defaultProject
    object : Task.Backgroundable(project, DraconBundle.Dracon.refreshing) {
    override fun run(indicator: ProgressIndicator) {
    indicator.isIndeterminate = false
    indicator.text = DraconBundle.message("cache.invalidate.text")
    indicator.fraction = 0.0
    val fileRevisionCache = project.service<FileRevisionCache>()
    indicator.text2 = DraconBundle.message("cache.invalidate.current.text", "file revision cache")
    fileRevisionCache.invalidate()
    indicator.fraction = 0.25
    val fileStatusCache = project.service<FileStatusCache>()
    indicator.text2 = DraconBundle.message("cache.invalidate.current.text", "file status cache")
    fileStatusCache.invalidate()
    indicator.fraction = 0.50
    val logEntryCache = project.service<PijulLogEntryCache>()
    indicator.text2 = DraconBundle.message("cache.invalidate.current.text", "pijul log entry cache")
    logEntryCache.invalidate()
    indicator.fraction = 0.75
    val logEntryChangeCache = project.service<PijulLogEntryChangeCache>()
    indicator.text2 = DraconBundle.message("cache.invalidate.current.text", "pijul log entry changes cache")
    logEntryChangeCache.invalidate()
    indicator.fraction = 1.0
    indicator.text2 = DraconBundle.message("cache.invalidate.finish.text")
    }
    }.queue()
    }
    }