RE4EKNSLYGCITZZEOPIRJAWTKIONGP7IY6S77BQO7JQL2CK27RZAC
OMZXJL6QA6INENIEAARSWYFHOPMLTP4WRCVI646GQVJVWCH3LENQC
37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC
FRFFQV7VNYKGCA7ZAOSRPC2HHYTAIZ6AGGR7A5QEV6QPAQGFDYGAC
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC
2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC
6CR2EFUN7JXFHCBTNX3WWOOP4WFOCFO6KSPEBN6V6J5HFZO2LHNQC
Q7FXTHVUPVAFMNY277C3NFJO3VXLZU5G6C6UYSD5QPURHSG3A7OQC
OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC
Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
MTPTFTHGAOKQGRUDXC55AM6XJHZZZ5EF6FPVXKFVCUYVXJNEANYQC
if (Files.exists(tmpTarget.resolve(".pijul"))) {
val ls = pijul(project).trackedFiles(project, tmpTarget)
if (ls.statusCode !is SuccessStatusCode) {
throw IllegalStateException("Failed to load state of all files in revision $revisionHash during reset. $ls")
}
i.fraction += 0.0001
val trackedFiles = ls.result!!
trackedFiles.forEachWithProgress(i) { it, indic ->
indic.text2 = DraconBundle.message("index.item.description.text", it.toString())
if (Files.isRegularFile(it)) {
pathToRevisionState[it] = Files.readAllBytes(it)
}
}
return pathToRevisionState
}
fun loadStateInRevisionForAllFilesForFile(revisionHash: String,
project: Project,
root: Path,
file: Path): ByteArray {
val sha1 = DigestUtils.sha1Hex(revisionHash)
val tempDir = FileUtilRt.createTempDirectory("dracon_diffs-all-", sha1)
val tmpTarget = tempDir.toPath()
if (Files.exists(tmpTarget.resolve(".pijul"))) {
val relativeToRoot = file.relativeTo(root)
val relativeToTmpTarget = tmpTarget.resolve(relativeToRoot)
if (Files.exists(relativeToTmpTarget)) {
return Files.readAllBytes(relativeToTmpTarget)
}
}
copyFolder(root, tmpTarget, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)
val revisions = pijul(project).latestRevisionNumber(project, tmpTarget).result
val reset = pijul(project).reset(project, tmpTarget)
if (reset.statusCode !is SuccessStatusCode) {
throw IllegalStateException("Failed to load state of all files in revision $revisionHash during reset. $revisions")
}
if (revisions == null || revisions.hash != revisionHash) {
val rollbackOp = pijul(project).rollbackTo(revisionHash, project, tmpTarget)
if (rollbackOp.statusCode !is SuccessStatusCode) {
throw IllegalStateException("Failed to load state of all files in revision $revisionHash during unrecord. $rollbackOp")
}
}
try {
val relativeToRoot = file.relativeTo(root)
val relativeToTmpTarget = tmpTarget.resolve(relativeToRoot)
return if (Files.exists(relativeToTmpTarget)) {
Files.readAllBytes(relativeToTmpTarget)
} else {
ByteArray(0)
}
} finally {
//FileUtilRt.delete(tempDir)
}
}
@OptIn(ExperimentalPathApi::class)
throw IllegalStateException("Failed to load state of all files in revisions '$allRevisions' during reset. $reset")
IllegalStateException("Failed to load state of all files in revisions '$allRevisions' during reset. $reset").printStackTrace()
return revisionToPathToState
FileUtilRt.delete(tempDir)
//FileUtilRt.delete(tempDir)
}
}
@OptIn(ExperimentalPathApi::class)
fun loadFileStateInRevision(
revision: String,
project: Project,
root: Path,
cachePath: Path,
file: Path,
i: ProgressIndicator,
cacheAllRevisions: Boolean = true
): FileStateInCache {
val dir = resolveDirForRevision(cachePath, revision, createIfNotExists = false)
val fileRelativeToRoot = file.relativeTo(root)
if (!Files.exists(dir)) {
val allRevisions = pijul(project).allRevisions(project, root).result!!.map { it.hash }
val revisions = if (allRevisions[0] == revision) {
listOf(revision)
} else {
allRevisions.subList(0, allRevisions.indexOf(revision) + 1)
}
val cacheMap = createCacheForRevisionFromTo(
revisions,
project,
root,
cachePath,
i,
cacheAllRevisions
)
if (!cacheMap.containsKey(revision)) {
throw IllegalStateException("Failed to load state of all files in revision $revision")
}
}
val fileInCache = dir.resolve(fileRelativeToRoot)
return if (Files.exists(fileInCache) && Files.isRegularFile(fileInCache)) {
FileStateInCache(file, fileInCache, false, Files.readAllBytes(fileInCache))
} else {
FileStateInCache(file, fileInCache, true, ByteArray(0))
}
}
data class FileStateInCache(
val originalPath: Path,
val pathInCache: Path,
val deleted: Boolean,
val content: ByteArray
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FileStateInCache
if (originalPath != other.originalPath) return false
if (pathInCache != other.pathInCache) return false
if (deleted != other.deleted) return false
if (!content.contentEquals(other.content)) return false
return true
}
override fun hashCode(): Int {
var result = originalPath.hashCode()
result = 31 * result + pathInCache.hashCode()
result = 31 * result + deleted.hashCode()
result = 31 * result + content.contentHashCode()
return result
}
}
fun resolveDirForRevision(cachePath: Path, revision: String, createIfNotExists: Boolean = true): Path {
val path = cachePath.resolve(revision)
if (createIfNotExists)
Files.createDirectories(path)
return path
}
@OptIn(ExperimentalPathApi::class)
fun createCacheForRevisionFromTo(allRevisions: List<String>,
project: Project,
root: Path,
cachePath: Path,
i: ProgressIndicator,
cacheAllRevisions: Boolean = true): Map<String, Path> {
if (allRevisions.isEmpty()) {
throw IllegalArgumentException("Provided 'allRevisions' argument must not be empty.")
}
val revisionsToCache = allRevisions.toMutableList()
i.text2 = DraconBundle.message("index.check.text")
val revisionToPath = mutableMapOf<String, Path>()
var lastFoundCachedRevisionPath: Path? = null
var lastFoundCachedRevision: String? = null
for (rev in allRevisions) {
i.text2 = DraconBundle.message("index.check.rev.text", rev)
val path = resolveDirForRevision(cachePath, rev, createIfNotExists = false)
if (path.exists()) {
revisionsToCache.remove(rev)
lastFoundCachedRevisionPath = path
lastFoundCachedRevision = rev
revisionToPath["rev"] = path
}
}
i.text2 = DraconBundle.message("index.item.description.finish.text")
if (revisionsToCache.isEmpty()) {
return revisionToPath
}
if (!cacheAllRevisions) {
val last = revisionsToCache.last()
revisionsToCache.clear()
revisionsToCache.add(last)
}
val temporaryWorkingDirectory = cachePath.resolve(".tmp-work-dir-" + UUID.randomUUID().toString())
Files.createDirectories(temporaryWorkingDirectory)
if (lastFoundCachedRevision != null && lastFoundCachedRevisionPath != null) {
// Copies last found revision into temporaryWorkingDirectory
// This prevents from rolling back from revisions that are already cached.
copyFolder(lastFoundCachedRevisionPath, temporaryWorkingDirectory)
} else {
// Copy current .pijul working dir into temporaryWorkingDirectory
copyFolder(root, temporaryWorkingDirectory)
}
val revisions = pijul(project).allRevisions(project, temporaryWorkingDirectory)
if (revisions.statusCode !is SuccessStatusCode) {
throw IllegalStateException("Failed to load state of all files in revisions '$revisionsToCache' during all revisions hash retrieval. $revisions")
}
val pijulRevisions = revisions.result!!.map { it.hash }
val indexOfFirstRevision = pijulRevisions.indexOf(revisionsToCache[0])
if (indexOfFirstRevision == -1) {
throw IllegalArgumentException("Could not find revision ${revisionsToCache[0]} in Pijul repository!")
} else {
if (indexOfFirstRevision + revisionsToCache.size > pijulRevisions.size) {
throw IllegalArgumentException("There are more revisions to unrecord than the amount of recorded changes in Pijul Repository.")
} else {
val revisionSubList = pijulRevisions.subList(indexOfFirstRevision, indexOfFirstRevision + revisionsToCache.size)
if (revisionsToCache != revisionSubList) {
throw IllegalArgumentException("Revisions to load must sequentially match a sub sequence of revisions in Pijul repository. " +
"Changes found in pijul: $revisionSubList. Changes to unrecord: $revisionsToCache")
}
}
}
i.text2 = DraconBundle.message("index.reset.text")
val reset = pijul(project).reset(project, temporaryWorkingDirectory)
if (reset.statusCode !is SuccessStatusCode) {
IllegalStateException("Failed to load state of all files in revisions '$revisionsToCache' during reset. $reset").printStackTrace()
return revisionToPath
}
i.fraction += 0.0001
revisionsToCache.withIndex().toList().forEachWithProgress(i) { (index, rev), indicator ->
indicator.text2 = DraconBundle.message("index.revision.description.text", rev)
val resolvedPathForRevision = resolveDirForRevision(cachePath, rev, createIfNotExists = false)
if (Files.exists(resolvedPathForRevision)) {
deleteFilesInsideDirectory(temporaryWorkingDirectory)
copyFolder(resolvedPathForRevision, temporaryWorkingDirectory)
} else {
val rollback = pijul(project).rollbackTo(rev, project, temporaryWorkingDirectory)
if (rollback.statusCode !is SuccessStatusCode) {
throw IllegalStateException("Failed to load state of all files in revision '$rev' during rollback. $rollback")
}
/*if (index == 0) {
} else {
val unrecord = pijul(project).unrecord(project, temporaryWorkingDirectory, revisionsToCache[index - 1])
if (unrecord.statusCode !is SuccessStatusCode) {
throw IllegalStateException("Failed to load state of all files in revision '$rev' during unrecord. $unrecord")
}
}*/
indicator.text2 = DraconBundle.message("index.copy.text")
copyFolder(temporaryWorkingDirectory, resolvedPathForRevision)
}
revisionToPath[rev] = resolvedPathForRevision
}
i.text2 = DraconBundle.message("index.item.description.finish.text")
try {
return revisionToPath
} finally {
FileUtilRt.delete(temporaryWorkingDirectory.toFile())
Files.copy(file, target.resolve(source.relativize(file)), *options)
Files.copy(file, target.resolve(source.relativize(file)), *copyOptions)
return FileVisitResult.CONTINUE
}
})
}
fun deleteFilesInsideDirectory(path: Path) {
Files.walkFileTree(path, object : SimpleFileVisitor<Path>() {
override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
return if (dir != path) FileVisitResult.CONTINUE
else FileVisitResult.SKIP_SUBTREE
}
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
Files.delete(file)
package com.github.jonathanxd.dracon.log
import com.intellij.vcs.log.Hash
class PijulHash(val hash: String) : Hash {
override fun asString(): String = this.hash
override fun toShortString(): String = this.hash.substring(0, 10)
}
package com.github.jonathanxd.dracon.log
import com.intellij.vcs.log.Hash
class PijulHash(val hash: String) : Hash {
override fun asString(): String = this.hash
override fun toShortString(): String = this.hash.substring(0, 10)
}
package com.github.jonathanxd.dracon.handler
import com.github.jonathanxd.dracon.cache.FileRevisionCache
import com.intellij.openapi.components.serviceIfCreated
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectCloseHandler
class DraconCloseHandler: ProjectCloseHandler {
override fun canClose(project: Project): Boolean {
val fileRevisionCache = project.serviceIfCreated<FileRevisionCache>()
fileRevisionCache?.invalidate()
return true
}
}
import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.forEachWithProgress
import com.intellij.openapi.progress.withPushPop
import com.intellij.openapi.progress.*
class FileRevisionCache(val project: Project) : CacheService<FileRevisionRef, ByteArray> {
override val cache = DataCache<FileRevisionRef, ByteArray>(this.project, "file_revision")
class FileRevisionCache(val project: Project) {
//override val cache = DataCache<String, Map<String, ByteArray>>(this.project, "path_revision")
fun cachePath(): Path =
this.project.getProjectDataPath("com.github.jonathanxd.dracon").resolve("revision-cache")
return this.cache.queryOrLoadAsyncExpanded(fileRev) {
val revisions = loadStateInEveryRevisionForAllFiles(
listOf(rev.hash),
project,
root,
EmptyProgressIndicator()
)
Files.createDirectories(cache)
val completableFuture = CompletableFuture<ByteArray>()
val revisionsForHash = revisions[rev.hash]!!
val stringKeys = revisionsForHash.mapKeys { (k, _) -> k.filePathAsString() }
stringKeys[it.filePath]!! to stringKeys.map { (k, v) -> FileRevisionRef(k, rev.hash) to v }
ProgressManager.getInstance().run(object : Task.Backgroundable(
project,
DraconBundle.message("index.load.rev.for.text", rev.hash.substring(0, 10), file.fileName.toString()),
true
) {
override fun run(indicator: ProgressIndicator) {
indicator.isIndeterminate = false
try {
val revision = loadFileStateInRevision(
rev.hash,
project,
root,
cache,
file,
indicator,
project.service<PijulSettings>().isToCacheAllRevisions()
)
completableFuture.complete(revision.content)
} catch (t: Throwable) {
t.printStackTrace()
completableFuture.completeExceptionally(t)
}
}
})
return completableFuture.handle { t, u ->
u?.printStackTrace()
t ?: ByteArray(0)
stateMap.entries.forEachWithProgress(indic) { (path, bytes), indicator2 ->
indicator2.text2 =
DraconBundle.message("index.revision.file.description.text", path.toString(), rev)
val revRef = FileRevisionRef(path.filePathAsString(), rev)
this.cache.updateCache(revRef) {
bytes
}
this.cache.updateCache(rev) {
stateMap.mapKeys { (k, _) -> k.filePathAsString() }