A7IL6I3VCB3F3QMYQFPXBL33DN5XDGGQPZIBZRHYSQBSIUBBFQJQC
37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC
ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
EAGIDXOLFTHZMZ77ZWAM7MVVUBBXJMZD7RZUNBHJPYRFGGKILGVAC
FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC
OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
FRFFQV7VNYKGCA7ZAOSRPC2HHYTAIZ6AGGR7A5QEV6QPAQGFDYGAC
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
<vcsRepositoryCreator implementation="com.github.jonathanxd.dracon.repository.PijulRepositoryCreator"/>
<vcsRepositoryInitializer implementation="com.github.jonathanxd.dracon.repository.PijulRepositoryInit"/>
<vcs.ignoredFilesHolder implementation="com.github.jonathanxd.dracon.ignore.PijulIgnoredFileHolderProvider"/>
<ignoredFileContentProvider implementation="com.github.jonathanxd.dracon.ignore.PijulIgnoredFileContentProvider"/>
package com.github.jonathanxd.dracon.repository
import com.github.jonathanxd.dracon.PijulVcs
import com.github.jonathanxd.dracon.pijulVcs
import com.intellij.dvcs.repo.AbstractRepositoryManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
@Service
class PijulRepositoryManager(project: Project) : AbstractRepositoryManager<PijulRepository>(
pijulVcs(project), ".pijul"
) {
override fun getRepositories(): MutableList<PijulRepository> =
this.getRepositories(PijulRepository::class.java)
override fun isSyncEnabled(): Boolean =
true
}
package com.github.jonathanxd.dracon.repository
import com.github.jonathanxd.dracon.PijulVcs
import com.github.jonathanxd.dracon.pijul.Pijul
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.github.jonathanxd.dracon.pijul.pijul
import com.intellij.dvcs.repo.Repository
import com.intellij.dvcs.repo.VcsRepositoryCreator
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vcs.VcsKey
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcs.VcsRepositoryInitializer
import java.io.File
class PijulRepositoryInit : VcsRepositoryInitializer {
override fun initRepository(rootDir: File) {
val project = ProjectManager.getInstance().defaultProject
val result = pijul(project).init(project, rootDir.toPath())
if (result.statusCode !is SuccessStatusCode) {
throw VcsException(result.toString())
}
}
override fun getSupportedVcs(): VcsKey =
PijulVcs.KEY
}
package com.github.jonathanxd.dracon.repository
import com.github.jonathanxd.dracon.PijulVcs
import com.github.jonathanxd.dracon.pijul.Pijul
import com.intellij.dvcs.repo.Repository
import com.intellij.dvcs.repo.VcsRepositoryCreator
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.VcsKey
import com.intellij.openapi.vfs.VirtualFile
class PijulRepositoryCreator : VcsRepositoryCreator {
override fun createRepositoryIfValid(
project: Project,
root: VirtualFile,
parentDisposable: Disposable
): Repository? =
if (Pijul.isUnderPijul(root)) PijulRepository(project, root, parentDisposable) else null
override fun getVcsKey(): VcsKey = PijulVcs.KEY
}
package com.github.jonathanxd.dracon.repository
import com.github.jonathanxd.dracon.PijulVcs
import com.github.jonathanxd.dracon.ignore.PijulLocalIgnoredFileHolder
import com.github.jonathanxd.dracon.pijul.pijul
import com.github.jonathanxd.dracon.pijulVcs
import com.intellij.dvcs.ignore.VcsIgnoredHolderUpdateListener
import com.intellij.dvcs.ignore.VcsRepositoryIgnoredFilesHolder
import com.intellij.dvcs.repo.Repository
import com.intellij.dvcs.repo.RepositoryImpl
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.AbstractVcs
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.ChangesViewI
import com.intellij.openapi.vcs.changes.ChangesViewManager
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.annotations.NotNull
class PijulRepository(project: @NotNull Project, dir: @NotNull VirtualFile, parentDisposable: @NotNull Disposable) :
RepositoryImpl(project, dir, parentDisposable) {
private val vcs = pijulVcs(project)
private val localIgnoredHolder: PijulLocalIgnoredFileHolder by lazy {
PijulLocalIgnoredFileHolder(this, project.service())
}
init {
localIgnoredHolder.setupListeners()
Disposer.register(this, localIgnoredHolder)
localIgnoredHolder.addUpdateStateListener(
MyIgnoredHolderAsyncListener(
getProject()
)
)
update()
}
companion object {
fun getInstance(
root: VirtualFile,
project: Project,
parentDisposable: Disposable
): PijulRepository {
val repository = PijulRepository(project, root, parentDisposable)
return repository
}
}
override fun getState(): Repository.State = Repository.State.NORMAL
override fun getCurrentBranchName(): String? =
pijul(this.project).channel(project, project.baseDir).result?.channels?.firstOrNull { it.current }?.name
override fun getVcs(): AbstractVcs =
this.vcs
override fun getCurrentRevision(): String? =
pijul(this.project).latestRevisionNumber(project, project.baseDir).result?.hash
override fun update() {
}
fun getIgnoredFilesHolder(): VcsRepositoryIgnoredFilesHolder =
this.localIgnoredHolder
override fun toLogString(): String =
"PijulRepository ${this.root}"
private class MyIgnoredHolderAsyncListener(project: Project) :
VcsIgnoredHolderUpdateListener {
private val changesViewI: ChangesViewI
private val project: Project
override fun updateStarted() {
changesViewI.scheduleRefresh()
}
override fun updateFinished(ignoredPaths: Collection<FilePath>, isFullRescan: Boolean) {
if (project.isDisposed) return
changesViewI.scheduleRefresh()
}
init {
changesViewI = ChangesViewManager.getInstance(project)
this.project = project
}
}
}
}
fun <Y> andThen(other: (R) -> PijulOperationResult<Y>): PijulOperationResult<Y> {
return if (this.statusCode !is SuccessStatusCode) {
this as PijulOperationResult<Y>
} else {
other(this.result!!)
}
}
fun <Y> andThenValue(other: (R) -> Y): PijulOperationResult<Y> {
return if (this.statusCode !is SuccessStatusCode) {
this as PijulOperationResult<Y>
} else {
PijulOperationResult(operation, statusCode, other(this.result!!))
}
}
}
fun <R, Y> PijulOperationResult<List<R>>.mapEvery(f: (R) -> Y): PijulOperationResult<List<Y>> =
this.map { it.map(f) }
package com.github.jonathanxd.dracon.ignore
import com.github.jonathanxd.dracon.context.PijulVcsContext
import com.github.jonathanxd.dracon.repository.PijulRepository
import com.github.jonathanxd.dracon.repository.PijulRepositoryManager
import com.intellij.dvcs.ignore.VcsRepositoryIgnoredFilesHolderBase
import com.intellij.openapi.components.service
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.LocalFilePath
import com.intellij.openapi.vcs.VcsException
import java.nio.file.Files
import java.nio.file.Paths
import java.util.ArrayList
import java.util.HashSet
class PijulLocalIgnoredFileHolder(repository: PijulRepository, repositoryManager: PijulRepositoryManager) :
VcsRepositoryIgnoredFilesHolderBase<PijulRepository>(repository, repositoryManager) {
@Throws(VcsException::class)
override fun requestIgnored(paths: Collection<FilePath>?): Set<FilePath> {
val ctx = repository.project.service<PijulVcsContext>()
val ignored: MutableSet<FilePath> = HashSet()
ignored.addAll(
IgnoreUtil.ignoredFiles(ctx.root, paths?.map { Paths.get(it.path) }.orEmpty())
.map { LocalFilePath(it, Files.isDirectory(it)) }
)
return ignored
}
override fun scanTurnedOff(): Boolean {
return false
}
}
package com.github.jonathanxd.dracon.ignore
import com.github.jonathanxd.dracon.PijulVcs
import com.github.jonathanxd.dracon.pijulVcs
import com.github.jonathanxd.dracon.repository.PijulRepository
import com.github.jonathanxd.dracon.repository.PijulRepositoryManager
import com.intellij.dvcs.ignore.VcsIgnoredFilesHolderBase
import com.intellij.dvcs.ignore.VcsRepositoryIgnoredFilesHolder
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.AbstractVcs
import com.intellij.openapi.vcs.changes.ChangesViewRefresher
import com.intellij.openapi.vcs.changes.FileHolder
import com.intellij.openapi.vcs.changes.VcsIgnoredFilesHolder
class PijulIgnoredFileHolder(private val project: Project, val manager: PijulRepositoryManager) :
VcsIgnoredFilesHolderBase<PijulRepository>(manager) {
override fun getHolder(repository: PijulRepository): VcsRepositoryIgnoredFilesHolder {
return repository.getIgnoredFilesHolder()
}
override fun copy(): FileHolder {
return PijulIgnoredFileHolder(project, manager) // re-scan roots on refresh
}
}
class PijulIgnoredFileHolderProvider(private val project: Project) : VcsIgnoredFilesHolder.Provider,
ChangesViewRefresher {
private val vcs = pijulVcs(project)
private val manager by lazy {
project.service<PijulRepositoryManager>()
}
override fun getVcs(): AbstractVcs {
return vcs
}
override fun createHolder(): VcsIgnoredFilesHolder {
return PijulIgnoredFileHolder(project, manager)
}
override fun refresh(project: Project) {
manager.repositories.forEach { r -> r.getIgnoredFilesHolder().startRescan() }
}
}
package com.github.jonathanxd.dracon.ignore
import com.github.jonathanxd.dracon.PijulVcs
import com.github.jonathanxd.dracon.pijul.Pijul
import com.github.jonathanxd.dracon.pijul.pijul
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.LocalFilePath
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vcs.VcsKey
import com.intellij.openapi.vcs.actions.VcsContextFactory
import com.intellij.openapi.vcs.changes.IgnoreSettingsType
import com.intellij.openapi.vcs.changes.IgnoredFileContentProvider
import com.intellij.openapi.vcs.changes.IgnoredFileDescriptor
import com.intellij.openapi.vcs.changes.IgnoredFileProvider
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcsUtil.VcsUtil
import java.nio.file.Files
import java.nio.file.Paths
private val LOG = logger<PijulIgnoredFileContentProvider>()
class PijulIgnoredFileContentProvider(private val project: Project) : IgnoredFileContentProvider {
override fun getSupportedVcs(): VcsKey = PijulVcs.KEY
override fun getFileName() = ".ignore"
override fun buildIgnoreFileContent(
ignoreFileRoot: VirtualFile,
ignoredFileProviders: Array<IgnoredFileProvider>
): String {
if (!Pijul.isUnderPijul(ignoreFileRoot)) return ""
val repoRoot = VcsUtil.getVcsRootFor(project, ignoreFileRoot)
if (repoRoot == null || repoRoot != ignoreFileRoot) return ""
val content = StringBuilder()
val lineSeparator = System.lineSeparator()
val untrackedFiles = getUntrackedFiles(repoRoot, VcsUtil.getFilePath(ignoreFileRoot))
if (untrackedFiles.isEmpty()) return ""
for (provider in ignoredFileProviders) {
val ignoredFiles =
provider.getIgnoredFiles(project).ignoreBeansToRelativePaths(ignoreFileRoot, untrackedFiles)
if (ignoredFiles.isEmpty()) continue
if (content.isNotEmpty()) {
content.append(lineSeparator).append(lineSeparator)
}
val description = provider.ignoredGroupDescription
if (description.isNotBlank()) {
content.append(buildIgnoreGroupDescription(provider))
content.append(lineSeparator)
}
content.append(ignoredFiles.joinToString(lineSeparator))
}
return content.toString()//if (content.isNotEmpty()) "syntax: glob$lineSeparator$lineSeparator$content" else ""
}
private fun getUntrackedFiles(root: VirtualFile, ignoreFileRoot: FilePath): Set<FilePath> =
try {
val rootPath = Paths.get(VcsUtil.getFilePath(root).path)
HashSet(
pijul(project).untrackedFiles(project, rootPath)
.result!!
.map {
LocalFilePath(it, Files.isDirectory(it))
}.filter {
isAncestor(ignoreFileRoot, it, true)
}
)
} catch (e: VcsException) {
LOG.warn("Cannot get untracked files: ", e)
emptySet()
}
fun isAncestor(ancestor: FilePath, file: FilePath, strict: Boolean): Boolean {
var parent = if (strict) file.parentPath else file
while (true) {
if (parent == null) return false
if (parent == ancestor) return true
parent = parent.parentPath
}
}
private fun Iterable<IgnoredFileDescriptor>.ignoreBeansToRelativePaths(
ignoreFileRoot: VirtualFile,
untrackedFiles: Set<FilePath>
): List<String> {
val vcsRoot = VcsUtil.getVcsRootFor(project, ignoreFileRoot)
val vcsContextFactory = VcsContextFactory.SERVICE.getInstance()
return filter { ignoredBean ->
when (ignoredBean.type) {
IgnoreSettingsType.UNDER_DIR -> shouldIgnoreUnderDir(
ignoredBean,
untrackedFiles,
ignoreFileRoot,
vcsRoot,
vcsContextFactory
)
IgnoreSettingsType.FILE -> shouldIgnoreFile(
ignoredBean,
untrackedFiles,
ignoreFileRoot,
vcsRoot,
vcsContextFactory
)
IgnoreSettingsType.MASK -> shouldIgnoreByMask(ignoredBean, untrackedFiles)
}
}.map { ignoredBean ->
when (ignoredBean.type) {
IgnoreSettingsType.MASK -> ignoredBean.mask!!
IgnoreSettingsType.UNDER_DIR -> buildIgnoreEntryContent(ignoreFileRoot, ignoredBean)
IgnoreSettingsType.FILE -> buildIgnoreEntryContent(ignoreFileRoot, ignoredBean)
}
}
}
private fun shouldIgnoreUnderDir(
ignoredBean: IgnoredFileDescriptor,
untrackedFiles: Set<FilePath>,
ignoreFileRoot: VirtualFile,
vcsRoot: VirtualFile?,
vcsContextFactory: VcsContextFactory
) =
FileUtil.exists(ignoredBean.path)
&& untrackedFiles.any { FileUtil.isAncestor(ignoredBean.path!!, it.path, true) }
&& FileUtil.isAncestor(ignoreFileRoot.path, ignoredBean.path!!, false)
&& Comparing.equal(
vcsRoot,
VcsUtil.getVcsRootFor(project, vcsContextFactory.createFilePath(ignoredBean.path!!, true))
)
private fun shouldIgnoreFile(
ignoredBean: IgnoredFileDescriptor,
untrackedFiles: Set<FilePath>,
ignoreFileRoot: VirtualFile,
vcsRoot: VirtualFile?,
vcsContextFactory: VcsContextFactory
) =
FileUtil.exists(ignoredBean.path)
&& untrackedFiles.any { ignoredBean.matchesFile(it) }
&& FileUtil.isAncestor(ignoreFileRoot.path, ignoredBean.path!!, false)
&& Comparing.equal(
vcsRoot,
VcsUtil.getVcsRootFor(project, vcsContextFactory.createFilePath(ignoredBean.path!!, false))
)
private fun shouldIgnoreByMask(ignoredBean: IgnoredFileDescriptor, untrackedFiles: Set<FilePath>) =
untrackedFiles.any { ignoredBean.matchesFile(it) }
override fun buildUnignoreContent(ignorePattern: String) = throw UnsupportedOperationException()
override fun buildIgnoreGroupDescription(ignoredFileProvider: IgnoredFileProvider) =
prependCommentHashCharacterIfNeeded(ignoredFileProvider.ignoredGroupDescription)
override fun buildIgnoreEntryContent(ignoreEntryRoot: VirtualFile, ignoredFileDescriptor: IgnoredFileDescriptor) =
FileUtil.getRelativePath(ignoreEntryRoot.path, ignoredFileDescriptor.path!!, '/') ?: ""
override fun supportIgnoreFileNotInVcsRoot() = false
private fun prependCommentHashCharacterIfNeeded(description: @NlsSafe String): @NlsSafe String =
if (description.startsWith("#")) description else "# $description"
}
package com.github.jonathanxd.dracon.ignore
import java.nio.file.FileVisitOption
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.relativeTo
import kotlin.streams.toList
object IgnoreUtil {
@OptIn(ExperimentalPathApi::class)
fun ignoredFiles(root: Path, paths: List<Path>): List<Path> {
val ignore = root.resolve(".ignore")
return if (Files.exists(ignore) && Files.isRegularFile(ignore)) {
val lines = Files.readAllLines(ignore, Charsets.UTF_8)
Files.walk(root, FileVisitOption.FOLLOW_LINKS)
.filter {
paths.isEmpty() || paths.contains(it)
}
.filter {
val relative = it.toAbsolutePath().relativeTo(root).toString()
lines.any { line -> relative.startsWith(line) }
}.toList()
} else {
emptyList()
}
}
}