ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
7L5LODGZ7AN4ZULDJZMLALD7PL6E57VZSNNSG67SFJARUJGCT47QC
GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC
MTPTFTHGAOKQGRUDXC55AM6XJHZZZ5EF6FPVXKFVCUYVXJNEANYQC
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC
EAGIDXOLFTHZMZ77ZWAM7MVVUBBXJMZD7RZUNBHJPYRFGGKILGVAC
OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC
6CR2EFUN7JXFHCBTNX3WWOOP4WFOCFO6KSPEBN6V6J5HFZO2LHNQC
FRFFQV7VNYKGCA7ZAOSRPC2HHYTAIZ6AGGR7A5QEV6QPAQGFDYGAC
Q7FXTHVUPVAFMNY277C3NFJO3VXLZU5G6C6UYSD5QPURHSG3A7OQC
QXUEMZ3B2FUHFUC7ZZHJMH5FVWLEMEYXUMFA6JNXTJKIVZNMRIOAC
5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
val change = changesMap.computeIfAbsent(rootPath) {
pijul(this.project).diff(this.project, root).result
} ?: continue
val head = this.logEntryChangeCache.loadRevision(rootAbsolute) {
pijul(this.project).latestRevisionNumber(this.project, rootPath)
}.result ?: continue
package com.github.jonathanxd.dracon.pijul
import com.intellij.openapi.vcs.FileStatus
import java.nio.file.Path
data class PijulFilesStatus(
val changed: Map<Path, List<FileStatus>>,
val tracked: List<Path>
)
data class Author(val name: String?, val fullName: String?, val email: String?): Serializable
data class Author(val name: String?, val fullName: String?, val email: String?): Serializable {
companion object {
private const val serialVersionUID: Long = 1L
}
}
package com.github.jonathanxd.dracon.listeners
import com.github.jonathanxd.dracon.cache.FileStatusCache
import com.github.jonathanxd.dracon.cache.PijulLogEntryChangeCache
import com.intellij.openapi.components.service
import com.intellij.openapi.externalSystem.autoimport.AsyncFileChangeListenerBase
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.AsyncFileListener
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.vcsUtil.VcsUtil
import java.nio.file.Paths
import java.util.concurrent.CopyOnWriteArrayList
class PijulAsyncFileListener(val project: Project) : AsyncFileChangeListenerBase(), AsyncFileListener {
private val fileStatusCache by lazy { this.project.service<FileStatusCache>() }
private val pijulLogEntryChangeCache by lazy { this.project.service<PijulLogEntryChangeCache>() }
override fun apply() {
}
override fun init() {
}
override fun isRelevant(file: VirtualFile, event: VFileEvent): Boolean {
return ProjectRootManager.getInstance(this.project).fileIndex.isInContent(file)
}
override fun updateFile(file: VirtualFile, event: VFileEvent) {
if (ProjectRootManager.getInstance(this.project).fileIndex.isInContent(file)) {
this.fileStatusCache.unload(Paths.get(VcsUtil.getFilePath(file).path))
this.pijulLogEntryChangeCache.unload(Paths.get(VcsUtil.getFilePath(file).path))
}
}
}
class PijulVcsContext(val project: Project) {
val root by lazy {
class PijulVcsContext(val project: Project): Disposable {
init {
VirtualFileManager.getInstance().addAsyncFileListener(
PijulAsyncFileListener(project),
this
)
}
override fun dispose() {
}
val root: Path get() {
ProjectLevelVcsManager.getInstance(this.project).getRootsUnderVcs(pijulVcs(this.project))
.first()
.toNioPath()
return ProjectLevelVcsManager.getInstance(this.project).getRootsUnderVcs(pijulVcs(this.project))
.firstOrNull()
?.toNioPath() ?: Paths.get(project.basePath!!)
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.future.asDeferred
import kotlinx.coroutines.future.await
import kotlinx.coroutines.runBlocking
import java.io.File
import java.net.ServerSocket
val fileStatusBasedInPijulLs = this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, rootPath, listOf("ls"), delay = 10L)) {
val fileStatusBasedInPijulLs =
this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, rootPath, listOf("ls"), log = false)) {
}
}
@OptIn(ExperimentalPathApi::class)
@RequiresBackgroundThread
override fun fileStatus(project: Project, file: Path): PijulOperationResult<FileStatus> {
val root = project.service<PijulVcsContext>().root
val execution = this.createPainlessExecPijulOperation(project, root, listOf("diff", "--json"))
val fileStatusBasedInPijulDiff = this.doExecutionWithMapper("file_status", execution) {
try {
val changes = PijulDiffJson.parseJson(it)
val changeMap = changes.toFileStatusMap()
val fPath = file.relativeTo(root).toString()
if (fPath.isEmpty()) {
FileStatus.SUPPRESSED
} else {
changeMap[fPath]?.firstOrNull()
}
} catch(t: Throwable) {
logger<PijulCmd>().error(t)
null
}
}
val fileStatusBasedInPijulLs =
this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, root, listOf("ls"), log = false)) {
val trackedFiles = it.split("\n")
val fPath = file.relativeTo(root).toString()
if (trackedFiles.contains(fPath)) {
FileStatus.NOT_CHANGED
} else {
FileStatus.UNKNOWN
}
}
return if (fileStatusBasedInPijulDiff.statusCode !is SuccessStatusCode
|| fileStatusBasedInPijulDiff.result == null
|| fileStatusBasedInPijulDiff.result == FileStatus.SUPPRESSED) {
fileStatusBasedInPijulLs
} else {
fileStatusBasedInPijulDiff
}
}
override fun fileStatusMap(project: Project): PijulOperationResult<PijulFilesStatus> {
val root = project.service<PijulVcsContext>().root
val fileStatusMap = mutableMapOf<Path, List<FileStatus>>()
val tracked = mutableListOf<Path>()
val execution = this.createPainlessExecPijulOperation(project, root, listOf("diff", "--json"))
val fileStatusBasedInPijulDiff = this.doExecutionWithMapper("file_status", execution) {
try {
val changes = PijulDiffJson.parseJson(it)
val changeMap = changes.toFileStatusMap()
for (c in changeMap) {
fileStatusMap[root.resolve(c.key)] = c.value
}
} catch(t: Throwable) {
logger<PijulCmd>().error(t)
null
}
}
val fileStatusBasedInPijulLs =
this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, root, listOf("ls"), log = false)) {
it.split("\n").mapTo(tracked) {
root.resolve(it)
}
}
if (fileStatusBasedInPijulDiff.statusCode !is SuccessStatusCode) {
return fileStatusBasedInPijulDiff as PijulOperationResult<PijulFilesStatus>
val rollback = this.doExecution(
"rollback_$rollbackHash",
this.createPainlessExecPijulOperation(project, root, listOf("unrecord", "--reset", rollbackHash))
)
if (rollback.statusCode !is SuccessStatusCode) {
return rollback as PijulOperationResult<Boolean>
}
val rollback = this.doExecution(
"rollback_from_${hashList[0]}_until_$hash",
this.createPainlessExecPijulOperation(project, root, listOf("unrecord", "--reset") + rollbacks)
)
entry.hunks.filterIsInstance<HunkWithPath>().map { hunk ->
entry.hunks.filterIsInstance<HunkWithPath>().groupBy { it.resolvePath(ctx.root) }.map { (p, hunks) ->
val hunk = hunks.firstOrNull { it is EditHunk }
?: hunks.firstOrNull { it is ReplacementHunk }
?: hunks.firstOrNull { it is FileDelHunk }
?: hunks.firstOrNull { it is FileAddHunk }
?: hunks.first()
package com.github.jonathanxd.dracon.cache
import com.github.jonathanxd.dracon.context.PijulVcsContext
import com.github.jonathanxd.dracon.log.PijulLogEntry
import com.github.jonathanxd.dracon.pijul.PijulOperationResult
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.github.jonathanxd.dracon.revision.PijulRevisionNumber
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.relativeTo
@Suppress("UnstableApiUsage")
@OptIn(ExperimentalPathApi::class)
@Service
class PijulLogEntryChangeCache(val project: Project) {
private val dataCache = DataCache<String, PijulLogEntry>(project, "path_change")
private val revisionDataCache = DataCache<String, PijulRevisionNumber>(project, "revision")
fun load(path: Path, compute: () -> PijulOperationResult<PijulLogEntry>): PijulOperationResult<PijulLogEntry> {
val ctx = this.project.service<PijulVcsContext>()
val relativePath = path.relativeTo(ctx.root).toString()
return this.dataCache.queryOrLoad(relativePath,
{ compute() },
{ it.statusCode is SuccessStatusCode && it.result != null },
{it.result!!},
{ PijulOperationResult("file_changes", SuccessStatusCode, it) }
)
}
fun loadRevision(path: Path, compute: () -> PijulOperationResult<PijulRevisionNumber>): PijulOperationResult<PijulRevisionNumber> {
val ctx = this.project.service<PijulVcsContext>()
val relativePath = path.relativeTo(ctx.root).toString()
return this.revisionDataCache.queryOrLoad(relativePath,
{ compute() },
{ it.statusCode is SuccessStatusCode },
{it.result!!},
{ PijulOperationResult("revision", SuccessStatusCode, it) }
)
}
fun unload(path: Path) {
val ctx = this.project.service<PijulVcsContext>()
val relativePath = path.relativeTo(ctx.root).toString()
this.dataCache.unload(relativePath)
this.dataCache.unload("")
this.revisionDataCache.unload(relativePath)
this.revisionDataCache.unload("")
}
}
private val map = ConcurrentHashMap<String, PijulLogEntry>()
private val cachePath = project.getProjectDataPath("com.jonathanxd.dracon")
private val cacheFile = cachePath.resolve("log.obj")
private val dataCache = DataCache<String, PijulLogEntry>(project, "change")
init {
Files.createDirectories(cachePath)
if (Files.exists(this.cacheFile)) {
try {
this.map.putAll(this.cacheFile.toPijulLogEntriesMap())
} catch (e: Throwable) {
Files.delete(this.cacheFile)
}
}
}
fun queryOrCompute(hash: String, compute: () -> PijulOperationResult<PijulLogEntry>): PijulOperationResult<PijulLogEntry> {
return if (!this.map.containsKey(hash)) {
val computeEntry = compute()
if (computeEntry.statusCode !is SuccessStatusCode) {
return computeEntry
}
this.map[hash] = computeEntry.result!!
this.cacheFile.createLogEntriesPersist(this.map)
computeEntry
} else {
PijulOperationResult("change-$hash", SuccessStatusCode, this.map[hash])
}
fun load(hash: String, compute: () -> PijulOperationResult<PijulLogEntry>): PijulOperationResult<PijulLogEntry> {
return this.dataCache.queryOrLoad(hash,
{ compute() },
{ it.statusCode is SuccessStatusCode },
{it.result!!},
{ PijulOperationResult("change-$hash", SuccessStatusCode, it) }
)
package com.github.jonathanxd.dracon.cache
import com.github.jonathanxd.dracon.context.PijulVcsContext
import com.github.jonathanxd.dracon.pijul.PijulOperationResult
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.github.jonathanxd.dracon.pijul.pijul
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FileStatus
import com.intellij.openapi.vcs.FileStatusFactory
import java.io.Serializable
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.relativeTo
@OptIn(ExperimentalPathApi::class)
@Service
class FileStatusCache(val project: Project) {
private val cache = DataCache<String, CacheablePijulFileStatus>(this.project, "file_status")
init {
// Pre-load status
this.cache.lock()
try {
val ctx = this.project.service<PijulVcsContext>()
val map = pijul(project).fileStatusMap(project)
if (map.result != null) {
for (change in map.result.changed) {
if (change.value.isNotEmpty()) {
val path = change.key.relativeTo(ctx.root)
this.cache.queryOrLoad(path.toString()) {
change.value.first().toPijul()
}
}
}
for (tracked in map.result.tracked) {
val path = tracked.relativeTo(ctx.root)
this.cache.queryOrLoad(path.toString()) {
FileStatus.NOT_CHANGED.toPijul()
}
}
}
} finally {
this.cache.unlock()
}
}
fun load(path: Path): PijulOperationResult<FileStatus> {
val ctx = this.project.service<PijulVcsContext>()
return this.cache.queryOrLoad(path.relativeTo(ctx.root).toString(),
{ pijul(project).fileStatus(project, path) },
{ it.statusCode is SuccessStatusCode },
{ it.result!!.toPijul() },
{ PijulOperationResult("file_status", SuccessStatusCode, it.toFileStatus()) }
)
}
fun unload(path: Path) {
val ctx = this.project.service<PijulVcsContext>()
val relativePath = path.relativeTo(ctx.root).toString()
this.cache.unload(relativePath)
}
}
data class CacheablePijulFileStatus(val id: String) : Serializable {
companion object {
private const val serialVersionUID: Long = 1
}
}
fun FileStatus.toPijul(): CacheablePijulFileStatus = CacheablePijulFileStatus(this.id)
fun CacheablePijulFileStatus.toFileStatus(): FileStatus =
try {
FileStatus::class.java.getDeclaredField(this.id).get(null) as FileStatus
} catch (t: Throwable) {
FileStatusFactory.getInstance().allFileStatuses.first {
it.id == this.id
}
}
package com.github.jonathanxd.dracon.cache
import com.github.jonathanxd.dracon.context.PijulVcsContext
import com.github.jonathanxd.dracon.revision.PijulVcsFileRevision
import com.github.jonathanxd.dracon.revision.loadStateInRevision
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import java.io.Serializable
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.concurrent.locks.ReentrantLock
@Service
class FileRevisionCache(val project: Project) {
private val cache = DataCache<FileRevisionRef, ByteArray>(this.project, "file_revision")
fun loadAsync(rev: PijulVcsFileRevision): 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)
}
}
}
data class FileRevisionRef(
val filePath: String,
val revision: String
): Serializable
package com.github.jonathanxd.dracon.cache
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.getProjectDataPath
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.locks.ReentrantLock
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
@Suppress("UnstableApiUsage")
class DataCache<K, V>(val project: Project,
val name: String) {
private val cachePath = project.getProjectDataPath("com.github.jonathanxd.dracon")
private val cacheFile = cachePath.resolve("$name.gz")
private val manager = DataCacheManager<K, V>(this.cacheFile)
private val lock = ReentrantLock()
private val updateExecutor = Executors.newCachedThreadPool()
private val inMemory = ConcurrentHashMap<K, V>()
init {
this.manager.load()?.let {
this.inMemory.putAll(it)
}
}
fun lock() {
this.lock.lock()
}
fun unlock() {
this.lock.unlock()
}
fun unload(key: K) {
this.lock.lock()
try {
this.inMemory.remove(key)
this.manager.write(this.inMemory)
} finally {
this.lock.unlock()
}
}
fun queryOrLoad(key: K, compute: (K) -> V): V {
this.lock.lock()
try {
return if (!this.inMemory.containsKey(key)) {
val computeEntry = compute(key)
this.inMemory[key] = computeEntry
this.manager.write(this.inMemory)
computeEntry
} else {
this.inMemory[key]!!
}
} finally {
this.lock.unlock();
}
}
fun <I> queryOrLoad(key: K,
compute: (K) -> I,
filter: (I) -> Boolean,
iMapper: (I) -> V,
vMapper: (V) -> I): I {
this.lock.lock()
try {
return if (!this.inMemory.containsKey(key)) {
val computeEntry = compute(key)
if (!filter(computeEntry)) {
return computeEntry
}
this.inMemory[key] = iMapper(computeEntry!!)
this.manager.write(this.inMemory)
computeEntry
} else {
vMapper(this.inMemory[key]!!)
}
} finally {
this.lock.unlock();
}
}
fun queryOrLoadAsync(key: K, compute: (K) -> V): CompletableFuture<V> {
if (this.inMemory.containsKey(key)) {
return CompletableFuture.completedFuture(this.inMemory[key]!!)
} else {
return CompletableFuture.supplyAsync({
this.lock.lock()
try {
val computeEntry = compute(key)
this.inMemory[key] = computeEntry
this.manager.write(this.inMemory)
computeEntry
} finally {
this.lock.unlock()
}
}, this.updateExecutor)
}
}
fun <I> queryOrLoadAsync(key: K,
compute: (K) -> I,
filter: (I) -> Boolean,
iMapper: (I) -> V,
vMapper: (V) -> I): CompletableFuture<I> {
if (this.inMemory.containsKey(key)) {
return CompletableFuture.completedFuture(vMapper(this.inMemory[key]!!))
} else {
return CompletableFuture.supplyAsync({
this.lock.lock()
try {
val computeEntry = compute(key)
if (!filter(computeEntry)) {
computeEntry
} else {
this.inMemory[key] = iMapper(computeEntry!!)
this.manager.write(this.inMemory)
computeEntry
}
} finally {
this.lock.unlock()
}
}, this.updateExecutor)
}
}
}
class DataCacheManager<K, V>(val path: Path) {
init {
Files.createDirectories(this.path.parent)
}
fun load(): Map<K, V>? {
if (!Files.exists(this.path)) return null
try {
Files.newInputStream(this.path).use { stream ->
GZIPInputStream(stream).use { gz ->
ObjectInputStream(gz).use { reader ->
return reader.readObject() as Map<K, V>
}
}
}
} catch (t: Throwable) {
t.printStackTrace()
Files.delete(this.path)
return null
}
}
fun write(data: Map<K, V>) {
Files.newOutputStream(this.path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING).use { writer ->
GZIPOutputStream(writer).use { compress ->
ObjectOutputStream(compress).use { oos ->
oos.writeObject(data)
}
}
}
}
}