VBL5BQH7K5QZH3KCO4JBRDVYAN4YXYNAWETL2WVUD2PYHNZAGMTAC
DLDMHQY6F53N2YXNDBQY77GAU2I4N2OWC3DL6L36T2SSAVW6YSXAC
GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC
37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC
ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
FRFFQV7VNYKGCA7ZAOSRPC2HHYTAIZ6AGGR7A5QEV6QPAQGFDYGAC
Q7FXTHVUPVAFMNY277C3NFJO3VXLZU5G6C6UYSD5QPURHSG3A7OQC
RE4EKNSLYGCITZZEOPIRJAWTKIONGP7IY6S77BQO7JQL2CK27RZAC
QXUEMZ3B2FUHFUC7ZZHJMH5FVWLEMEYXUMFA6JNXTJKIVZNMRIOAC
A7IL6I3VCB3F3QMYQFPXBL33DN5XDGGQPZIBZRHYSQBSIUBBFQJQC
OMZXJL6QA6INENIEAARSWYFHOPMLTP4WRCVI646GQVJVWCH3LENQC
6CR2EFUN7JXFHCBTNX3WWOOP4WFOCFO6KSPEBN6V6J5HFZO2LHNQC
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC
5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
7L5LODGZ7AN4ZULDJZMLALD7PL6E57VZSNNSG67SFJARUJGCT47QC
Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
MZYZIVHY5DUHVJH7YGENRAIKTKMZWJ5ANRVGFLLU7YTHXC2OXZJQC
caching uses a bunch of storage space and takes a bit of more time at first loading.
caching uses a bunch of storage space and takes a bit of more time at first loading.
pijul.log.refGroup.default=Default
pijul.log.refGroup.current=Current
pijul.log.refGroup.channel=Channel
pijul.log.refGroup.tag=Tag
pijul.log.refGroup.tag_text={0}
pijul.log.refGroup.mainChannel=main
pijul.log.refGroup.other=Other
package com.github.jonathanxd.dracon.util
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcsUtil.VcsUtil
import java.nio.file.Path
import java.nio.file.Paths
fun VirtualFile.convertToNio(): Path =
Paths.get(VcsUtil.getFilePath(this).path)
fun FilePath.convertToNio(): Path =
Paths.get(this.path)
package com.github.jonathanxd.dracon.util
import com.intellij.openapi.project.Project
fun Project.pijul() =
com.github.jonathanxd.dracon.pijul.pijul(this)
fun Project.pijulVcs() =
com.github.jonathanxd.dracon.pijulVcs(this)
package com.github.jonathanxd.dracon.util
import com.github.jonathanxd.iutils.collection.view.ViewCollections
import com.github.jonathanxd.iutils.collection.wrapper.WrapperCollections
import com.intellij.util.containers.map2Array
interface BiDiMap<K, V> : Map<K, V> {
val otherDirection: BiDiMap<V, K>
}
interface MutableBiDiMap<K, V> : BiDiMap<K, V>, MutableMap<K, V> {
override val otherDirection: MutableBiDiMap<V, K>
}
class BiDiMapImpl<K, V>(val kvMap: Map<K, V>,
val vkMap: Map<V, K>): BiDiMap<K, V> {
constructor(map: Map<K, V>): this(map, map.createVKMap())
override val entries: Set<Map.Entry<K, V>>
get() = this.kvMap.entries
override val keys: Set<K>
get() = this.kvMap.keys
override val size: Int
get() = this.kvMap.size
override val values: Collection<V>
get() = this.kvMap.values
override fun containsKey(key: K): Boolean = this.kvMap.containsKey(key)
override fun containsValue(value: V): Boolean = this.kvMap.containsValue(value)
override fun get(key: K): V? = this.kvMap[key]
override fun isEmpty(): Boolean = this.kvMap.isEmpty()
override val otherDirection: BiDiMap<V, K> = OtherDirection()
inner class OtherDirection: BiDiMap<V, K> {
override val entries: Set<Map.Entry<V, K>>
get() = this@BiDiMapImpl.vkMap.entries
override val keys: Set<V>
get() = this@BiDiMapImpl.vkMap.keys
override val size: Int
get() = this@BiDiMapImpl.vkMap.size
override val values: Collection<K>
get() = this@BiDiMapImpl.vkMap.values
override fun containsKey(key: V): Boolean = this@BiDiMapImpl.vkMap.containsKey(key)
override fun containsValue(value: K): Boolean = this@BiDiMapImpl.vkMap.containsValue(value)
override fun get(key: V): K? = this@BiDiMapImpl.vkMap[key]
override fun isEmpty(): Boolean = this@BiDiMapImpl.vkMap.isEmpty()
override val otherDirection: BiDiMap<K, V>
get() = this@BiDiMapImpl
}
}
class MutableBiDiMapImpl<K, V>(val kvMap: MutableMap<K, V>,
val vkMap: MutableMap<V, K>): MutableBiDiMap<K, V> {
constructor(map: MutableMap<K, V>): this(map, map.createVKMutableMap())
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() = ViewCollections.setMapped(
this.kvMap.entries,
{ MutableBiDiEntry(it, this.vkMap)},
{
this.kvMap[it.key] = it.value
this.vkMap[it.value] = it.key
true
},
{
this.kvMap.remove(it.key, it.value)
this.vkMap.remove(it.value, it.key)
true
}
)
override val keys: MutableSet<K>
get() = ViewCollections.setMapped(this.kvMap.keys,
{it},
{
false
},
{
val value = this.kvMap.remove(it)
this.vkMap.remove(value)
true
}
)
override val values: MutableCollection<V>
get() = ViewCollections.collectionMapped(this.kvMap.values,
{it},
{
false
},
{
val key = this.vkMap.remove(it)
this.kvMap.remove(key)
true
}
)
override val size: Int
get() = this.kvMap.size
override fun containsKey(key: K): Boolean = this.kvMap.containsKey(key)
override fun containsValue(value: V): Boolean = this.kvMap.containsValue(value)
override fun get(key: K): V? = this.kvMap[key]
override fun isEmpty(): Boolean = this.kvMap.isEmpty()
override fun clear() {
this.kvMap.clear()
this.vkMap.clear()
}
override fun remove(key: K): V? {
val value = this.kvMap.remove(key)
this.vkMap.remove(value)
return value
}
override fun put(key: K, value: V): V? {
val oldValue = this.kvMap.put(key, value)
this.vkMap[value] = key
return oldValue
}
override fun putAll(from: Map<out K, V>) {
for ((k, v) in from) {
this[k] = v
}
}
override val otherDirection: MutableBiDiMap<V, K> = OtherDirection()
inner class OtherDirection: MutableBiDiMap<V, K> {
override val entries: MutableSet<MutableMap.MutableEntry<V, K>>
get() = ViewCollections.setMapped(
this@MutableBiDiMapImpl.vkMap.entries,
{ MutableBiDiEntry(it, this@MutableBiDiMapImpl.kvMap)},
{
this@MutableBiDiMapImpl.vkMap[it.key] = it.value
this@MutableBiDiMapImpl.kvMap[it.value] = it.key
true
},
{
this@MutableBiDiMapImpl.vkMap.remove(it.key, it.value)
this@MutableBiDiMapImpl.kvMap.remove(it.value, it.key)
true
}
)
override val keys: MutableSet<V>
get() = ViewCollections.setMapped(this@MutableBiDiMapImpl.vkMap.keys,
{it},
{
false
},
{
val value = this@MutableBiDiMapImpl.vkMap.remove(it)
this@MutableBiDiMapImpl.kvMap.remove(value)
true
}
)
override val values: MutableCollection<K>
get() = ViewCollections.collectionMapped(this@MutableBiDiMapImpl.vkMap.values,
{it},
{
false
},
{
val key = this@MutableBiDiMapImpl.kvMap.remove(it)
this@MutableBiDiMapImpl.vkMap.remove(key)
true
}
)
override val size: Int
get() = this@MutableBiDiMapImpl.vkMap.size
override fun containsKey(key: V): Boolean = this@MutableBiDiMapImpl.vkMap.containsKey(key)
override fun containsValue(value: K): Boolean = this@MutableBiDiMapImpl.vkMap.containsValue(value)
override fun get(key: V): K? = this@MutableBiDiMapImpl.vkMap[key]
override fun isEmpty(): Boolean = this@MutableBiDiMapImpl.vkMap.isEmpty()
override val otherDirection: MutableBiDiMap<K, V>
get() = this@MutableBiDiMapImpl
override fun clear() {
this@MutableBiDiMapImpl.kvMap.clear()
this@MutableBiDiMapImpl.vkMap.clear()
}
override fun remove(key: V): K? {
val value = this@MutableBiDiMapImpl.vkMap.remove(key)
this@MutableBiDiMapImpl.kvMap.remove(value)
return value
}
override fun put(key: V, value: K): K? {
val oldValue = this@MutableBiDiMapImpl.vkMap.put(key, value)
this@MutableBiDiMapImpl.kvMap[value] = key
return oldValue
}
override fun putAll(from: Map<out V, K>) {
for ((k, v) in from) {
this[k] = v
}
}
}
}
class MutableBiDiEntry<K, V>(val entry: MutableMap.MutableEntry<K, V>, val vkMap: MutableMap<V, K>) : MutableMap.MutableEntry<K, V> {
override val key: K
get() = entry.key
override val value: V
get() = entry.value
override fun setValue(newValue: V): V {
this.vkMap[newValue] = this.key
return entry.setValue(newValue)
}
}
fun <K, V> Map<K, V>.createVKMap(): Map<V, K> {
val map = mutableMapOf<V, K>()
this.entries.forEach {
map[it.value] = it.key
}
return map
}
fun <K, V> MutableMap<K, V>.createVKMutableMap(): MutableMap<V, K> {
val map = mutableMapOf<V, K>()
this.entries.forEach {
map[it.value] = it.key
}
return map
}
fun <K, V> mutableBiDiMap(vararg pairs: Pair<K, V>): MutableBiDiMap<K, V> =
MutableBiDiMapImpl(
mutableMapOf(*pairs),
mutableMapOf(*pairs.inverted()),
)
fun <K, V> biDiMap(vararg pairs: Pair<K, V>): BiDiMap<K, V> =
BiDiMapImpl(
mutableMapOf(*pairs),
mutableMapOf(*pairs.inverted()),
)
fun <K, V> Array<out Pair<K, V>>.inverted(): Array<Pair<V, K>> =
Array(this.size) {
this[it].second to this[it].first
}
package com.github.jonathanxd.dracon.revision
data class ChannelRevision(val channel: String, val revision: PijulRevisionNumber)
data class ChannelRevisions(val revisionsByChannel: List<ChannelRevision>)
package com.github.jonathanxd.dracon.repository
import com.github.jonathanxd.dracon.channel.PijulChannelManager
import com.github.jonathanxd.dracon.channel.PijulChannelType
import com.github.jonathanxd.dracon.i18n.DraconBundle
import com.github.jonathanxd.dracon.util.BiDiMap
import com.github.jonathanxd.dracon.util.biDiMap
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.vcs.log.*
import com.intellij.vcs.log.impl.SimpleRefGroup
import com.intellij.vcs.log.impl.SimpleRefType
import java.io.DataInput
import java.io.DataOutput
class PijulVcsLogRefManager(val project: Project) : VcsLogRefManager {
companion object {
val CURRENT: VcsRefType = SimpleRefType("CURRENT", false, VcsLogStandardColors.Refs.TIP)
val CHANNEL: VcsRefType = SimpleRefType("CHANNEL", true, VcsLogStandardColors.Refs.BRANCH)
val TAG: VcsRefType = SimpleRefType("TAG", false, VcsLogStandardColors.Refs.TAG)
val OTHER: VcsRefType = SimpleRefType("OTHER", true, VcsLogStandardColors.Refs.TAG)
private val REF_TYPE_INDEX: BiDiMap<VcsRefType, Int> = biDiMap(
CURRENT to 0,
CHANNEL to 1,
TAG to 2,
OTHER to 100
)
}
private val labelSorter = Comparator<VcsRef> { l, r ->
REF_TYPE_INDEX[l.type]!!.compareTo(REF_TYPE_INDEX[r.type]!!)
}
private val channelSorter = Comparator<VcsRef> { l, r ->
REF_TYPE_INDEX[l.type]!!.compareTo(REF_TYPE_INDEX[r.type]!!)
}
val channelManager = project.service<PijulChannelManager>()
val repositoryManager = project.service<PijulRepositoryManager>()
override fun getBranchLayoutComparator(): Comparator<VcsRef> = this.channelSorter
override fun getLabelsOrderComparator(): Comparator<VcsRef> = this.labelSorter
override fun groupForBranchFilter(refs: MutableCollection<out VcsRef>): MutableList<RefGroup> {
val current = mutableListOf<VcsRef>()
val channels = mutableListOf<VcsRef>()
val tags = mutableListOf<VcsRef>()
val others = mutableListOf<VcsRef>()
for (ref in refs) {
when (ref.type) {
CURRENT -> {
current.add(ref)
}
CHANNEL -> {
channels.add(ref)
}
TAG -> {
tags.add(ref)
}
else -> {
others.add(ref)
}
}
}
val groups = mutableListOf<RefGroup>()
if (current.isNotEmpty()) {
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.current"), current.toList()))
}
if (channels.isNotEmpty()) {
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.channel"), channels.toList()))
}
if (tags.isNotEmpty()) {
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.tag"), tags.toList()))
}
if (others.isNotEmpty()) {
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.other"), others.toList()))
}
return groups
}
override fun groupForTable(
refs: MutableCollection<out VcsRef>,
compact: Boolean,
showTagNames: Boolean
): MutableList<RefGroup> {
// TODO: Support compact and showTagNames
val mainChannels = mutableListOf<VcsRef>()
val channels = mutableListOf<VcsRef>()
val tags = mutableListOf<VcsRef>()
val others = mutableListOf<VcsRef>()
for (ref in refs) {
when (ref.type) {
CURRENT -> {}
CHANNEL -> {
if (ref.name == "main") {
mainChannels.add(ref)
} else {
channels.add(ref)
}
}
TAG -> {
tags.add(ref)
}
else -> {
others.add(ref)
}
}
}
val hasCurrent = refs.any { it.type == CURRENT }
val groups = mutableListOf<RefGroup>()
if (mainChannels.isNotEmpty() && hasCurrent) {
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.mainChannel"), mainChannels.toList()))
}
if (channels.isNotEmpty()) {
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.channel"), channels.toList()))
}
if (tags.isNotEmpty()) {
val f = tags.first()
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.tag_text", f.name), tags.toList()))
}
if (others.isNotEmpty()) {
groups.add(SimpleRefGroup(DraconBundle.message("pijul.log.refGroup.other"), others.toList()))
}
return groups
}
override fun serialize(out: DataOutput, type: VcsRefType) {
out.writeInt(REF_TYPE_INDEX[type]!!)
}
override fun deserialize(input: DataInput): VcsRefType {
val id = input.readInt()
return REF_TYPE_INDEX.otherDirection[id]!!
}
override fun isFavorite(reference: VcsRef): Boolean =
when (reference.type) {
CURRENT -> true
CHANNEL -> this.channelManager.isFavorite(
PijulChannelType.CHANNEL,
this.repositoryManager.getRepositoryForFileQuick(reference.root),
reference.name
)
else -> false
}
override fun setFavorite(reference: VcsRef, favorite: Boolean) {
when (reference.type) {
CURRENT -> return
CHANNEL -> this.channelManager.setFavorite(
PijulChannelType.CHANNEL,
this.repositoryManager.getRepositoryForFileQuick(reference.root),
reference.name,
favorite
)
else -> return
}
}
}
package com.github.jonathanxd.dracon.repository
@FunctionalInterface
interface PijulRepositoryChangeListener {
fun repositoryChanged(repository: PijulRepository)
}
fun PijulRepositoryChangeListener(f: (repository: PijulRepository) -> Unit): PijulRepositoryChangeListener =
object : PijulRepositoryChangeListener {
override fun repositoryChanged(repository: PijulRepository) {
f(repository)
}
}
package com.github.jonathanxd.dracon.log
import com.github.jonathanxd.dracon.push.parseDate
import org.tomlj.Toml
import java.time.ZonedDateTime
import java.util.*
data class PijulTag(
val hash: String,
val authors: List<Author>,
val date: ZonedDateTime,
val state: String,
val message: String
)
val PijulTag.simpleMessage get() =
if (this.message.isEmpty()) ""
else this.message.split("\n").first { it.trim().isNotEmpty() }.trim()
val AUTHOR_REGEX = Regex("Author \\{ name: (\"([^\"]+)\"|None), full_name: (Some\\(\"([^\"]+)\"\\)|None), email: (Some\\(\"([^\"]+)\"\\)|None) }")
fun String.parseTags(): List<PijulTag> {
if (this.isEmpty()) return emptyList()
val lines = this.lines().listIterator()
val tags = mutableListOf<PijulTag>()
while (lines.hasNext()) {
var hash: String? = null
val authors = mutableListOf<Author>()
var date: ZonedDateTime? = null
var state: String? = null
val message = StringJoiner("\n")
var messageStarted = false
while (lines.hasNext()) {
val line = lines.next()
if (line.startsWith("Tag")) {
hash = line.substring("Tag ".length)
} else if (line.startsWith("Author: ")) {
val finds = AUTHOR_REGEX.findAll(line)
for (find in finds) {
val groups = find.groups
val name: String? = groups[2]?.value
val fullName: String? = groups[4]?.value
val email: String? = groups[6]?.value
authors += Author(name, fullName, email)
}
} else if (line.startsWith("Date: ")) {
val dateString = line.substring("Date: ".length)
date = dateString.parseDate()
} else if (line.startsWith("State: ")) {
state = line.substring("State: ".length)
} else {
if (line.startsWith("Tag")) {
lines.previous()
break;
} else if (line.trim().isEmpty() && state != null && !messageStarted) {
messageStarted = true
} else {
if (line.startsWith(" ")) {
message.add(line.substring(" ".length))
} else {
message.add(line)
}
}
}
}
tags += PijulTag(hash!!, authors, date!!, state!!, message.toString().removeTrailingLineJump())
}
return tags
}
private val TRAILING_LINE_JUMP = Regex("[\n]+$")
fun String.removeTrailingLineJump() =
this.replace(TRAILING_LINE_JUMP, "")
package com.github.jonathanxd.dracon.log
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcs.log.VcsShortCommitDetails
import com.intellij.vcs.log.impl.VcsChangesLazilyParsedDetails
import com.intellij.vcs.log.impl.VcsChangesLazilyParsedDetails.ChangesParser
import com.intellij.vcs.log.impl.VcsFileStatusInfo
import java.util.*
class PijulRecordDetails(val project: Project,
val rootFile: VirtualFile,
val entry: PijulLogEntryCommitDetails) : VcsChangesLazilyParsedDetails(
project, entry.id, emptyList(), entry.commitTime, rootFile, entry.subject, entry.author,
entry.fullMessage, entry.committer, entry.authorTime,
(listOf(PijulHash(entry.entry.changeHash)) /*+ entry.getPijulParents()*/).map {
entry.changeRetriever(it.hash).map {
VcsFileStatusInfo(
it.type,
(it.beforeRevision?.file?.path ?: it.afterRevision?.file?.path)!!,
it.afterRevision?.file?.path
)
}
},
PijulChangesParser(entry)
)
private class PijulChangesParser(val entry: PijulLogEntryCommitDetails) : ChangesParser {
override fun parseStatusInfo(
project: Project,
commit: VcsShortCommitDetails,
changes: List<VcsFileStatusInfo>,
parentIndex: Int
): List<Change> {
var parentHash: String? = null
/*if (parentIndex < commit.parents.size) {
parentHash = commit.parents[parentIndex].asString()
}*/
return entry.changeRetriever(parentHash ?: entry.entry.changeHash)
}
}
package com.github.jonathanxd.dracon.log
import com.github.jonathanxd.dracon.PijulVcs
import com.github.jonathanxd.dracon.changes.PijulCommittedChangeList
import com.github.jonathanxd.dracon.pijul.Pijul
import com.github.jonathanxd.dracon.pijul.pijul
import com.github.jonathanxd.dracon.repository.PijulRepository
import com.github.jonathanxd.dracon.repository.PijulRepositoryChangeListener
import com.github.jonathanxd.dracon.repository.PijulVcsLogRefManager
import com.github.jonathanxd.dracon.util.convertToNio
import com.github.jonathanxd.dracon.util.pijul
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.VcsKey
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.Consumer
import com.intellij.util.messages.MessageBusConnection
import com.intellij.vcs.log.*
import com.intellij.vcs.log.impl.HashImpl
import com.intellij.vcs.log.impl.LogDataImpl
import com.intellij.vcs.log.impl.VcsUserImpl
import org.apache.commons.codec.digest.DigestUtils
import java.time.ZoneId
class PijulLogProvider(val project: Project) : VcsLogProvider {
val manager = PijulVcsLogRefManager(project)
override fun readFirstBlock(
root: VirtualFile,
requirements: VcsLogProvider.Requirements
): VcsLogProvider.DetailedLogData {
return createMetadata(this.project, root) {
this.project.pijul().log(this.project, root.convertToNio(), it).result?.entries.orEmpty()
}
}
override fun readAllHashes(root: VirtualFile, commitConsumer: Consumer<in TimedVcsCommit>): VcsLogProvider.LogData {
return createMetadata(this.project, root, {
this.project.pijul().log(this.project, root.convertToNio(), it).result?.entries.orEmpty()
}, { commitConsumer.consume(it) })
}
override fun readMetadata(
root: VirtualFile,
hashes: MutableList<String>,
consumer: Consumer<in VcsCommitMetadata>
) {
createMetadata(this.project, root, {
this.project.pijul().log(this.project, root.convertToNio(), it).result?.entries.orEmpty().filter {
hashes.contains(DigestUtils.sha1Hex(it.changeHash))
}
}, {
consumer.consume(it)
})
}
override fun readFullDetails(
root: VirtualFile,
hashes: MutableList<String>,
commitConsumer: Consumer<in VcsFullCommitDetails>
) {
createMetadataForEntries(this.project, root, {
this.project.pijul().log(this.project, root.convertToNio(), it).result?.entries.orEmpty().filter {
hashes.contains(DigestUtils.sha1Hex(it.changeHash))
}
}, { _, it ->
commitConsumer.consume(PijulRecordDetails(this.project, root, it))
})
}
override fun getSupportedVcs(): VcsKey {
return PijulVcs.KEY
}
override fun getReferenceManager(): VcsLogRefManager =
this.manager
override fun subscribeToRootRefreshEvents(
roots: MutableCollection<out VirtualFile>,
refresher: VcsLogRefresher
): Disposable {
val connection: MessageBusConnection = this.project.messageBus.connect()
connection.subscribe(PijulRepository.PIJUL_REPO_CHANGE, PijulRepositoryChangeListener { repository ->
val root: VirtualFile = repository.root
if (roots.contains(root)) {
refresher.refresh(root)
}
})
return connection
}
override fun getCommitsMatchingFilter(
root: VirtualFile,
filterCollection: VcsLogFilterCollection,
maxCount: Int
): MutableList<TimedVcsCommit> {
val rootPath = root.convertToNio()
val channelFilters = mutableListOf<(String) -> Boolean>()
val filters = mutableListOf<(PijulLogEntry) -> Boolean>()
val fullDetailsFilters = mutableListOf<(VcsFullCommitDetails) -> Boolean>()
val metadataFilters = mutableListOf<(VcsCommitMetadata) -> Boolean>()
val date = filterCollection.get(VcsLogFilterCollection.DATE_FILTER)
date?.after?.let { after ->
filters.add {
it.date.isAfter(after.toInstant().atZone(ZoneId.of("UTC")))
}
}
date?.before?.let { before ->
filters.add {
it.date.isBefore(before.toInstant().atZone(ZoneId.of("UTC")))
}
}
filterCollection.get(VcsLogFilterCollection.BRANCH_FILTER)?.let { filter ->
channelFilters.add {
filter.matches(it)
}
}
filterCollection.get(VcsLogFilterCollection.REVISION_FILTER)?.let { filter ->
filters.add {
filter.heads.any { head ->
it.asSha1Hash == head.hash.asString()
}
}
}
filterCollection.get(VcsLogFilterCollection.USER_FILTER)?.let { filter ->
metadataFilters.add {
filter.matches(it)
}
}
filterCollection.get(VcsLogFilterCollection.HASH_FILTER)?.let { filter ->
filters.add {
filter.hashes.any { hash ->
it.changeHash == hash || it.asSha1Hash == hash
}
}
}
filterCollection.get(VcsLogFilterCollection.TEXT_FILTER)?.let { filter ->
filters.add {
filter.matches(it.message)
}
}
filterCollection.get(VcsLogFilterCollection.STRUCTURE_FILTER)?.let { filter ->
filters.add {
filter.files.any { filterRoot ->
val filterRootPath = filterRoot.convertToNio()
it.hunks.any { hunk ->
(hunk is HunkWithPath) && hunk.resolvePath(rootPath).startsWith(filterRootPath)
}
}
}
}
filterCollection.get(VcsLogFilterCollection.ROOT_FILTER)?.let { filter ->
filters.add {
filter.roots.any { filterRoot ->
val filterRootPath = filterRoot.convertToNio()
it.hunks.any { hunk ->
(hunk is HunkWithPath) && hunk.resolvePath(rootPath).startsWith(filterRootPath)
}
}
}
}
val records = mutableListOf<TimedVcsCommit>()
createMetadataForEntries(this.project, root, {
if (channelFilters.all { filter -> filter(it) }) {
this.project.pijul().log(this.project, root.convertToNio(), it).result?.entries.orEmpty()
.filter { filters.all { filter -> filter(it) } }
} else {
emptyList()
}
}, { _, it ->
if (fullDetailsFilters.all { filter -> filter(it) }) {
val meta = createMetadata(project, root, it)
if (metadataFilters.all { filter -> filter(meta) }) {
records += meta
}
}
})
return records
}
override fun getCurrentUser(root: VirtualFile): VcsUser? {
return null
}
override fun getContainingBranches(root: VirtualFile, commitHash: Hash): MutableCollection<String> {
val channels = mutableListOf<String>()
createMetadataForEntries(this.project, root, {
this.project.pijul().log(this.project, root.convertToNio(), it).result?.entries.orEmpty().filter {
DigestUtils.sha1Hex(it.changeHash) == commitHash.asString()
}
}, { c, it ->
channels += c
})
return channels
}
override fun <T : Any?> getPropertyValue(property: VcsLogProperties.VcsLogProperty<T>?): T? {
if (property == VcsLogProperties.HAS_COMMITTER) {
return true as T
}
return null
}
override fun getVcsRoot(project: Project, detectedRoot: VirtualFile, filePath: FilePath): VirtualFile? {
return Pijul.findPijulDirectory(detectedRoot)
}
override fun getCurrentBranch(root: VirtualFile): String? {
return this.project.pijul().channel(this.project, root).result?.channels?.firstOrNull { it.current }?.name
}
companion object {
fun createMetadata(
project: Project,
root: VirtualFile,
fetcher: (channel: String) -> List<PijulLogEntry>
): VcsLogProvider.DetailedLogData {
val commits = mutableListOf<VcsCommitMetadata>()
val log = createMetadata(project, root, fetcher) {
commits.add(it)
}
return LogDataImpl(log.refs, commits)
}
fun createMetadataForEntries(
project: Project,
root: VirtualFile,
fetcher: (channel: String) -> List<PijulLogEntry>,
consumer: (channel: String, PijulLogEntryCommitDetails) -> Unit
): VcsLogProvider.LogData {
val latest = project.pijul().latestRevisionNumber(project, root.convertToNio()).result?.hash
val allChannels = project.pijul().channel(project, root.convertToNio()).result?.channels.orEmpty()
val currentChannel = allChannels.firstOrNull {
it.current
}
val refs = mutableSetOf<VcsRef>()
val authors = mutableSetOf<VcsUser>()
val rootPath = root.convertToNio()
val factory = getObjectsFactoryWithDisposeCheck(project) ?: return LogDataImpl.empty()
allChannels.forEach { channel ->
val allTags = if (channel.current) {
// Only current tag of current channel is available through `pijul tag` ATM
project.pijul().tags(project, root.convertToNio()).result.orEmpty()
} else {
emptyList()
}
allTags.forEach {
val hash = HashImpl.build(DigestUtils.sha1Hex(it.hash))//PijulHash(it.hash)
val ref = factory.createRef(hash, it.simpleMessage, PijulVcsLogRefManager.TAG, root)
if (!refs.contains(ref)) refs.add(ref)
}
var first = true
fetcher(channel.name).forEach {
val hash = HashImpl.build(DigestUtils.sha1Hex(it.changeHash))//PijulHash(it.changeHash)
authors.addAll(it.authors.filter { it.name != null && it.email != null }
.map { VcsUserImpl(it.name!!, it.email!!) })
if (channel.current) {
// Only current tag of current channel is available through `pijul tag` ATM
val tag = allTags.singleOrNull()
if (tag != null) {
if (first) {
val ref = factory.createRef(hash, tag.simpleMessage, PijulVcsLogRefManager.TAG, root)
if (!refs.contains(ref)) refs.add(ref)
}
}
}
first = false
if (it.changeHash == latest) {
val ref = factory.createRef(hash, "CURRENT", PijulVcsLogRefManager.CURRENT, root)
if (!refs.contains(ref)) refs.add(ref)
}
val ref = factory.createRef(hash, channel.name, PijulVcsLogRefManager.CHANNEL, root)
if (!refs.contains(ref)) refs.add(ref)
val details = PijulLogEntryCommitDetails(
rootPath,
project,
it,
) {
val changes = mutableListOf<PijulCommittedChangeList>()
pijul(project).changes(project, rootPath, it, 1, {
true
}) {
changes += it
}
changes.single().changes.toList()
}
consumer(channel.name, details)
}
}
return LogDataImpl(refs, authors)
}
fun createMetadata(
project: Project,
root: VirtualFile,
fetcher: (channel: String) -> List<PijulLogEntry>,
consumer: (VcsCommitMetadata) -> Unit
): VcsLogProvider.LogData {
val factory = getObjectsFactoryWithDisposeCheck(project) ?: return LogDataImpl.empty()
return createMetadataForEntries(project, root, fetcher) { _, it ->
consumer(
factory.createCommitMetadata(
it.id,
emptyList(),
it.commitTime,
root,
it.subject,
it.author.name,
it.author.email,
it.fullMessage,
it.committer.name,
it.committer.email,
it.authorTime
)
)
}
}
fun createMetadata(
project: Project,
root: VirtualFile,
it: PijulLogEntryCommitDetails
): VcsCommitMetadata {
val factory = getObjectsFactoryWithDisposeCheck(project)!!
return factory.createCommitMetadata(
it.id,
emptyList(),
it.commitTime,
root,
it.subject,
it.author.name,
it.author.email,
it.fullMessage,
it.committer.name,
it.committer.email,
it.authorTime
)
}
fun getObjectsFactoryWithDisposeCheck(project: Project): VcsLogObjectsFactory? {
return ReadAction.compute<VcsLogObjectsFactory?, RuntimeException> {
if (!project.isDisposed) {
return@compute project.getService(VcsLogObjectsFactory::class.java)
}
null
}
}
}
}
package com.github.jonathanxd.dracon.log
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.vcs.log.Hash
import com.intellij.vcs.log.VcsFullCommitDetails
import com.intellij.vcs.log.VcsUser
import com.intellij.vcs.log.impl.HashImpl
import com.intellij.vcs.log.impl.VcsUserImpl
import org.apache.commons.codec.digest.DigestUtils
import java.nio.file.Path
class PijulLogEntryCommitDetails(
val root: Path,
val project: Project,
val entry: PijulLogEntry,
val changeRetriever: (String) -> List<Change>
) : VcsFullCommitDetails {
override fun getId(): Hash =
HashImpl.build(DigestUtils.sha1Hex(this.entry.changeHash))
//PijulHash(this.entry.changeHash)
override fun getParents(): MutableList<Hash> =
//this.entry.dependencies.map { it.hash }.map(::PijulHash).toMutableList()
this.entry.dependencies.map { it.hash }.map { DigestUtils.sha1Hex(it) }.map { HashImpl.build(it) }.toMutableList()
fun getPijulParents(): List<PijulHash> =
this.entry.dependencies.map { it.hash }.map(::PijulHash)
override fun getTimestamp(): Long = this.entry.date.toInstant().toEpochMilli()
override fun getRoot(): VirtualFile =
VirtualFileManager.getInstance().findFileByNioPath(this.root)!!
override fun getSubject(): String =
if (this.entry.message.isEmpty()) ""
else this.entry.message.split("\n").first { it.trim().isNotEmpty() }
override fun getAuthor(): VcsUser =
this.entry.authors.firstOrNull()?.let {
VcsUserImpl(it.name ?: "", it.email ?: "")
} ?: VcsUserImpl("", "")
override fun getCommitter(): VcsUser =
this.author
override fun getAuthorTime(): Long =
this.timestamp
override fun getCommitTime(): Long =
this.timestamp
override fun getFullMessage(): String =
this.entry.message
override fun getChanges(): MutableCollection<Change> =
this.changeRetriever(this.entry.changeHash).toMutableList()
override fun getChanges(parent: Int): MutableCollection<Change> =
this.changeRetriever(this.parents[parent].asString()).toMutableList()
}
val RFC3339_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS]XXX")
val RFC3339_FORMATTER = DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
.appendOffsetId()
.toFormatter()
}
}
fun isIgnored(root: Path, file: Path): Boolean {
val ignore = root.resolve(".ignore")
return if (Files.exists(ignore) && Files.isRegularFile(ignore)) {
val lines = Files.readAllLines(ignore, Charsets.UTF_8)
val paths = lines.map { path ->
root.resolve(path)
}
paths.any { path -> file.startsWith(path) }
} else {
false
override fun tags(project: Project, root: Path): PijulOperationResult<List<PijulTag>> {
val result = this.doExecutionWithMapperEvenFailed("tag", this.createPainlessExecPijulOperation(project, root, listOf("tag"))) {
it.parseTags()
}
if (result.result != null && result.result.isNotEmpty()) { // Force success because Pijul is failing with `Not a directory` even in success case.
return PijulOperationResult("tag", SuccessStatusCode, result.result)
} else {
return result
}
}
override fun log(project: Project, root: Path, channel: String): PijulOperationResult<PijulLog> {
val logHashExecution = this.createPainlessExecPijulOperation(project, root, listOf("log", "--hash-only", "--channel", channel))
val hashes = this.doExecutionWithMapper("log--hash-only", logHashExecution) {
it.lines()
}
if (hashes.statusCode !is SuccessStatusCode) {
return hashes as PijulOperationResult<PijulLog>
} else {
val entries = mutableListOf<PijulLogEntry>()
for (hash in hashes.result!!) {
if (hash.isEmpty()) {
break
}
val change = this.pijulLogEntryCache.load(hash) {
this.doExecutionWithMapper(
"change-$hash",
this.createPainlessExecPijulOperation(project, root, listOf("change", hash))
) {
it.parseChange(hash)
}
}
if (change.statusCode !is SuccessStatusCode) {
return change as PijulOperationResult<PijulLog>
} else if (change.result != null) {
entries += change.result
}
}
return PijulOperationResult(hashes.operation, hashes.statusCode, PijulLog(entries))
}
}
override fun revisionByChannel(project: Project, root: Path): PijulOperationResult<ChannelRevisions> {
return this.channel(project, root).flatMap {
val mapped = it.channels.map { ch ->
this.log(project, root, ch.name).map {
it.entries.firstOrNull()?.let {
ChannelRevision(ch.name, PijulRevisionNumber(it.changeHash, it.date))
}
}
}
for (m in mapped) {
if (m.result == null)
return@flatMap m as PijulOperationResult<ChannelRevisions>
}
PijulOperationResult(
"revision by channel",
SuccessStatusCode,
ChannelRevisions(mapped.map { it.result!! })
)
}
}
else ChannelInfo(channels.filter { it.isNotEmpty() }.map { PijulChannel(it[0] == '*', it.substring(1)) })
else ChannelInfo(channels.filter { it.isNotEmpty() }.map {
val name = it.substring(1)
val effectiveName = if (name.startsWith(" ")) name.substring(1) else name
PijulChannel(it[0] == '*', effectiveName)
})
package com.github.jonathanxd.dracon.channel
import com.intellij.dvcs.branch.BranchType
enum class PijulChannelType(private val nname: String) : BranchType {
CHANNEL("CHANNEL");
override fun getName(): String = this.nname
}
package com.github.jonathanxd.dracon.channel
import com.github.jonathanxd.dracon.config.PijulSettings
import com.intellij.dvcs.branch.BranchType
import com.intellij.dvcs.branch.DvcsBranchManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
@Service
class PijulChannelManager(val project: Project) : DvcsBranchManager(
project,
PijulSettings.instance.getChannelSettings(),
PijulChannelType.values()
) {
override fun getDefaultBranchName(type: BranchType): String? =
when (type) {
PijulChannelType.CHANNEL -> "main"
else -> null
}
}