FRFFQV7VNYKGCA7ZAOSRPC2HHYTAIZ6AGGR7A5QEV6QPAQGFDYGAC else null
else nullfun VirtualFile.path(): Path =this.toNioPath()fun VirtualFile.vcsRoot(project: Project): Path? =ProjectLevelVcsManager.getInstance(project).getVcsRootFor(this)?.toNioPath()fun FilePath.vcsRoot(project: Project): Path? =ProjectLevelVcsManager.getInstance(project).getVcsRootFor(this)?.toNioPath()
package com.github.jonathanxd.dracon.revisionimport com.github.jonathanxd.dracon.pijul.pijulimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.FilePathimport com.intellij.openapi.vfs.VirtualFileimport java.io.Fileimport java.io.IOExceptionimport java.nio.file.*import java.nio.file.attribute.BasicFileAttributesimport java.util.Comparatorimport kotlin.io.path.ExperimentalPathApiimport kotlin.io.path.relativeTo@OptIn(ExperimentalPathApi::class)fun loadStateInRevision(revisionHash: String,project: Project,root: Path,fileToResolve: FilePath): String {val tmpTarget = Paths.get(project.baseDir.path, ".idea", "dracon_diffs", revisionHash)if (Files.exists(tmpTarget)) {Files.walk(tmpTarget).use { walk ->walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete)}}Files.createDirectories(tmpTarget)copyFolder(root, tmpTarget, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)val revisions = pijul(project).latestRevisionNumber(project, tmpTarget).resultif (revisions != null && revisions.hash == revisionHash) {val rollbackOp = pijul(project).reset(project, tmpTarget)} else {val resetOp = pijul(project).reset(project, tmpTarget)val rollbackOp = pijul(project).rollbackTo(revisionHash, project, tmpTarget)}val relative = Paths.get(fileToResolve.path).relativeTo(root).toString()return Files.readString(tmpTarget.resolve(relative))}@Throws(IOException::class)fun copyFolder(source: Path, target: Path, vararg options: CopyOption) {Files.walkFileTree(source, object : SimpleFileVisitor<Path>() {@Throws(IOException::class)override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {if (dir.fileName.toString().equals(".idea", ignoreCase = true))return FileVisitResult.SKIP_SUBTREEFiles.createDirectories(target.resolve(source.relativize(dir)))return FileVisitResult.CONTINUE}@Throws(IOException::class)override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {Files.copy(file, target.resolve(source.relativize(file)), *options)return FileVisitResult.CONTINUE}})}
package com.github.jonathanxd.dracon.revisionimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.FilePathimport com.intellij.openapi.vcs.RepositoryLocationimport com.intellij.openapi.vcs.history.VcsFileRevisionimport com.intellij.openapi.vcs.history.VcsRevisionNumberimport java.nio.file.Pathimport java.util.*class PijulVcsFileRevision(val project: Project,val vcsRoot: Path,val filePath: FilePath,val revision: PijulRevisionNumber,val author_: String?,val message: String?,val branch: String?) : VcsFileRevision {override fun loadContent(): ByteArray =loadStateInRevision(this.revision.hash, this.project, vcsRoot, this.filePath).toByteArray(Charsets.UTF_8)override fun getContent(): ByteArray = this.loadContent()override fun getRevisionNumber(): VcsRevisionNumber = this.revisionoverride fun getRevisionDate(): Date = Date.from(this.revision.timestamp.toInstant())override fun getAuthor(): String? = this.author_override fun getCommitMessage(): String? = this.messageoverride fun getBranchName(): String? = this.branchoverride fun getChangedRepositoryPath(): RepositoryLocation? = null}
package com.github.jonathanxd.dracon.providerimport com.github.jonathanxd.dracon.log.draconConsoleWriterimport com.github.jonathanxd.dracon.pijul.diff.PijulDiffFromHistoryHandlerimport com.github.jonathanxd.dracon.pijul.pijulimport com.github.jonathanxd.dracon.revision.PijulRevisionNumberimport com.github.jonathanxd.dracon.revision.PijulVcsFileRevisionimport com.intellij.openapi.actionSystem.AnActionimport com.intellij.openapi.components.Serviceimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.FilePathimport com.intellij.openapi.vcs.ProjectLevelVcsManagerimport com.intellij.openapi.vcs.VcsExceptionimport com.intellij.openapi.vcs.history.*import com.intellij.openapi.vfs.VirtualFileimport com.intellij.util.ui.ColumnInfoimport javax.swing.JComponent@Serviceclass PijulHistoryProvider(val project: Project) : VcsHistoryProvider {override fun getUICustomization(session: VcsHistorySession?,forShortcutRegistration: JComponent?): VcsDependentHistoryComponents {return VcsDependentHistoryComponents(ColumnInfo.EMPTY_ARRAY, null, null)}override fun getAdditionalActions(refresher: Runnable?): Array<AnAction> {return emptyArray()}override fun isDateOmittable(): Boolean =falseoverride fun getHelpId(): String? = nulloverride fun createSessionFor(filePath: FilePath): VcsHistorySession? {// TODO: Support multiple-channelsval root = ProjectLevelVcsManager.getInstance(this.project).getVcsRootFor(filePath)!!val channels = pijul(this.project).channel(this.project, root)val currentChannel = channels.result?.channels?.firstOrNull { it.current }?.nameval history = pijul(this.project).log(this.project, root).map {it.entries.map {PijulVcsFileRevision(this.project,root.toNioPath(),filePath,PijulRevisionNumber(it.changeHash, it.date),it.authors.map { it.name }.firstOrNull(),it.message,currentChannel)}}return if (history.result != null) {PijulHistorySession(filePath, history.result.firstOrNull()?.revision, history.result)} else {draconConsoleWriter(this.project).logError("Failed to build history session")null}}override fun reportAppendableHistory(path: FilePath, partner: VcsAppendableHistorySessionPartner) {val root = ProjectLevelVcsManager.getInstance(this.project).getVcsRootFor(path)!!val emptySession = createSession(path, emptyList(), null)partner.reportCreatedEmptySession(emptySession)val channels = pijul(this.project).channel(this.project, root)val currentChannel = channels.result?.channels?.firstOrNull { it.current }?.namepijul(this.project).fileHistory(this.project,root,path,{partner.acceptRevision(PijulVcsFileRevision(this.project,root.toNioPath(),path,PijulRevisionNumber(it.changeHash, it.date),it.authors.map { it.name }.firstOrNull(),it.message,currentChannel))},{ partner.reportException(VcsException(it.toString())) })}override fun supportsHistoryForDirectories(): Boolean = trueoverride fun getHistoryDiffHandler(): DiffFromHistoryHandler =PijulDiffFromHistoryHandler(this.project)override fun canShowHistoryFor(file: VirtualFile): Boolean {return true// TODO?}private fun createSession(filePath: FilePath, revisions: List<VcsFileRevision>,number: VcsRevisionNumber?): VcsAbstractHistorySession {return PijulHistorySession(filePath, number, revisions)}inner class PijulHistorySession(val filePath: FilePath,number: VcsRevisionNumber?,revisions: List<VcsFileRevision>) :VcsAbstractHistorySession(revisions, number) {override fun calcCurrentRevisionNumber(): VcsRevisionNumber? {return try {val root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(filePath)!!val rev = pijul(project).latestRevisionNumber(project, root)return rev.result} catch (e: Throwable) {draconConsoleWriter(project).logError(e.stackTraceToString())null}}override fun getHistoryAsTreeProvider(): HistoryAsTreeProvider? {return null}override fun copy(): VcsHistorySession {return createSession(filePath, revisionList, currentRevisionNumber)}}/*override fun createFromCachedData(cacheable: Boolean?,revisions: MutableList<out VcsFileRevision>,filePath: FilePath,currentRevision: VcsRevisionNumber?): VcsAbstractHistorySession {TODO("Not yet implemented")}override fun getBaseVersionContent(filePath: FilePath?,processor: Processor<in String>?,beforeVersionId: String?): Boolean {TODO("Not yet implemented")}*/}
package com.github.jonathanxd.dracon.pijul.diffimport com.github.jonathanxd.dracon.content.PijulContentRevisionimport com.github.jonathanxd.dracon.revision.PijulVcsFileRevisionimport com.github.jonathanxd.dracon.util.vcsRootimport com.intellij.openapi.actionSystem.AnActionEventimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.FilePathimport com.intellij.openapi.vcs.FileStatusimport com.intellij.openapi.vcs.changes.Changeimport com.intellij.openapi.vcs.changes.CurrentContentRevisionimport com.intellij.openapi.vcs.history.BaseDiffFromHistoryHandlerimport com.intellij.openapi.vcs.history.DiffFromHistoryHandlerimport com.intellij.openapi.vcs.history.VcsFileRevisionclass PijulDiffFromHistoryHandler(val project: Project) : BaseDiffFromHistoryHandler<PijulVcsFileRevision>(project) {override fun getChangesBetweenRevisions(path: FilePath,rev1: PijulVcsFileRevision,rev2: PijulVcsFileRevision?): MutableList<Change> {val root = path.vcsRoot(project)!!return if (rev2 == null) {mutableListOf(Change(PijulContentRevision(root,path,rev1.revision,this.project),CurrentContentRevision.create(path),FileStatus.MODIFIED))} else {mutableListOf(Change(PijulContentRevision(root,path,rev1.revision,this.project),PijulContentRevision(root,path,rev2.revision,this.project),FileStatus.MODIFIED))}}override fun getAffectedChanges(path: FilePath, rev: PijulVcsFileRevision): MutableList<Change> {val root = path.vcsRoot(project)!!return mutableListOf(Change(PijulContentRevision(root,path,rev.revision,this.project),CurrentContentRevision.create(path),FileStatus.MODIFIED))}override fun getPresentableName(revision: PijulVcsFileRevision): String =revision.revision.hash}
* Retrieves change history*/@RequiresBackgroundThreadfun log(project: Project, root: Path): PijulOperationResult<PijulLog>/*** Retrieves change history*/@RequiresBackgroundThreadfun fileHistory(project: Project,root: VirtualFile,file: FilePath,consumer: (PijulLogEntry) -> Unit,errorConsumer: (PijulOperationResult<Unit>) -> Unit): PijulOperationResult<Unit>/**
@OptIn(ExperimentalPathApi::class)private fun loadStateInRevision(): String {val tmpTarget = Paths.get(this.project.baseDir.path, ".idea", "dracon_diffs", this.revision.hash)if (Files.exists(tmpTarget)) {Files.walk(tmpTarget).use { walk ->walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete)}}Files.createDirectories(tmpTarget)copyFolder(this.root, tmpTarget, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)val rollbackOp = pijul(project).reset(this.project, tmpTarget)val relative = Paths.get(filePath.path).relativeTo(this.root).toString()return Files.readString(tmpTarget.resolve(relative))}@Throws(IOException::class)fun copyFolder(source: Path, target: Path, vararg options: CopyOption) {Files.walkFileTree(source, object : SimpleFileVisitor<Path>() {@Throws(IOException::class)override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {if (dir.fileName.toString().equals(".idea", ignoreCase = true))return FileVisitResult.SKIP_SUBTREEFiles.createDirectories(target.resolve(source.relativize(dir)))return FileVisitResult.CONTINUE}@Throws(IOException::class)override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {Files.copy(file, target.resolve(source.relativize(file)), *options)return FileVisitResult.CONTINUE}})}
import com.github.jonathanxd.dracon.log.PijulLogimport com.github.jonathanxd.dracon.log.PijulLogEntryimport com.github.jonathanxd.dracon.log.draconConsoleWriterimport com.github.jonathanxd.dracon.log.parseChange
import com.github.jonathanxd.dracon.channel.ChannelInfoimport com.github.jonathanxd.dracon.channel.PijulChannelimport com.github.jonathanxd.dracon.log.*
val fileStatusBasedInPijulLs = this.doExecutionWithMapper("file_status_from_ls", this.execPijul(project, rootPath, listOf("ls"), delay = 10L)) {
val fileStatusBasedInPijulLs = this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, rootPath, listOf("ls"), delay = 10L)) {
val logHashExecution = this.execPijul(project, rootPath, listOf("log", "--hash-only"), delay = 10L)
return this.log(project, rootPath)}override fun log(project: Project, root: Path): PijulOperationResult<PijulLog> {val logHashExecution = this.createPainlessExecPijulOperation(project, root, listOf("log", "--hash-only"))
val change = this.doExecutionWithMapper("change-$hash", this.execPijul(project, rootPath, listOf("change", hash), delay = 10L)) {
val change = this.doExecutionWithMapper("change-$hash", this.createPainlessExecPijulOperation(project, root, listOf("change", hash))) {
@OptIn(ExperimentalPathApi::class)override fun fileHistory(project: Project,root: VirtualFile,file: FilePath,consumer: (PijulLogEntry) -> Unit,errorConsumer: (PijulOperationResult<Unit>) -> Unit): PijulOperationResult<Unit> {val rootPath = Paths.get(VcsUtil.getFilePath(root).path)val filePath = Paths.get(file.path).relativeTo(rootPath)
val logHashExecution = this.createExecPijulOperation(project, rootPath, listOf("log", "--hash-only"), delay = 10L)val hashes = this.doExecutionWithMapper("log--hash-only", logHashExecution) {it.lines()}if (hashes.statusCode !is SuccessStatusCode) {errorConsumer(hashes as PijulOperationResult<Unit>)return hashes as PijulOperationResult<Unit>} else {val hashList = hashes.result!!for (hash in hashList) {if (hash.isEmpty()) {break}val change = this.doExecutionWithMapper("change-$hash", this.createPainlessExecPijulOperation(project, rootPath, listOf("change", hash))) {it.parseChange(hash)}if (change.statusCode !is SuccessStatusCode) {errorConsumer(change as PijulOperationResult<Unit>)} else if (change.result != null) {var shouldConsume = falsevar isAdd = falsefor (hunk in change.result.hunks) {if (hunk is HunkWithPath) {if (filePath.toString().equals(hunk.resolvedPath, ignoreCase = true)) {if (hunk is FileAddHunk) {isAdd = true}shouldConsume = true}}}if (shouldConsume) {consumer(change.result)}if (isAdd) {break}}}}return hashes as PijulOperationResult<Unit>}
* Creates a [PijulExecution] operation that could be executed at any time. This operation uses Kotlin Coroutines* and can be executed immediately through [doExecution] or through [doExecutionWithMapper].** As this implementation depends on System Processes, a [delay] should be provided as the interval between* [Process.isAlive] check before trying to retrieve [Process.onExit]. Bigger values yields less resource* intensive operation, smaller values yields less input lag and better feed back but in cost of intensive* scheduling.*
private fun execPijul(project: Project,dir: Path,args: List<String>,delay: Long = 1000L): PijulExecution {
private fun createExecPijulOperation(project: Project,dir: Path,args: List<String>,delay: Long = 1000L): PijulExecution {
/*** Creates a [PijulExecution] operation that could be executed at any time. This operation uses Kotlin Coroutines* and can be executed immediately through [doExecution] or through [doExecutionWithMapper].** This implementation does not requires a delay value to be provided, like [createExecPijulOperation] does, instead* it uses the kotlin conversion from `CompletionStage` to `Coroutines` and awaits the process through [Process.onExit].** [doExecution] and [doExecutionWithMapper] does execution by scheduling task to [Dispatchers.IO], instead of Main Thread,* offloading the Process execution handling to a different scheduler. However, mapping operation of [doExecutionWithMapper]* is not offloaded from the caller context.**/@RequiresBackgroundThreadprivate fun createPainlessExecPijulOperation(project: Project,dir: Path,args: List<String>): PijulExecution {val process = ProcessBuilder().command(listOf(this.findPijul()) + args).directory(dir.toFile()).start()
val input = process.inputStreamval error = process.errorStreamreturn PijulExecution(input.linesToFlow().onEach {draconConsoleWriter(project).logCommand("pijul", args, it)},error.linesToFlow().onEach {draconConsoleWriter(project).logCommandError("pijul", args, it)},flow {process.onExit().await()val exit = process.exitValue()if (exit == 0) {draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")} else {draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")}emit(exit)}.flowOn(Dispatchers.IO))}
package com.github.jonathanxd.dracon.channeldata class PijulChannel(val current: Boolean, val name: String)
package com.github.jonathanxd.dracon.channeldata class ChannelInfo(val channels: List<PijulChannel>)