FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC com.github.jonathanxd.dracon.cmd.PijulCmd
package com.github.jonathanxd.dracon.vfsimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.FileStatusimport com.intellij.openapi.vcs.impl.FileStatusProviderimport com.intellij.openapi.vfs.VirtualFileclass PijulVirtualFileStatusProvider(val project: Project): FileStatusProvider {override fun getFileStatus(virtualFile: VirtualFile): FileStatus {TODO("Not yet implemented")}}
package com.github.jonathanxd.dracon.vfsimport com.intellij.openapi.vfs.VfsUtilimport com.intellij.openapi.vfs.VirtualFileobject DraconVfsUtil {fun refreshVfs(roots: List<VirtualFile>) {VfsUtil.markDirtyAndRefresh(false, true, false, *roots.toTypedArray());}}
package com.github.jonathanxd.dracon.vcsobject NotificationIds {const val FAILED_TO_INIT = "pijul.init.failed"}
package com.github.jonathanxd.dracon.vcsimport com.github.jonathanxd.dracon.vfs.DraconVfsUtilimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.ProjectLevelVcsManagerimport com.intellij.openapi.vcs.changes.VcsDirtyScopeManagerimport com.intellij.openapi.vfs.VirtualFileimport com.intellij.vcsUtil.VcsUtilobject DraconVcsUtil {fun refreshAfterInit(project: Project, root: VirtualFile) {DraconVfsUtil.refreshVfs(listOf(root))val manager = ProjectLevelVcsManager.getInstance(project)manager.directoryMappings = VcsUtil.addMapping(manager.directoryMappings, root.path, "Pijul")VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(root)}}
package com.github.jonathanxd.dracon.utilimport java.nio.file.Filesimport java.nio.file.Pathfun Path.existsOrNull(): Path? =if (Files.exists(this)) thiselse nullfun Path.isDirectoryOrNull(): Path? =if (Files.isDirectory(this)) thiselse nullfun Path.isRegularFileOrNull(): Path? =if (Files.isRegularFile(this)) thiselse nullfun Path.isExecutableOrNull(): Path? =if (Files.isExecutable(this)) thiselse null
package com.github.jonathanxd.dracon.utilimport com.intellij.openapi.util.text.StringUtilimport com.intellij.xml.util.XmlStringUtilfun String.wrapInHml() =XmlStringUtil.wrapInHtml(this)fun String.escapeXml() =StringUtil.escapeXmlEntities(this)
package com.github.jonathanxd.dracon.utilimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.flow.asFlowimport kotlinx.coroutines.flow.flowOnimport java.io.InputStreamfun InputStream.linesToFlow() = bufferedReader().lineSequence().asFlow().flowOn(Dispatchers.IO)
package com.github.jonathanxd.dracon.rootsimport com.github.jonathanxd.dracon.PijulVcsimport com.github.jonathanxd.dracon.pijul.Pijulimport com.intellij.openapi.vcs.VcsKeyimport com.intellij.openapi.vcs.VcsRootCheckerimport java.nio.file.Pathsclass PijulRootChecker : VcsRootChecker() {override fun getSupportedVcs(): VcsKey = PijulVcs.KEYoverride fun isRoot(path: String): Boolean =Pijul.isPijulRepository(Paths.get(path))override fun isVcsDir(dirName: String): Boolean =dirName.equals(".pijul", ignoreCase = true)}
package com.github.jonathanxd.dracon.pijul/*** An interface which provides introspection of [Pijul] `pristine` channels data.*/interface PijulPristine {}
package com.github.jonathanxd.dracon.pijulsealed class StatusCode(val code: Int)object SuccessStatusCode : StatusCode(0)data class NonZeroExitStatusCode(val exitCode: Int, val message: String) : StatusCode(exitCode)data class PijulOperationResult<R>(val operation: String,val statusCode: StatusCode,val result: R?)
package com.github.jonathanxd.dracon.pijulimport kotlinx.coroutines.flow.Flowclass PijulExecution(val regularStream: Flow<String>,val errorStream: Flow<String>,val status: Flow<Int>)
package com.github.jonathanxd.dracon.pijul/*** An interface which provides introspection of [Pijul] `changes` data.*/interface PijulChanges {}
package com.github.jonathanxd.dracon.pijulimport com.github.jonathanxd.dracon.util.existsOrNullimport com.github.jonathanxd.dracon.util.isDirectoryOrNullimport com.intellij.openapi.application.ApplicationManagerimport com.intellij.openapi.components.serviceimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.FilePathimport com.intellij.openapi.vcs.FileStatusimport com.intellij.openapi.vfs.LocalFileSystemimport com.intellij.openapi.vfs.VirtualFileimport com.intellij.util.concurrency.annotations.RequiresBackgroundThreadimport com.intellij.vcsUtil.VcsUtilimport java.nio.file.Filesimport java.nio.file.Pathimport java.nio.file.Pathsimport java.util.*val PIJUL_DIR = ".pijul"val PIJUL_INSTANCE get() = service<Pijul>()fun pijul(project: Project): Pijul = project.service()/*** Base interface for Pijul-IntelliJ communication.** This interface provides all methods for communication with [Pijul], as well some basic implementations* for operations that does not requires communication with [Pijul] backend.** However, some functionalities are not provided through this interface, for example, Pijul changes are stored in binary* format in the `.pijul/changes` directory and channels (like branches in git) are stored in `.pijul/pristine` directory,* as binary data as well, introspecting this data is done through [PijulChanges] and [PijulPristine] interfaces.*/interface Pijul {companion object {fun isPijulRepository(root: Path) =root.resolve(PIJUL_DIR).existsOrNull()?.isDirectoryOrNull() != null}fun findPijulDirectory(root: VirtualFile): VirtualFile? {var dir: Path? = Paths.get(VcsUtil.getFilePath(root).path)while (dir != null) {if (isPijulRepository(dir)) {return LocalFileSystem.getInstance().findFileByNioFile(dir)}dir = dir.parent}return null}fun isUnderPijul(root: VirtualFile): Boolean = this.findPijulDirectory(root) != null@RequiresBackgroundThreadfun init(project: Project, root: VirtualFile): PijulOperationResult<Unit>@RequiresBackgroundThreadfun add(project: Project, root: VirtualFile, paths: List<FilePath>): PijulOperationResult<Unit>@RequiresBackgroundThreadfun fileStatus(project: Project, file: VirtualFile): PijulOperationResult<FileStatus>}
package com.github.jonathanxd.dracon.logimport com.intellij.execution.ui.ConsoleViewContentTypeimport com.intellij.openapi.application.ApplicationManagerimport com.intellij.openapi.components.Serviceimport com.intellij.openapi.components.serviceimport com.intellij.openapi.project.Projectimport com.intellij.openapi.util.NlsSafeimport com.intellij.openapi.util.text.StringUtilimport com.intellij.openapi.vcs.ProjectLevelVcsManagerfun draconConsoleWriter(project: Project): DraconConsoleWriter =project.service()@Serviceclass DraconConsoleWriter(val project: Project) {fun logCommand(@NlsSafe command: String, @NlsSafe args: List<String>, @NlsSafe message: String) {this.log("$ $command ${args.joinToString(separator = " ")} > $message", ConsoleViewContentType.NORMAL_OUTPUT)}fun logCommandError(@NlsSafe command: String, @NlsSafe args: List<String>, @NlsSafe message: String) {this.log("$ $command ${args.joinToString(separator = " ")} > $message", ConsoleViewContentType.ERROR_OUTPUT)}fun log(@NlsSafe message: String) {this.log(message, ConsoleViewContentType.NORMAL_OUTPUT)}fun logError(@NlsSafe message: String) {this.log(message, ConsoleViewContentType.ERROR_OUTPUT)}fun log(@NlsSafe message: String, contentType: ConsoleViewContentType) {ProjectLevelVcsManager.getInstance(this.project).addMessageToConsoleWindow(message, contentType)}}
package com.github.jonathanxd.dracon.i18nimport com.github.jonathanxd.dracon.util.escapeXmlimport com.github.jonathanxd.dracon.util.wrapInHmlimport com.intellij.DynamicBundleimport org.jetbrains.annotations.Nlsimport org.jetbrains.annotations.PropertyKeyimport java.util.function.Supplierconst val BUNDLE = "messages.DraconBundle"object DraconBundle : DynamicBundle(BUNDLE) {fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): @Nls String {return this.getMessage(key, *params)}fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): Supplier<String> {return this.getLazyMessage(key, *params)}object Init {val titleget() = message("init.title")val descriptionget() = message("init.description")val errorget() = message("init.error")fun alreadyUnderPijul(arg: Any) =message("init.warning.already.under.pijul", arg)fun alreadyUnderPijulForHtml(arg: String) =message("init.warning.already.under.pijul", arg.escapeXml()).wrapInHml()}object Dracon {val nameSupplierget() = DraconBundle.messagePointer("dracon.vcs.name")val mnemonicget() = DraconBundle.message("dracon.vcs.name.with.mnemonic")val refreshingget() = message("dracon.refresh")}}
package com.github.jonathanxd.dracon.executorimport com.intellij.dvcs.ui.DvcsBundleimport com.intellij.openapi.util.Keyimport com.intellij.openapi.vcs.changes.CommitContextimport com.intellij.openapi.vcs.changes.CommitExecutorimport com.intellij.openapi.vcs.changes.CommitSessionimport com.intellij.vcs.commit.commitPropertyimport org.jetbrains.annotations.Nlsprivate val IS_PUSH_AFTER_COMMIT_KEY = Key.create<Boolean>("Pijul.Commit.IsPushAfterCommit")internal var CommitContext.isPushAfterCommit: Boolean by commitProperty(IS_PUSH_AFTER_COMMIT_KEY)class PijulCommitAndPushExecutor : CommitExecutor {@Nlsoverride fun getActionText(): String = DvcsBundle.message("action.commit.and.push.text")override fun useDefaultAction(): Boolean = falseoverride fun getId(): String = IDoverride fun createCommitSession(commitContext: CommitContext): CommitSession {commitContext.isPushAfterCommit = truereturn CommitSession.VCS_COMMIT}companion object {internal const val ID = "Pijul.Commit.And.Push.Executor"}}
package com.github.jonathanxd.dracon.cmdimport com.github.jonathanxd.dracon.log.draconConsoleWriterimport com.github.jonathanxd.dracon.pijul.*import com.github.jonathanxd.dracon.util.existsOrNullimport com.github.jonathanxd.dracon.util.isExecutableOrNullimport com.github.jonathanxd.dracon.util.isRegularFileOrNullimport com.github.jonathanxd.dracon.util.linesToFlowimport com.intellij.openapi.components.Serviceimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.FilePathimport com.intellij.openapi.vcs.FileStatusimport com.intellij.openapi.vfs.VirtualFileimport com.intellij.util.concurrency.annotations.RequiresBackgroundThreadimport com.intellij.vcsUtil.VcsFileUtilimport com.intellij.vcsUtil.VcsUtilimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.delayimport kotlinx.coroutines.flow.*import kotlinx.coroutines.runBlockingimport java.io.Fileimport java.nio.file.Filesimport java.nio.file.Pathimport java.nio.file.Paths/*** Command-line based implementation of [Pijul] interface.** An IPC (Inter Process Communication) version of [Pijul] could be implemented in the future,* however, an command-line based is enough for now.*/class PijulCmd(val project: Project) : Pijul {@RequiresBackgroundThreadoverride fun init(project: Project, root: VirtualFile): PijulOperationResult<Unit> {val path = Paths.get(VcsUtil.getFilePath(root).path)val execution = this.execPijul(project, path, listOf("init", path.toString()))return this.doExecution("init", execution)}@RequiresBackgroundThreadoverride fun add(project: Project, root: VirtualFile, paths: List<FilePath>): PijulOperationResult<Unit> {val path = Paths.get(VcsUtil.getFilePath(root).path)val results = mutableListOf<PijulOperationResult<Unit>>()if (paths.isEmpty()) {val execution = this.execPijul(project, path, listOf("add", "-r") + root.path)results + this.doExecution("add", execution)} else {for (pathList in VcsFileUtil.chunkPaths(root, paths)) {val execution = this.execPijul(project, path, listOf("add", "-r") + pathList)results + this.doExecution("add", execution)}}for (result in results) {if (result.statusCode !is SuccessStatusCode)return result}return PijulOperationResult("add", SuccessStatusCode, Unit)}@RequiresBackgroundThreadoverride fun fileStatus(project: Project, file: VirtualFile): PijulOperationResult<FileStatus> {}fun doExecution(name: String, execution: PijulExecution): PijulOperationResult<Unit> {val status = runBlocking(Dispatchers.IO) {execution.status.first()}return if (status == 0) {PijulOperationResult(name, SuccessStatusCode, Unit)} else {val error = runBlocking(Dispatchers.IO) {execution.errorStream.toList().joinToString("\n")}PijulOperationResult(name, NonZeroExitStatusCode(status, error), Unit)}}/*** TODO: Support configurable Pijul path.** Executes a pijul command and waits for the output status*/@RequiresBackgroundThreadprivate fun execPijul(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 {while (process.isAlive)delay(1000L)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))}private fun findPijul(): String {// Works in Windows, Linux and MacOS when Pijul is installed with cargo.val localPijul = this.findCargoPijul()// For Homebrew/Linuxbrew installationsval brewPijul = this.findBrewPijul()// For *nix only.val usrBinPijul = Paths.get(File.pathSeparator, "usr", "bin", "pijul").asPijulExecutableStringOrNull()val usrLocalBinPijul = Paths.get(File.pathSeparator, "usr", "local", "bin", "pijul").asPijulExecutableStringOrNull()val binPijul = Paths.get(File.pathSeparator, "bin", "pijul").asPijulExecutableStringOrNull()// For Windows only.// Windows could download Pijul binaries from https://github.com/boringcactus/pijul-windows-builds/releases/latest// However, Dracon plugin could not detect Pijul outside from these directories, for the cases where// Pijul is not in these locations, a search through $PATH variable will be made, so if the $PATH is correctly// configured to point to Pijul executable, then it will be found, this applies to all OSes.val programFiles = Paths.get("C:"+ File.pathSeparator, "Program Files", "pijul", "pijul.exe").asPijulExecutableStringOrNull()val programFiles86 = Paths.get("C:"+ File.pathSeparator, "Program Files (x86)", "pijul", "pijul.exe").asPijulExecutableStringOrNull()return localPijul?: brewPijul?: usrBinPijul?: usrLocalBinPijul?: binPijul?: programFiles?: programFiles86?: this.findExecutableOnPath("pijul")?: "pijul"}private fun Path.asPijulExecutableStringOrNull(): String? =this.existsOrNull()?.isRegularFileOrNull()?.isExecutableOrNull()?.toAbsolutePath()?.toString()private fun findCargoPijul(): String? {return System.getProperty("user.dir")?.let {Paths.get(it, ".cargo", "bin", "pijul")}?.asPijulExecutableStringOrNull()}private fun findBrewPijul(): String? {return System.getenv("HOMEBREW_PREFIX")?.ifBlank { null }?.ifEmpty { null }?.let {Paths.get(it, "bin", "pijul")}?.asPijulExecutableStringOrNull()}fun findExecutableOnPath(name: String): String? {for (dirname in System.getenv("PATH").split(File.pathSeparator)) {val path = Paths.get(dirname, name)if (Files.isRegularFile(path) && Files.isExecutable(path)) {return path.toAbsolutePath().toString()}}return null}}
package com.github.jonathanxd.dracon.actionsimport com.github.jonathanxd.dracon.NAMEimport com.github.jonathanxd.dracon.pijulVcsimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.AbstractVcsimport com.intellij.openapi.vcs.actions.StandardVcsGroupclass PijulMenu : StandardVcsGroup() {override fun getVcs(project: Project): AbstractVcs =pijulVcs(project)override fun getVcsName(project: Project): String = NAME}
package com.github.jonathanxd.dracon.actionsimport com.github.jonathanxd.dracon.i18n.DraconBundleimport com.github.jonathanxd.dracon.pijul.NonZeroExitStatusCodeimport com.github.jonathanxd.dracon.pijul.pijulimport com.github.jonathanxd.dracon.util.wrapInHmlimport com.github.jonathanxd.dracon.vcs.DraconVcsUtilimport com.github.jonathanxd.dracon.vcs.NotificationIdsimport com.github.jonathanxd.dracon.vfs.DraconVfsUtilimport com.intellij.openapi.actionSystem.AnActionEventimport com.intellij.openapi.actionSystem.CommonDataKeysimport com.intellij.openapi.fileChooser.FileChooserimport com.intellij.openapi.fileChooser.FileChooserDescriptorFactoryimport com.intellij.openapi.progress.ProgressIndicatorimport com.intellij.openapi.progress.Taskimport com.intellij.openapi.project.DumbAwareActionimport com.intellij.openapi.project.ProjectManagerimport com.intellij.openapi.ui.Messagesimport com.intellij.openapi.vcs.ProjectLevelVcsManagerimport com.intellij.openapi.vcs.VcsNotifierimport com.intellij.openapi.vcs.changes.VcsDirtyScopeManagerimport com.intellij.vcsUtil.VcsUtilclass PijulInit: DumbAwareAction() {override fun actionPerformed(e: AnActionEvent) {val project = e.getData(CommonDataKeys.PROJECT) ?: ProjectManager.getInstance().defaultProjectval fcd = FileChooserDescriptorFactory.createSingleFileDescriptor()fcd.isShowFileSystemRoots = truefcd.title = DraconBundle.Init.titlefcd.description = DraconBundle.Init.descriptionfcd.isHideIgnored = falseval baseDir = e.getData(CommonDataKeys.VIRTUAL_FILE)?.let { if (it.isDirectory) null else it } ?: project.baseDirFileChooser.chooseFile(fcd, project, baseDir) { root ->if (pijul(project).isUnderPijul(root)) {val dialog = Messages.showYesNoCancelDialog(project,DraconBundle.Init.alreadyUnderPijulForHtml(root.presentableUrl),DraconBundle.Init.title,Messages.getWarningIcon())if (dialog != Messages.YES) {return@chooseFile}}object : Task.Backgroundable(project, DraconBundle.Dracon.refreshing) {override fun run(indicator: ProgressIndicator) {indicator.isIndeterminate = trueval init = pijul(project).init(project, root)if (init.statusCode is NonZeroExitStatusCode) {VcsNotifier.getInstance(this.project).notifyError(NotificationIds.FAILED_TO_INIT,DraconBundle.Init.error,init.statusCode.message.wrapInHml(),true)}if (project.isDefault) {return}// Refresh VCS?DraconVcsUtil.refreshAfterInit(project, root)}}.queue()}}}
package com.github.jonathanxd.dracon.actionsimport com.github.jonathanxd.dracon.executor.PijulCommitAndPushExecutorimport com.intellij.dvcs.ui.DvcsBundleimport com.intellij.dvcs.commit.getCommitAndPushActionNameimport com.intellij.openapi.actionSystem.AnActionEventimport com.intellij.openapi.vcs.VcsDataKeysimport com.intellij.openapi.vcs.changes.actions.BaseCommitExecutorActionimport com.intellij.vcs.commit.CommitWorkflowHandlerimport com.intellij.vcs.commit.NonModalCommitWorkflowHandlerclass PijulCommitAndPushExecutorAction : BaseCommitExecutorAction() {init {templatePresentation.setText(DvcsBundle.messagePointer("action.commit.and.push.text"))}override fun update(e: AnActionEvent) {// update presentation before synchronizing its state with buttonval workflowHandler = e.getData(VcsDataKeys.COMMIT_WORKFLOW_HANDLER)if (workflowHandler != null) {e.presentation.text = workflowHandler.getCommitAndPushActionName()}super.update(e)}override val executorId: String = PijulCommitAndPushExecutor.ID}
package com.github.jonathanxd.dracon.actionsimport com.github.jonathanxd.dracon.pijul.pijulimport com.github.jonathanxd.dracon.pijulVcsimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.AbstractVcsimport com.intellij.openapi.vcs.FilePathimport com.intellij.openapi.vcs.FileStatusimport com.intellij.openapi.vcs.changes.actions.ScheduleForAdditionActionExtensionimport com.intellij.openapi.vfs.VirtualFileclass PijulAddExtension : ScheduleForAdditionActionExtension {override fun getSupportedVcs(project: Project): AbstractVcs = pijulVcs(project)override fun isStatusForAddition(status: FileStatus): Boolean {// TODO: Change check after fully integrated.return true/*status === FileStatus.MODIFIED ||status === FileStatus.MERGED_WITH_CONFLICTS ||status === FileStatus.ADDED ||status === FileStatus.DELETED ||status === FileStatus.IGNORED*/}override fun doAddFiles(project: Project, vcsRoot: VirtualFile, paths: List<FilePath>, containsIgnored: Boolean) {pijul(project).add(project, vcsRoot, paths)}}
package com.github.jonathanxd.draconimport com.github.jonathanxd.dracon.i18n.DraconBundleimport com.intellij.openapi.progress.ProgressManagerimport com.intellij.openapi.project.Projectimport com.intellij.openapi.vcs.AbstractVcsimport com.intellij.openapi.vcs.ProjectLevelVcsManagerimport com.intellij.openapi.vcs.VcsTypeimport com.intellij.openapi.vfs.VirtualFileconst val NAME = "Pijul"const val ID = "pijul"val DISPLAY_NAME_SUPPLIER = DraconBundle.Dracon.nameSupplierfun pijulVcs(project: Project): PijulVcs {val vcs = ProjectLevelVcsManager.getInstance(project).findVcsByName(NAME) as PijulVcsProgressManager.checkCanceled()return vcs}class PijulVcs(project: Project) : AbstractVcs(project, NAME) {override fun getDisplayName(): String =DISPLAY_NAME_SUPPLIER.get()override fun getType(): VcsType =VcsType.distributedoverride fun isVersionedDirectory(dir: VirtualFile?): Boolean {return super.isVersionedDirectory(dir)}override fun getShortNameWithMnemonic(): String =DraconBundle.Dracon.mnemoniccompanion object {val KEY = createKey(NAME)}}