7L5LODGZ7AN4ZULDJZMLALD7PL6E57VZSNNSG67SFJARUJGCT47QC message = 'Initial CahngeProvider'timestamp = '2021-03-29T01:50:38.537749028Z'[[authors]]name = 'Jonathan H. R. Lopes'# Dependencies[2] PGNTR2EPCZBOWI67LKY6AN5B3RGIEOQ6NTTXGODLESKDSPWV26KQC[3]+FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC[4]+OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC[5]+GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC[*] NTRPUMVQHUIQZ6O72NJ72XFYTZWZOSDA6CSKMUCGKFVNE3KIDYYQC# Hunks1. File addition: "revision" in "src/main/kotlin/com/github/jonathanxd/dracon" +dxup 3.107, new 0:102. File addition: "PijulRevisionNumber.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/revision"up 0.11, new 12:36+ package com.github.jonathanxd.dracon.revision++ import com.intellij.openapi.vcs.changes.patch.BlobIndexUtil+ import com.intellij.openapi.vcs.history.ShortVcsRevisionNumber+ import com.intellij.openapi.vcs.history.VcsRevisionNumber+ import java.time.LocalDateTime+ import java.time.ZonedDateTime++ class PijulRevisionNumber(val hash: String, val timestamp: ZonedDateTime) : ShortVcsRevisionNumber {++ val NOT_COMMITTED_HASH = BlobIndexUtil.NOT_COMMITTED_HASH++ override fun compareTo(other: VcsRevisionNumber): Int {+ if (this === other) return 0++ if (other is PijulRevisionNumber) {+ return this.timestamp.compareTo(other.timestamp)+ }++ return -1+ }++ override fun asString(): String = this.hash++ override fun toShortString(): String = this.asString()+ }\3. File addition: "provider" in "src/main/kotlin/com/github/jonathanxd/dracon" +dxup 3.107, new 843:8534. File addition: "PijulChangeProvider.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/provider"up 0.854, new 855:879+ package com.github.jonathanxd.dracon.provider++ import com.intellij.openapi.progress.ProgressIndicator+ import com.intellij.openapi.project.Project+ import com.intellij.openapi.vcs.VcsKey+ import com.intellij.openapi.vcs.changes.*++ class PijulChangeProvider(val project: Project, val key: VcsKey) : ChangeProvider {+ override fun getChanges(+ dirtyScope: VcsDirtyScope,+ builder: ChangelistBuilder,+ progress: ProgressIndicator,+ addGate: ChangeListManagerGate+ ) {+ if (project.isDisposed) return+ val dirtDirs = dirtyScope.recursivelyDirtyDirectories++ println(dirtDirs)+ }++ override fun isModifiedDocumentTrackingRequired(): Boolean = true+ }\5. Edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt:13 3.4428up 3.4473, new 1584:1698, down 3.4473+ import com.github.jonathanxd.dracon.log.PijulLog+ import com.github.jonathanxd.dracon.revision.PijulRevisionNumber6. Edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt:82 3.4428up 3.6839, new 1699:1950, down 3.6839+ @RequiresBackgroundThread+ fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber>++ @RequiresBackgroundThread+ fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog>7. File addition: "PijulLog.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/log"up 3.6847, new 1951:1964+ package com.github.jonathanxd.dracon.log++ import java.time.LocalDateTime+ import java.time.ZonedDateTime+ import java.time.format.DateTimeFormatter+ import java.time.format.ResolverStyle++++++++ class Author(val name: String?, val fullName: String?, val email: String?)+ class PijulLog(val entries: List<PijulLogEntry>)++ class PijulLogEntry(+ val changeHash: String,+ val message: String,+ val date: ZonedDateTime,+ val authors: List<Author>+ )+++ val MESSAGE_PATTERN = Regex("message = '(.*)'\\n")+ val TIME_PATTERN = Regex("timestamp = '(.*)'\\n")+ val AUTHORS_SECTION_PATTERN = Regex("\\[\\[authors\\]\\]\n")+ val AUTHOR_PATTERN = Regex("name = '(.*)'\\n")++ fun String.parseChange(hash: String): PijulLogEntry {+ val message = MESSAGE_PATTERN.find(this)?.groupValues?.get(1)!!+ val timestamp = TIME_PATTERN.find(this)?.groupValues?.get(1)!!+ val authorsSection = AUTHORS_SECTION_PATTERN.find(this)?.range?.endInclusive+ val authors = mutableListOf<Author>()++ if (authorsSection != null) {++ var lastFound: Int? = authorsSection+ while (lastFound != null) {+ val foundAuthor = AUTHOR_PATTERN.find(this, lastFound)+ val foundAuthorGroup = foundAuthor?.groupValues?.get(1)++ if (foundAuthorGroup != null) {+ authors += Author(foundAuthorGroup, null, null)+ lastFound = if (foundAuthor.range.last + 1 < this.length && this[foundAuthor.range.last + 1] == '\n') {+ null+ } else {+ foundAuthor.range.last+ }+ } else {+ lastFound = null+ }+ }+ }++ return PijulLogEntry(hash, message, timestamp.parseAsLocalDateTime(), authors)+ }++ val RFC3339_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS]XXX")+ .withResolverStyle(ResolverStyle.LENIENT)++ fun String.parseAsLocalDateTime(): ZonedDateTime {+ return ZonedDateTime.parse(this, RFC3339_FORMATTER)+ }\8. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:13 3.10972up 3.11015, new 4000:4103, down 3.11015+ import com.github.jonathanxd.dracon.log.PijulLog+ import com.github.jonathanxd.dracon.log.PijulLogEntry9. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:16 3.10972up 3.11075, new 4104:4156, down 3.11075+ import com.github.jonathanxd.dracon.log.parseChange10. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:20 3.10972up 4.2779, new 4157:4222, down 3.11119+ import com.github.jonathanxd.dracon.revision.PijulRevisionNumber11. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:106 3.10972up 3.13610, new 4223:4490, down 3.13610++ override fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog> {+ val rootPath = Paths.get(VcsUtil.getFilePath(root).path)++ val logHashExecution = this.execPijul(project, rootPath, listOf("log", "--hash-only"), delay = 10L)12. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:112 3.10972up 3.13611, new 4491:5952, down 3.13611+ 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!!) {+ val change = this.doExecutionWithMapper("change-$hash", this.execPijul(project, rootPath, listOf("change", hash), delay = 10L)) {+ it.parseChange(hash)+ }++ if (change.statusCode !is SuccessStatusCode) {+ return change as PijulOperationResult<PijulLog>+ }+ }++ return PijulOperationResult(hashes.operation, hashes.statusCode, PijulLog(entries))+ }+ }++ override fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber> {+ /*val root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file)+ ?: return PijulOperationResult("file_status", SuccessStatusCode, FileStatus.UNKNOWN)*/+ val rootPath = Paths.get(VcsUtil.getFilePath(root).path)++ val log = this.log(project, root)++ return PijulOperationResult(log.operation, log.statusCode,+ log.result?.entries?.firstOrNull()?.let {+ PijulRevisionNumber(it.changeHash, it.date)+ }+ )+ }+13. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:14 3.24146up 3.24239, new 5953:6018, down 3.24239+ import com.github.jonathanxd.dracon.provider.PijulChangeProvider14. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:20 3.24146up 3.24475, new 6019:6074, down 3.24475+ import com.intellij.openapi.vcs.changes.ChangeProvider15. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:46 3.24146up 3.25153, new 6075:6178, down 3.25153+ override fun getChangeProvider(): ChangeProvider =+ PijulChangeProvider(this.project, KEY)+16. File addition: "dada.toml" in "/"up 1.0, new 6179:619017. Edit in build.gradle.kts:33 5.13712up 5.14803, new 6193:6231, down 5.14803+ maven(url = "https://jitpack.io")18. Edit in build.gradle.kts:36 5.13712up 5.14820, new 6232:6394, down 5.14820+ implementation("org.tomlj:tomlj:1.0.0")+ implementation("com.github.JonathanxD.JwIUtils:JwIUtils:4.17.0") {+ exclude(group = "org.jetbrains")+ }19. Replacement in README.md:21 5.17946B:BD 2.59651 -> 2.59651:59660/2up 2.59651, new 6395:8239- - [ ] ...+ - [ ] ...+++ # FAQ++ ## Why Dracon and not Pijul for IDEA (or something like that)?++ Because Dracon is not an official plugin, and I want to make sure that it is clear to anyone who uses this plugin. Dracon is an individual project being developed by [Jonathan H. R. Lopes](https://github.com/JonathanxD) which does not have any relation with Pijul creator. Also, Dracon uses MIT license, allowing to anyone, and that includes the Pijul creator, to contribute and create your own version of Dracon, if wish to.++ ## Why Dracon is developed against IntelliJ EAP instead of a stable releases?++ Dracon is being developed based in actual [HG4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/hg4idea) and [Git4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/git4idea) plugins, as well based in current code of [IntelliJ IDEA Community](https://github.com/JetBrains/intellij-community/), and is planned to be released by the end of first semester of 2021, when release time comes, the IntelliJ EAP will be already released as stable, also I don't want to support older releases of IntelliJ IDEA because this involves basing on deprecated code, and this is my first IDEA plugin, so the more I focus the present than the past, more I deliver in terms of better and stable features.++ ## Could I contribute to Dracon development?++ I'm currently focused on learning how IDEA Plugin platform works and how DVCS is implemented in IDEA, so until Dracon is officially released, I will not be accepting contributions, but Dracon is licensed under MIT, you are free to create your own version of Dracon and developing it on your own.++ ## When Dracon will be official released?++ I will be periodically releasing Dragon experimental builds, but Dracon stable release is planned by the end of first semester of 2021.\
package com.github.jonathanxd.dracon.testimport com.github.jonathanxd.dracon.log.*import io.kotest.core.spec.style.ShouldSpecimport io.kotest.matchers.shouldBeimport java.time.ZoneOffsetimport java.time.ZonedDateTimeclass DraconTest : ShouldSpec({should("correctly parse a 'pijul change' text without authors") {val text = """message = 'Initial ChangeProvider'timestamp = '2021-03-29T01:50:38.537749028Z'""".trimIndent()val change = text.parseChange("DRACON")change.changeHash shouldBe "DRACON"change.message shouldBe "Initial ChangeProvider"change.date shouldBe ZonedDateTime.of(2021, 3, 29, 1, 50, 38, 537749028, ZoneOffset.UTC)}should("correctly parse a 'pijul change' text with single author") {val text = """message = 'Initial ChangeProvider'timestamp = '2021-03-29T01:50:38.537749028Z'[[authors]]name = 'Author'""".trimIndent()val change = text.parseChange("DRACON")change.changeHash shouldBe "DRACON"change.message shouldBe "Initial ChangeProvider"change.authors.isEmpty() shouldBe falsechange.authors[0].name shouldBe "Author"change.date shouldBe ZonedDateTime.of(2021, 3, 29, 1, 50, 38, 537749028, ZoneOffset.UTC)}should("correctly parse a 'pijul change' text with two authors") {val text = """message = 'Initial ChangeProvider'timestamp = '2021-03-29T01:50:38.537749028Z'[[authors]]name = 'Author1'name = 'Author2'""".trimIndent()val change = text.parseChange("DRACON")change.changeHash shouldBe "DRACON"change.message shouldBe "Initial ChangeProvider"change.authors.size shouldBe 2change.authors[0].name shouldBe "Author1"change.authors[1].name shouldBe "Author2"change.date shouldBe ZonedDateTime.of(2021, 3, 29, 1, 50, 38, 537749028, ZoneOffset.UTC)}should("correctly parse a 'pijul change' text with three authors and dependencies") {val text = """message = 'Initial ChangeProvider'timestamp = '2021-03-29T01:50:38.537749028Z'[[authors]]name = 'Author1'name = 'Author2'name = 'Author3'# Dependencies[2] PGNTR2EPCZBOWI67LKY6AN5B3RGIEOQ6NTTXGODLESKDSPWV26KQC[3]+FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC[4]+OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC[5]+GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC[*] NTRPUMVQHUIQZ6O72NJ72XFYTZWZOSDA6CSKMUCGKFVNE3KIDYYQC""".trimIndent()val change = text.parseChange("DRACON")change.changeHash shouldBe "DRACON"change.message shouldBe "Initial ChangeProvider"change.authors.size shouldBe 3change.authors[0].name shouldBe "Author1"change.authors[1].name shouldBe "Author2"change.authors[2].name shouldBe "Author3"change.date shouldBe ZonedDateTime.of(2021, 3, 29, 1, 50, 38, 537749028, ZoneOffset.UTC)change.dependencies.size shouldBe 5change.dependencies[0] shouldBe Dependency(IndexInt(2),DependencyType.REGULAR_DEPENDENCY,"PGNTR2EPCZBOWI67LKY6AN5B3RGIEOQ6NTTXGODLESKDSPWV26KQC")change.dependencies[1] shouldBe Dependency(IndexInt(3),DependencyType.CHANGE_DEPENDENCY,"FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC")change.dependencies[2] shouldBe Dependency(IndexInt(4),DependencyType.CHANGE_DEPENDENCY,"OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC")change.dependencies[3] shouldBe Dependency(IndexInt(5),DependencyType.CHANGE_DEPENDENCY,"GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC")change.dependencies[4] shouldBe Dependency(Asterisk,DependencyType.EXTRA_KNOWN_DEPENDENCY,"NTRPUMVQHUIQZ6O72NJ72XFYTZWZOSDA6CSKMUCGKFVNE3KIDYYQC")}should("correctly parse a 'pijul change' text with example change") {val text = """message = 'Initial ChangeProvider'timestamp = '2021-03-29T01:50:38.537749028Z'[[authors]]name = 'Author1'# Dependencies[2] PGNTR2EPCZBOWI67LKY6AN5B3RGIEOQ6NTTXGODLESKDSPWV26KQC[3]+FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC[4]+OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC[5]+GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC[*] NTRPUMVQHUIQZ6O72NJ72XFYTZWZOSDA6CSKMUCGKFVNE3KIDYYQC# Hunks1. File addition: "revision" in "src/main/kotlin/com/github/jonathanxd/dracon" +dxup 3.107, new 0:102. File addition: "PijulRevisionNumber.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/revision"up 0.11, new 12:36+ package com.github.jonathanxd.dracon.revision++ import com.intellij.openapi.vcs.changes.patch.BlobIndexUtil+ import com.intellij.openapi.vcs.history.ShortVcsRevisionNumber+ import com.intellij.openapi.vcs.history.VcsRevisionNumber+ import java.time.LocalDateTime+ import java.time.ZonedDateTime++ class PijulRevisionNumber(val hash: String, val timestamp: ZonedDateTime) : ShortVcsRevisionNumber {++ val NOT_COMMITTED_HASH = BlobIndexUtil.NOT_COMMITTED_HASH++ override fun compareTo(other: VcsRevisionNumber): Int {+ if (this === other) return 0++ if (other is PijulRevisionNumber) {+ return this.timestamp.compareTo(other.timestamp)+ }++ return -1+ }++ override fun asString(): String = this.hash++ override fun toShortString(): String = this.asString()+ }\3. File addition: "provider" in "src/main/kotlin/com/github/jonathanxd/dracon" +dxup 3.107, new 843:8534. File addition: "PijulChangeProvider.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/provider"up 0.854, new 855:879+ package com.github.jonathanxd.dracon.provider++ import com.intellij.openapi.progress.ProgressIndicator+ import com.intellij.openapi.project.Project+ import com.intellij.openapi.vcs.VcsKey+ import com.intellij.openapi.vcs.changes.*++ class PijulChangeProvider(val project: Project, val key: VcsKey) : ChangeProvider {+ override fun getChanges(+ dirtyScope: VcsDirtyScope,+ builder: ChangelistBuilder,+ progress: ProgressIndicator,+ addGate: ChangeListManagerGate+ ) {+ if (project.isDisposed) return+ val dirtDirs = dirtyScope.recursivelyDirtyDirectories++ println(dirtDirs)+ }++ override fun isModifiedDocumentTrackingRequired(): Boolean = true+ }\5. Edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt:13 3.4428up 3.4473, new 1584:1698, down 3.4473+ import com.github.jonathanxd.dracon.log.PijulLog+ import com.github.jonathanxd.dracon.revision.PijulRevisionNumber6. Edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt:82 3.4428up 3.6839, new 1699:1950, down 3.6839+ @RequiresBackgroundThread+ fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber>++ @RequiresBackgroundThread+ fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog>7. File addition: "PijulLog.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/log"up 3.6847, new 1951:1964+ package com.github.jonathanxd.dracon.log++ import java.time.LocalDateTime+ import java.time.ZonedDateTime+ import java.time.format.DateTimeFormatter+ import java.time.format.ResolverStyle++++++++ class Author(val name: String?, val fullName: String?, val email: String?)+ class PijulLog(val entries: List<PijulLogEntry>)++ class PijulLogEntry(+ val changeHash: String,+ val message: String,+ val date: ZonedDateTime,+ val authors: List<Author>+ )+++ val MESSAGE_PATTERN = Regex("message = '(.*)'\\n")+ val TIME_PATTERN = Regex("timestamp = '(.*)'\\n")+ val AUTHORS_SECTION_PATTERN = Regex("\\[\\[authors\\]\\]\n")+ val AUTHOR_PATTERN = Regex("name = '(.*)'\\n")++ fun String.parseChange(hash: String): PijulLogEntry {+ val message = MESSAGE_PATTERN.find(this)?.groupValues?.get(1)!!+ val timestamp = TIME_PATTERN.find(this)?.groupValues?.get(1)!!+ val authorsSection = AUTHORS_SECTION_PATTERN.find(this)?.range?.endInclusive+ val authors = mutableListOf<Author>()++ if (authorsSection != null) {++ var lastFound: Int? = authorsSection+ while (lastFound != null) {+ val foundAuthor = AUTHOR_PATTERN.find(this, lastFound)+ val foundAuthorGroup = foundAuthor?.groupValues?.get(1)++ if (foundAuthorGroup != null) {+ authors += Author(foundAuthorGroup, null, null)+ lastFound = if (foundAuthor.range.last + 1 < this.length && this[foundAuthor.range.last + 1] == '\n') {+ null+ } else {+ foundAuthor.range.last+ }+ } else {+ lastFound = null+ }+ }+ }++ return PijulLogEntry(hash, message, timestamp.parseAsLocalDateTime(), authors)+ }++ val RFC3339_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS]XXX")+ .withResolverStyle(ResolverStyle.LENIENT)++ fun String.parseAsLocalDateTime(): ZonedDateTime {+ return ZonedDateTime.parse(this, RFC3339_FORMATTER)+ }\8. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:13 3.10972up 3.11015, new 4000:4103, down 3.11015+ import com.github.jonathanxd.dracon.log.PijulLog+ import com.github.jonathanxd.dracon.log.PijulLogEntry9. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:16 3.10972up 3.11075, new 4104:4156, down 3.11075+ import com.github.jonathanxd.dracon.log.parseChange10. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:20 3.10972up 4.2779, new 4157:4222, down 3.11119+ import com.github.jonathanxd.dracon.revision.PijulRevisionNumber11. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:106 3.10972up 3.13610, new 4223:4490, down 3.13610++ override fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog> {+ val rootPath = Paths.get(VcsUtil.getFilePath(root).path)++ val logHashExecution = this.execPijul(project, rootPath, listOf("log", "--hash-only"), delay = 10L)12. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:112 3.10972up 3.13611, new 4491:5952, down 3.13611+ 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!!) {+ val change = this.doExecutionWithMapper("change-""" + "\$hash" + """, this.execPijul(project, rootPath, listOf("change", hash), delay = 10L)) {+ it.parseChange(hash)+ }++ if (change.statusCode !is SuccessStatusCode) {+ return change as PijulOperationResult<PijulLog>+ }+ }++ return PijulOperationResult(hashes.operation, hashes.statusCode, PijulLog(entries))+ }+ }++ override fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber> {+ /*val root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file)+ ?: return PijulOperationResult("file_status", SuccessStatusCode, FileStatus.UNKNOWN)*/+ val rootPath = Paths.get(VcsUtil.getFilePath(root).path)++ val log = this.log(project, root)++ return PijulOperationResult(log.operation, log.statusCode,+ log.result?.entries?.firstOrNull()?.let {+ PijulRevisionNumber(it.changeHash, it.date)+ }+ )+ }+13. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:14 3.24146up 3.24239, new 5953:6018, down 3.24239+ import com.github.jonathanxd.dracon.provider.PijulChangeProvider14. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:20 3.24146up 3.24475, new 6019:6074, down 3.24475+ import com.intellij.openapi.vcs.changes.ChangeProvider15. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:46 3.24146up 3.25153, new 6075:6178, down 3.25153+ override fun getChangeProvider(): ChangeProvider =+ PijulChangeProvider(this.project, KEY)+16. File addition: "dada.toml" in "/"up 1.0, new 6179:619017. Edit in build.gradle.kts:33 5.13712up 5.14803, new 6193:6231, down 5.14803+ maven(url = "https://jitpack.io")18. Edit in build.gradle.kts:36 5.13712up 5.14820, new 6232:6394, down 5.14820+ implementation("org.tomlj:tomlj:1.0.0")+ implementation("com.github.JonathanxD.JwIUtils:JwIUtils:4.17.0") {+ exclude(group = "org.jetbrains")+ }19. Replacement in README.md:21 5.17946B:BD 2.59651 -> 2.59651:59660/2up 2.59651, new 6395:8239- - [ ] ...+ - [ ] ...+++ # FAQ++ ## Why Dracon and not Pijul for IDEA (or something like that)?++ Because Dracon is not an official plugin, and I want to make sure that it is clear to anyone who uses this plugin. Dracon is an individual project being developed by [Jonathan H. R. Lopes](https://github.com/JonathanxD) which does not have any relation with Pijul creator. Also, Dracon uses MIT license, allowing to anyone, and that includes the Pijul creator, to contribute and create your own version of Dracon, if wish to.++ ## Why Dracon is developed against IntelliJ EAP instead of a stable releases?++ Dracon is being developed based in actual [HG4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/hg4idea) and [Git4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/git4idea) plugins, as well based in current code of [IntelliJ IDEA Community](https://github.com/JetBrains/intellij-community/), and is planned to be released by the end of first semester of 2021, when release time comes, the IntelliJ EAP will be already released as stable, also I don't want to support older releases of IntelliJ IDEA because this involves basing on deprecated code, and this is my first IDEA plugin, so the more I focus the present than the past, more I deliver in terms of better and stable features.++ ## Could I contribute to Dracon development?++ I'm currently focused on learning how IDEA Plugin platform works and how DVCS is implemented in IDEA, so until Dracon is officially released, I will not be accepting contributions, but Dracon is licensed under MIT, you are free to create your own version of Dracon and developing it on your own.++ ## When Dracon will be official released?++ I will be periodically releasing Dragon experimental builds, but Dracon stable release is planned by the end of first semester of 2021.\""".trimIndent()val change = text.parseChange("DRACON")change.changeHash shouldBe "DRACON"change.message shouldBe "Initial ChangeProvider"change.authors.size shouldBe 1change.authors[0].name shouldBe "Author1"change.date shouldBe ZonedDateTime.of(2021, 3, 29, 1, 50, 38, 537749028, ZoneOffset.UTC)change.dependencies.size shouldBe 5change.dependencies[0] shouldBe Dependency(IndexInt(2),DependencyType.REGULAR_DEPENDENCY,"PGNTR2EPCZBOWI67LKY6AN5B3RGIEOQ6NTTXGODLESKDSPWV26KQC")change.dependencies[1] shouldBe Dependency(IndexInt(3),DependencyType.CHANGE_DEPENDENCY,"FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC")change.dependencies[2] shouldBe Dependency(IndexInt(4),DependencyType.CHANGE_DEPENDENCY,"OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC")change.dependencies[3] shouldBe Dependency(IndexInt(5),DependencyType.CHANGE_DEPENDENCY,"GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC")change.dependencies[4] shouldBe Dependency(Asterisk,DependencyType.EXTRA_KNOWN_DEPENDENCY,"NTRPUMVQHUIQZ6O72NJ72XFYTZWZOSDA6CSKMUCGKFVNE3KIDYYQC")change.hunks.size shouldBe 19val sources = change.hunks.map {when (it) {is FileAddHunk -> it.fileAddHunkAsSource()is EditHunk -> it.editHunkAsSource()is ReplacementHunk -> it.replacementHunkAsSource()else -> it.toString()}}println(sources)change.hunks[0] shouldBe FileAddHunk(hunkNumber = 1,type = HunkType.FILE_ADDITION,path = "revision",rootPath = "src/main/kotlin/com/github/jonathanxd/dracon",lines = emptyList(),meta = Meta("+dx"),flags = emptyList(),context = Context(up = listOf(3.107), new = IntRange(0, 10), down = emptyList()),)change.hunks[1] shouldBe FileAddHunk(hunkNumber = 2,type = HunkType.FILE_ADDITION,path = "PijulRevisionNumber.kt",rootPath = "src/main/kotlin/com/github/jonathanxd/dracon/revision",lines = listOf(LineChange(type = LineChangeType.ADD, data = "package com.github.jonathanxd.dracon.revision"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "import com.intellij.openapi.vcs.changes.patch.BlobIndexUtil"),LineChange(type = LineChangeType.ADD,data = "import com.intellij.openapi.vcs.history.ShortVcsRevisionNumber"),LineChange(type = LineChangeType.ADD,data = "import com.intellij.openapi.vcs.history.VcsRevisionNumber"),LineChange(type = LineChangeType.ADD, data = "import java.time.LocalDateTime"),LineChange(type = LineChangeType.ADD, data = "import java.time.ZonedDateTime"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "class PijulRevisionNumber(val hash: String, val timestamp: ZonedDateTime) : ShortVcsRevisionNumber {"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " val NOT_COMMITTED_HASH = BlobIndexUtil.NOT_COMMITTED_HASH"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " override fun compareTo(other: VcsRevisionNumber): Int {"),LineChange(type = LineChangeType.ADD, data = " if (this === other) return 0"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " if (other is PijulRevisionNumber) {"),LineChange(type = LineChangeType.ADD,data = " return this.timestamp.compareTo(other.timestamp)"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " return -1"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " override fun asString(): String = this.hash"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " override fun toShortString(): String = this.asString()"),LineChange(type = LineChangeType.ADD, data = "}")),meta = Meta(""),flags = emptyList(),context = Context(up = listOf(0.11), new = IntRange(12, 36), down = emptyList()),)change.hunks[2] shouldBe FileAddHunk(hunkNumber = 3,type = HunkType.FILE_ADDITION,path = "provider",rootPath = "src/main/kotlin/com/github/jonathanxd/dracon",lines = emptyList(),meta = Meta("+dx"),flags = emptyList(),context = Context(up = listOf(3.107), new = IntRange(843, 853), down = emptyList()),)change.hunks[3] shouldBe FileAddHunk(hunkNumber = 4,type = HunkType.FILE_ADDITION,path = "PijulChangeProvider.kt",rootPath = "src/main/kotlin/com/github/jonathanxd/dracon/provider",lines = listOf(LineChange(type = LineChangeType.ADD, data = "package com.github.jonathanxd.dracon.provider"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "import com.intellij.openapi.progress.ProgressIndicator"),LineChange(type = LineChangeType.ADD, data = "import com.intellij.openapi.project.Project"),LineChange(type = LineChangeType.ADD, data = "import com.intellij.openapi.vcs.VcsKey"),LineChange(type = LineChangeType.ADD, data = "import com.intellij.openapi.vcs.changes.*"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "class PijulChangeProvider(val project: Project, val key: VcsKey) : ChangeProvider {"),LineChange(type = LineChangeType.ADD, data = " override fun getChanges("),LineChange(type = LineChangeType.ADD, data = " dirtyScope: VcsDirtyScope,"),LineChange(type = LineChangeType.ADD, data = " builder: ChangelistBuilder,"),LineChange(type = LineChangeType.ADD, data = " progress: ProgressIndicator,"),LineChange(type = LineChangeType.ADD, data = " addGate: ChangeListManagerGate"),LineChange(type = LineChangeType.ADD, data = " ) {"),LineChange(type = LineChangeType.ADD, data = " if (project.isDisposed) return"),LineChange(type = LineChangeType.ADD,data = " val dirtDirs = dirtyScope.recursivelyDirtyDirectories"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " println(dirtDirs)"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " override fun isModifiedDocumentTrackingRequired(): Boolean = true"),LineChange(type = LineChangeType.ADD, data = "}")),meta = Meta(""),flags = emptyList(),context = Context(up = listOf(0.854), new = IntRange(855, 879), down = emptyList()),)change.hunks[4] shouldBe EditHunk(hunkNumber = 5,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt",startLine = 13,inode = 3.4428,lines = listOf(LineChange(type = LineChangeType.ADD,data = "import com.github.jonathanxd.dracon.log.PijulLog"),LineChange(type = LineChangeType.ADD,data = "import com.github.jonathanxd.dracon.revision.PijulRevisionNumber")),meta = null,flags = emptyList(),context = Context(up = listOf(3.4473), new = IntRange(1584, 1698), down = listOf(3.4473)),)change.hunks[5] shouldBe EditHunk(hunkNumber = 6,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt",startLine = 82,inode = 3.4428,lines = listOf(LineChange(type = LineChangeType.ADD, data = " @RequiresBackgroundThread"),LineChange(type = LineChangeType.ADD,data = " fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber>"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " @RequiresBackgroundThread"),LineChange(type = LineChangeType.ADD,data = " fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog>")),meta = null,flags = emptyList(),context = Context(up = listOf(3.6839), new = IntRange(1699, 1950), down = listOf(3.6839)),)change.hunks[6] shouldBe FileAddHunk(hunkNumber = 7,type = HunkType.FILE_ADDITION,path = "PijulLog.kt",rootPath = "src/main/kotlin/com/github/jonathanxd/dracon/log",lines = listOf(LineChange(type = LineChangeType.ADD, data = "package com.github.jonathanxd.dracon.log"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "import java.time.LocalDateTime"),LineChange(type = LineChangeType.ADD, data = "import java.time.ZonedDateTime"),LineChange(type = LineChangeType.ADD, data = "import java.time.format.DateTimeFormatter"),LineChange(type = LineChangeType.ADD, data = "import java.time.format.ResolverStyle"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "class Author(val name: String?, val fullName: String?, val email: String?)"),LineChange(type = LineChangeType.ADD, data = "class PijulLog(val entries: List<PijulLogEntry>)"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "class PijulLogEntry("),LineChange(type = LineChangeType.ADD, data = " val changeHash: String,"),LineChange(type = LineChangeType.ADD, data = " val message: String,"),LineChange(type = LineChangeType.ADD, data = " val date: ZonedDateTime,"),LineChange(type = LineChangeType.ADD, data = " val authors: List<Author>"),LineChange(type = LineChangeType.ADD, data = ")"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "val MESSAGE_PATTERN = Regex(\"message = '(.*)'\\\\n\")"),LineChange(type = LineChangeType.ADD, data = "val TIME_PATTERN = Regex(\"timestamp = '(.*)'\\\\n\")"),LineChange(type = LineChangeType.ADD,data = "val AUTHORS_SECTION_PATTERN = Regex(\"\\\\[\\\\[authors\\\\]\\\\]\\n\")"),LineChange(type = LineChangeType.ADD, data = "val AUTHOR_PATTERN = Regex(\"name = '(.*)'\\\\n\")"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "fun String.parseChange(hash: String): PijulLogEntry {"),LineChange(type = LineChangeType.ADD,data = " val message = MESSAGE_PATTERN.find(this)?.groupValues?.get(1)!!"),LineChange(type = LineChangeType.ADD,data = " val timestamp = TIME_PATTERN.find(this)?.groupValues?.get(1)!!"),LineChange(type = LineChangeType.ADD,data = " val authorsSection = AUTHORS_SECTION_PATTERN.find(this)?.range?.endInclusive"),LineChange(type = LineChangeType.ADD, data = " val authors = mutableListOf<Author>()"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " if (authorsSection != null) {"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " var lastFound: Int? = authorsSection"),LineChange(type = LineChangeType.ADD, data = " while (lastFound != null) {"),LineChange(type = LineChangeType.ADD,data = " val foundAuthor = AUTHOR_PATTERN.find(this, lastFound)"),LineChange(type = LineChangeType.ADD,data = " val foundAuthorGroup = foundAuthor?.groupValues?.get(1)"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " if (foundAuthorGroup != null) {"),LineChange(type = LineChangeType.ADD,data = " authors += Author(foundAuthorGroup, null, null)"),LineChange(type = LineChangeType.ADD,data = " lastFound = if (foundAuthor.range.last + 1 < this.length && this[foundAuthor.range.last + 1] == '\\n') {"),LineChange(type = LineChangeType.ADD, data = " null"),LineChange(type = LineChangeType.ADD, data = " } else {"),LineChange(type = LineChangeType.ADD, data = " foundAuthor.range.last"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = " } else {"),LineChange(type = LineChangeType.ADD, data = " lastFound = null"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " return PijulLogEntry(hash, message, timestamp.parseAsLocalDateTime(), authors)"),LineChange(type = LineChangeType.ADD, data = "}"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "val RFC3339_FORMATTER = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS]XXX\")"),LineChange(type = LineChangeType.ADD, data = " .withResolverStyle(ResolverStyle.LENIENT)"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "fun String.parseAsLocalDateTime(): ZonedDateTime {"),LineChange(type = LineChangeType.ADD, data = " return ZonedDateTime.parse(this, RFC3339_FORMATTER)"),LineChange(type = LineChangeType.ADD, data = "}")),meta = Meta(""),flags = emptyList(),context = Context(up = listOf(3.6847), new = IntRange(1951, 1964), down = emptyList()),)change.hunks[7] shouldBe EditHunk(hunkNumber = 8,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt",startLine = 13,inode = 3.10972,lines = listOf(LineChange(type = LineChangeType.ADD,data = "import com.github.jonathanxd.dracon.log.PijulLog"), LineChange(type = LineChangeType.ADD, data = "import com.github.jonathanxd.dracon.log.PijulLogEntry")),meta = null,flags = emptyList(),context = Context(up = listOf(3.11015), new = IntRange(4000, 4103), down = listOf(3.11015)),)change.hunks[8] shouldBe EditHunk(hunkNumber = 9,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt",startLine = 16,inode = 3.10972,lines = listOf(LineChange(type = LineChangeType.ADD,data = "import com.github.jonathanxd.dracon.log.parseChange")),meta = null,flags = emptyList(),context = Context(up = listOf(3.11075), new = IntRange(4104, 4156), down = listOf(3.11075)),)change.hunks[9] shouldBe EditHunk(hunkNumber = 10,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt",startLine = 20,inode = 3.10972,lines = listOf(LineChange(type = LineChangeType.ADD,data = "import com.github.jonathanxd.dracon.revision.PijulRevisionNumber")),meta = null,flags = emptyList(),context = Context(up = listOf(4.2779), new = IntRange(4157, 4222), down = listOf(3.11119)),)change.hunks[10] shouldBe EditHunk(hunkNumber = 11,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt",startLine = 106,inode = 3.10972,lines = listOf(LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " override fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog> {"),LineChange(type = LineChangeType.ADD,data = " val rootPath = Paths.get(VcsUtil.getFilePath(root).path)"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " val logHashExecution = this.execPijul(project, rootPath, listOf(\"log\", \"--hash-only\"), delay = 10L)")),meta = null,flags = emptyList(),context = Context(up = listOf(3.1361), new = IntRange(4223, 4490), down = listOf(3.1361)),)change.hunks[11] shouldBe EditHunk(hunkNumber = 12,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt",startLine = 112,inode = 3.10972,lines = listOf(LineChange(type = LineChangeType.ADD,data = " val hashes = this.doExecutionWithMapper(\"log--hash-only\", logHashExecution) {"),LineChange(type = LineChangeType.ADD, data = " it.lines()"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " if (hashes.statusCode !is SuccessStatusCode) {"),LineChange(type = LineChangeType.ADD,data = " return hashes as PijulOperationResult<PijulLog>"),LineChange(type = LineChangeType.ADD, data = " } else {"),LineChange(type = LineChangeType.ADD,data = " val entries = mutableListOf<PijulLogEntry>()"),LineChange(type = LineChangeType.ADD, data = " for (hash in hashes.result!!) {"),LineChange(type = LineChangeType.ADD,data = " val change = this.doExecutionWithMapper(\"change-\$hash, this.execPijul(project, rootPath, listOf(\"change\", hash), delay = 10L)) {"),LineChange(type = LineChangeType.ADD, data = " it.parseChange(hash)"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " if (change.statusCode !is SuccessStatusCode) {"),LineChange(type = LineChangeType.ADD,data = " return change as PijulOperationResult<PijulLog>"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " return PijulOperationResult(hashes.operation, hashes.statusCode, PijulLog(entries))"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " override fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber> {"),LineChange(type = LineChangeType.ADD,data = " /*val root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file)"),LineChange(type = LineChangeType.ADD,data = " ?: return PijulOperationResult(\"file_status\", SuccessStatusCode, FileStatus.UNKNOWN)*/"),LineChange(type = LineChangeType.ADD,data = " val rootPath = Paths.get(VcsUtil.getFilePath(root).path)"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = " val log = this.log(project, root)"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = " return PijulOperationResult(log.operation, log.statusCode,"),LineChange(type = LineChangeType.ADD, data = " log.result?.entries?.firstOrNull()?.let {"),LineChange(type = LineChangeType.ADD,data = " PijulRevisionNumber(it.changeHash, it.date)"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = " )"),LineChange(type = LineChangeType.ADD, data = " }"),LineChange(type = LineChangeType.ADD, data = "")),meta = null,flags = emptyList(),context = Context(up = listOf(3.13611), new = IntRange(4491, 5952), down = listOf(3.13611)),)change.hunks[12] shouldBe EditHunk(hunkNumber = 13,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt",startLine = 14,inode = 3.24146,lines = listOf(LineChange(type = LineChangeType.ADD,data = "import com.github.jonathanxd.dracon.provider.PijulChangeProvider")),meta = null,flags = emptyList(),context = Context(up = listOf(3.24239), new = IntRange(5953, 6018), down = listOf(3.24239)),)change.hunks[13] shouldBe EditHunk(hunkNumber = 14,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt",startLine = 20,inode = 3.24146,lines = listOf(LineChange(type = LineChangeType.ADD,data = "import com.intellij.openapi.vcs.changes.ChangeProvider")),meta = null,flags = emptyList(),context = Context(up = listOf(3.24475), new = IntRange(6019, 6074), down = listOf(3.24475)),)change.hunks[14] shouldBe EditHunk(hunkNumber = 15,type = HunkType.FILE_EDIT,path = "src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt",startLine = 46,inode = 3.24146,lines = listOf(LineChange(type = LineChangeType.ADD,data = " override fun getChangeProvider(): ChangeProvider ="),LineChange(type = LineChangeType.ADD, data = " PijulChangeProvider(this.project, KEY)"),LineChange(type = LineChangeType.ADD, data = "")),meta = null,flags = emptyList(),context = Context(up = listOf(3.25153), new = IntRange(6075, 6178), down = listOf(3.25153)),)change.hunks[15] shouldBe FileAddHunk(hunkNumber = 16,type = HunkType.FILE_ADDITION,path = "dada.toml",rootPath = "/",lines = emptyList(),meta = Meta(""),flags = emptyList(),context = Context(up = listOf(1.0), new = IntRange(6179, 6190), down = emptyList()),)change.hunks[16] shouldBe EditHunk(hunkNumber = 17,type = HunkType.FILE_EDIT,path = "build.gradle.kts",startLine = 33,inode = 5.13712,lines = listOf(LineChange(type = LineChangeType.ADD, data = " maven(url = \"https://jitpack.io\")")),meta = null,flags = emptyList(),context = Context(up = listOf(5.14803), new = IntRange(6193, 6231), down = listOf(5.14803)),)change.hunks[17] shouldBe EditHunk(hunkNumber = 18,type = HunkType.FILE_EDIT,path = "build.gradle.kts",startLine = 36,inode = 5.13712,lines = listOf(LineChange(type = LineChangeType.ADD,data = " implementation(\"org.tomlj:tomlj:1.0.0\")"),LineChange(type = LineChangeType.ADD,data = " implementation(\"com.github.JonathanxD.JwIUtils:JwIUtils:4.17.0\") {"),LineChange(type = LineChangeType.ADD, data = " exclude(group = \"org.jetbrains\")"),LineChange(type = LineChangeType.ADD, data = " }")),meta = null,flags = emptyList(),context = Context(up = listOf(5.1482), new = IntRange(6232, 6394), down = listOf(5.1482)),)change.hunks[18] shouldBe ReplacementHunk(hunkNumber = 19,type = HunkType.REPLACEMENT,path = "README.md",startLine = 21,inode = 5.17946,lines = listOf(LineChange(type = LineChangeType.DEL, data = "- [ ] ..."),LineChange(type = LineChangeType.ADD, data = "- [ ] ..."),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "# FAQ"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "## Why Dracon and not Pijul for IDEA (or something like that)?"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "Because Dracon is not an official plugin, and I want to make sure that it is clear to anyone who uses this plugin. Dracon is an individual project being developed by [Jonathan H. R. Lopes](https://github.com/JonathanxD) which does not have any relation with Pijul creator. Also, Dracon uses MIT license, allowing to anyone, and that includes the Pijul creator, to contribute and create your own version of Dracon, if wish to."),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "## Why Dracon is developed against IntelliJ EAP instead of a stable releases?"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "Dracon is being developed based in actual [HG4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/hg4idea) and [Git4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/git4idea) plugins, as well based in current code of [IntelliJ IDEA Community](https://github.com/JetBrains/intellij-community/), and is planned to be released by the end of first semester of 2021, when release time comes, the IntelliJ EAP will be already released as stable, also I don't want to support older releases of IntelliJ IDEA because this involves basing on deprecated code, and this is my first IDEA plugin, so the more I focus the present than the past, more I deliver in terms of better and stable features."),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "## Could I contribute to Dracon development?"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "I'm currently focused on learning how IDEA Plugin platform works and how DVCS is implemented in IDEA, so until Dracon is officially released, I will not be accepting contributions, but Dracon is licensed under MIT, you are free to create your own version of Dracon and developing it on your own."),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD, data = "## When Dracon will be official released?"),LineChange(type = LineChangeType.ADD, data = ""),LineChange(type = LineChangeType.ADD,data = "I will be periodically releasing Dragon experimental builds, but Dracon stable release is planned by the end of first semester of 2021.")),meta = null,flags = emptyList(),context = Context(up = listOf(2.59651), new = IntRange(6395, 8239), down = emptyList()),)}})
class Author(val name: String?, val fullName: String?, val email: String?)class PijulLog(val entries: List<PijulLogEntry>)
/*** Only appears in dependencies of [extra-known type][DependencyType.EXTRA_KNOWN_DEPENDENCY].*/object Asterisk : IntOrAsterisk() {override fun toString(): String = "*"}/*** Appears in dependencies of [regular type][DependencyType.REGULAR_DEPENDENCY] and [change type][DependencyType.CHANGE_DEPENDENCY].*/data class IndexInt(val index: Int) : IntOrAsterisk()enum class DependencyType {/*** Regular dependency.*/REGULAR_DEPENDENCY,/*** Change dependency.*/CHANGE_DEPENDENCY,/*** Extra known "context" changes.*/EXTRA_KNOWN_DEPENDENCY,/*** Unknown dependency type.*/UNKNOWN}// Hunksealed class Hunk {abstract val hunkNumber: Intabstract val type: HunkTypeabstract val context: Context?abstract val meta: Meta?abstract val flags: List<Flag>}data class FileAddHunk(override val hunkNumber: Int,override val type: HunkType = HunkType.FILE_ADDITION,val path: String,val rootPath: String,val lines: List<LineChange>,override val meta: Meta?,override val flags: List<Flag> = emptyList(),override val context: Context?): Hunk()data class FileDelHunk(override val hunkNumber: Int,override val type: HunkType = HunkType.FILE_DELETION,val path: String,val inode: Double?,val lines: List<LineChange>,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()data class FileUnDelHunk(override val hunkNumber: Int,override val type: HunkType = HunkType.FILE_UN_DELETION,val path: String,val inode: Double?,val lines: List<LineChange>,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()data class MoveHunk(override val hunkNumber: Int,override val type: HunkType = HunkType.FILE_MOVE,val older: String,val new: String,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()
class PijulLogEntry(val changeHash: String,val message: String,val date: ZonedDateTime,val authors: List<Author>
data class EditHunk(override val hunkNumber: Int,override val type: HunkType = HunkType.FILE_EDIT,val path: String,val startLine: Int,val inode: Double?,val lines: List<LineChange>,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()data class ReplacementHunk(override val hunkNumber: Int,override val type: HunkType = HunkType.REPLACEMENT,val path: String,val startLine: Int,val inode: Double?,val lines: List<LineChange>,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()data class NameConflictHunk(override val hunkNumber: Int,override val type: HunkType,val path: String,val inode: Double?,val deletedNames: List<String>,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()data class OrderConflictHunk(override val hunkNumber: Int,override val type: HunkType,val path: String,val index: Int?, // ??val inode: Double?,val lines: List<LineChange>,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()data class ZombieResurrectHunk(override val hunkNumber: Int,override val type: HunkType = HunkType.ZOMBIE_RESURRECT,val path: String,val line: Int,val inode: Double?,val lines: List<LineChange>,override val meta: Meta?,override val flags: List<Flag>,override val context: Context?): Hunk()/*** Meta is ignored as it is not needed for this plugin work.** However, we still read metadata and store them, as they are useful to keep changelog reading consistent.*/data class Meta(val plain: String)/*** Flag is ignored as it is not needed for this plugin work.** However, we still read flags and store them, as they are useful to keep changelog reading consistent.*/data class Flag(val plain: String)data class Context(val up: List<Double>,val new: IntRange?,val down: List<Double>
enum class HunkType {FILE_ADDITION,FILE_DELETION,FILE_UN_DELETION,FILE_MOVE,FILE_EDIT,REPLACEMENT,SOLVING_NAME_CONFLICT,UN_SOLVING_NAME_CONFLICT,UNKNOWN_NAME_CONFLICT_RESOLUTION,SOLVING_ORDER_CONFLICT,UN_SOLVING_ORDER_CONFLICT,UNKNOWN_ORDER_CONFLICT_RESOLUTION,ZOMBIE_RESURRECT}data class LineChange(val type: LineChangeType, val data: String)enum class LineChangeType(val symbol: String) {ADD("+"),DEL("-");override fun toString(): String ="${this.name}(${this.symbol})"companion object {fun fromSymbol(symbol: String): LineChangeType =when (symbol) {"+" -> ADD"-" -> DELelse -> throw IllegalArgumentException("Unknown change symbol: $symbol. Known symbols: +, -. For: ADD, DEL.")}}}
val AUTHORS_SECTION_PATTERN = Regex("\\[\\[authors\\]\\]\n")val AUTHOR_PATTERN = Regex("name = '(.*)'\\n")
val AUTHORS_SECTION_PATTERN = Regex("\\[\\[authors\\]\\]\\n")val AUTHOR_PATTERN = Regex("name = '(.*)'\\n?")val DEPENDENCIES_SECTION_PATTERN = Regex("# Dependencies\\n")val DEPENDENCY_PATTERN = Regex("\\[(\\d+|\\*)\\]([+ ])([A-Z0-9]+)\\n?")val HUNK_SECTION_PATTERN = Regex("# Hunks\\n")val HUNK_ADD_PATTERN = Regex("(\\d+)\\. File addition: \"([^\"]*)\" in \"([^\"]*)\"( [^ ]+)?\\n?")val HUNK_DEL_PATTERN = Regex("(\\d+)\\. File deletion: \"([^\"]*)\" (\\d+\\.\\d+)\\n?")val HUNK_UNDEL_PATTERN = Regex("(\\d+)\\. File un-deletion: \"([^\"]*)\" (\\d+\\.\\d+)\\n?")val HUNK_MOVE_PATTERN = Regex("(\\d+)\\. Moved: \"([^\"]*)\" \"([^\"]*)\" ([^ ]+ )?(.*)\\n?")val HUNK_EDIT_PATTERN = Regex("(\\d+)\\. Edit in ([^:]+):(\\d+) (\\d+\\.\\d+)\\n?")val HUNK_REPLACEMENT_PATTERN = Regex("(\\d+)\\. Replacement in ([^:]+):(\\d+) (\\d+\\.\\d+)\\n?")val HUNK_NAME_CONFLICT_PATTERN = Regex("(\\d+)\\. (Solving|Un-solving) a name conflict in \"([^\"]*)\" (.*): (.*)\\n?")val HUNK_ORDER_CONFLICT_PATTERN = Regex("(\\d+)\\. (Solving|Un-solving) an order conflict in (.*):(\\d+)? (\\d+\\.\\d+)\\n?")val HUNK_ZOMBIE_PATTERN = Regex("(\\d+)\\. Resurrecting zombie lines in (\"[^\"]+\"):(\\d+) (\\d+\\.\\d+)\\n?")val HUNK_CONTEXT = Regex("up ((\\d+\\.\\d+ )*\\d+\\.\\d+)(, new \\d+:\\d+)?(, down ((\\d+\\.\\d+ )*\\d+\\.\\d+))?\\n?")val CHANGE_PATTERN = Regex("([-+]) (.*)\\n?")/*** Flags are currently ignored by Dracon, as they are not required for the plugin work correctly.*/val FLAG_PATTERN = Regex("([BFD]+:[BFD]+ [^\\n]+)\\n?")
}} else {lastFound = null}}}if (dependencySection != null) {var lastFound: Int? = dependencySectionwhile (lastFound != null) {val foundDependency = DEPENDENCY_PATTERN.find(this, lastFound)if (foundDependency != null) {val foundDependencyId = foundDependency.groupValues[1]val foundDependencySeparator = foundDependency.groupValues[2]val foundDependencyHash = foundDependency.groupValues[3]val dependencyType = when {foundDependencyId == "*" && foundDependencySeparator == " " -> DependencyType.EXTRA_KNOWN_DEPENDENCYfoundDependencyId.toIntOrNull() != null && foundDependencySeparator == "+" -> DependencyType.CHANGE_DEPENDENCYfoundDependencyId.toIntOrNull() != null && foundDependencySeparator == " " -> DependencyType.REGULAR_DEPENDENCYelse -> DependencyType.UNKNOWN}val intOrAsterisk = foundDependencyId.toIntOrNull()?.let(::IndexInt) ?: Asteriskdependencies += Dependency(intOrAsterisk,dependencyType,foundDependencyHash)lastFound = if (foundDependency.range.last.endsInNewLine(this)) {null} else {foundDependency.range.last
if (hunkSection != null) {var lastFound: Int? = hunkSectionwhile (lastFound != null) {if (this[lastFound] == '\n') {lastFound += 1continue;}var anyFound = falseval add = HUNK_ADD_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (add != null) {anyFound = truelastFound += add.range.lastval hunkNumber = add.groupValues[1].toInt()val fileOrPath = add.groupValues[2]val rootPath = add.groupValues[3]val meta = add.groupValues.getOrNull(4)?.trim()?.let(::Meta)val (context, stoppedLine) = this.tryReadContext(lastFound)lastFound = stoppedLineval (lines, lastKnownLine) = this.readLineChanges(lastFound)lastFound = lastKnownLinehunks += FileAddHunk(hunkNumber = hunkNumber,path = fileOrPath,rootPath = rootPath,lines = lines,meta = meta,context = context)}val remove = HUNK_DEL_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (remove != null) {anyFound = truelastFound += remove.range.lastval hunkNumber = remove.groupValues[1].toInt()val path = remove.groupValues[2]val inode = remove.groupValues[3].toDoubleOrNull()val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (lines, linesLastKnownLine) = this.readLineChanges(lastFound)lastFound = linesLastKnownLinehunks += FileDelHunk(hunkNumber = hunkNumber,path = path,inode = inode,flags = flags,lines = lines,meta = null,context = null)}val undel = HUNK_UNDEL_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (undel != null) {anyFound = truelastFound += undel.range.lastval hunkNumber = undel.groupValues[1].toInt()val path = undel.groupValues[2]val inode = undel.groupValues[3].toDoubleOrNull()val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (lines, linesLastKnownLine) = this.readLineChanges(lastFound)lastFound = linesLastKnownLinehunks += FileUnDelHunk(hunkNumber = hunkNumber,path = path,inode = inode,flags = flags,lines = lines,meta = null,context = null)}val move = HUNK_MOVE_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (move != null) {anyFound = truelastFound += move.range.lastval hunkNumber = move.groupValues[1].toInt()val new = move.groupValues[2]val old = move.groupValues[3]val meta = move.groupValues.getOrNull(4)?.trim()?.let(::Meta)val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (context, stoppedLine) = this.tryReadContext(lastFound)lastFound = stoppedLinehunks += MoveHunk(hunkNumber = hunkNumber,older = old,new = new,flags = flags,meta = meta,context = context)}
return PijulLogEntry(hash, message, timestamp.parseAsLocalDateTime(), authors)
val edit = HUNK_EDIT_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (edit != null) {anyFound = truelastFound += edit.range.lastval hunkNumber = edit.groupValues[1].toInt()val path = edit.groupValues[2]val line = edit.groupValues[3].toInt()val inode = edit.groupValues.getOrNull(4)?.toDoubleOrNull()val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (context, stoppedLine) = this.tryReadContext(lastFound)lastFound = stoppedLineval (lines, linesLastKnownLine) = this.readLineChanges(lastFound)lastFound = linesLastKnownLinehunks += EditHunk(hunkNumber = hunkNumber,path = path,startLine = line,inode = inode,flags = flags,meta = null,lines = lines,context = context)}val replacement = HUNK_REPLACEMENT_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (replacement != null) {anyFound = truelastFound += replacement.range.lastval hunkNumber = replacement.groupValues[1].toInt()val path = replacement.groupValues[2]val line = replacement.groupValues[3].toInt()val inode = replacement.groupValues.getOrNull(4)?.toDoubleOrNull()val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (context, stoppedLine) = this.tryReadContext(lastFound)lastFound = stoppedLineval (lines, linesLastKnownLine) = this.readLineChanges(lastFound)lastFound = linesLastKnownLinehunks += ReplacementHunk(hunkNumber = hunkNumber,path = path,startLine = line,inode = inode,flags = flags,meta = null,lines = lines,context = context)}val nameConflict = HUNK_NAME_CONFLICT_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (nameConflict != null) {anyFound = truelastFound += nameConflict.range.lastval hunkNumber = nameConflict.groupValues[1].toInt()val typeStr = nameConflict.groupValues[2]val path = nameConflict.groupValues[3]val inode = nameConflict.groupValues[4].toDouble()val deletedNames = nameConflict.groupValues[5].split(", ")val type = when {typeStr.equals("solving", ignoreCase = true) -> HunkType.SOLVING_NAME_CONFLICTtypeStr.equals("un-solving", ignoreCase = true) -> HunkType.UN_SOLVING_NAME_CONFLICTelse -> HunkType.UNKNOWN_NAME_CONFLICT_RESOLUTION}val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (context, stoppedLine) = this.tryReadContext(lastFound)lastFound = stoppedLinehunks += NameConflictHunk(hunkNumber = hunkNumber,path = path,type = type,inode = inode,deletedNames = deletedNames,flags = flags,meta = null,context = context)}val orderConflict = HUNK_ORDER_CONFLICT_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (orderConflict != null) {anyFound = truelastFound += orderConflict.range.lastval hunkNumber = orderConflict.groupValues[1].toInt()val typeStr = orderConflict.groupValues[2]val path = orderConflict.groupValues[3]val pos = orderConflict.groupValues.getOrNull(4)?.toIntOrNull()val inode = orderConflict.groupValues[5].toDouble()val type = when {typeStr.equals("solving", ignoreCase = true) -> HunkType.SOLVING_ORDER_CONFLICTtypeStr.equals("un-solving", ignoreCase = true) -> HunkType.UN_SOLVING_ORDER_CONFLICTelse -> HunkType.UNKNOWN_ORDER_CONFLICT_RESOLUTION}val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (context, stoppedLine) = this.tryReadContext(lastFound)lastFound = stoppedLineval (lines, linesLastKnownLine) = this.readLineChanges(lastFound)lastFound = linesLastKnownLinehunks += OrderConflictHunk(hunkNumber = hunkNumber,path = path,index = pos,type = type,inode = inode,lines = lines,flags = flags,meta = null,context = context)}val zombieLines = HUNK_ZOMBIE_PATTERN.matchEntire(this.substringUntil(lastFound, '\n'))if (zombieLines != null) {anyFound = truelastFound += zombieLines.range.lastval hunkNumber = zombieLines.groupValues[1].toInt()val path = zombieLines.groupValues[2]val line = zombieLines.groupValues[3].toInt()val inode = zombieLines.groupValues[4].toDouble()val (flags, flagsLastKnownLine) = this.readFlags(lastFound)lastFound = flagsLastKnownLineval (context, stoppedLine) = this.tryReadContext(lastFound)lastFound = stoppedLineval (lines, linesLastKnownLine) = this.readLineChanges(lastFound)lastFound = linesLastKnownLinehunks += ZombieResurrectHunk(hunkNumber = hunkNumber,path = path,line = line,inode = inode,lines = lines,flags = flags,meta = null,context = context)}if (!anyFound) {lastFound = null}}}return PijulLogEntry(hash, message, timestamp.parseAsLocalDateTime(), authors, dependencies, hunks)}fun String.substringUntil(start: Int, char: Char, includeChar: Boolean = true) =this.indexOf(char, start + 1).let {if (it == -1) {this.substring(start)} else {this.substring(start, if (includeChar) it + 1 else it)}}fun String.tryReadContext(start: Int): Pair<Context?, Int> {val end = this.indexOf('\n', start + 1)val text = this.substring(start, end).trim()val foundContext = HUNK_CONTEXT.find(text)if (foundContext != null) {val up = foundContext.groupValues[1].split(" ").map { it.toDouble() }val newText = foundContext.groupValues.getOrNull(3)val downText = foundContext.groupValues.getOrNull(4)val new = if (newText != null && newText.startsWith(", new ")) {val range = newText.substring(", new ".length)val rangeDivisor = range.indexOf(':')val firstPart = range.substring(0 until rangeDivisor).toInt()val secondPart = range.substring(rangeDivisor + 1).toInt()IntRange(firstPart, secondPart)} else {null}val down = if (downText != null && downText.startsWith(", down ")) {downText.substring(", down ".length).split(" ").map { it.toDouble() }} else {emptyList()}return Context(up, new, down) to end} else {return null to start}}fun String.readFlags(start: Int): Pair<List<Flag>, Int> {val flags = mutableListOf<Flag>()var lastKnownLine: Int = startvar lastLine: Int? = startwhile (lastLine != null) {if (this[lastLine] == '\n') {lastLine += 1lastKnownLine = lastLinecontinue;}val foundFlag = FLAG_PATTERN.matchEntire(this.substringUntil(lastLine, '\n'))if (foundFlag != null) {lastKnownLine = lastLine + foundFlag.range.lastval flag = foundFlag.groupValues[1]flags + Flag(flag)lastLine = if (lastKnownLine.endsInNewLine(this)) {null} else {lastKnownLine}} else {lastLine = null}}return flags to lastKnownLine}fun String.readLineChanges(start: Int): Pair<List<LineChange>, Int> {val changes = mutableListOf<LineChange>()var lastKnownLine: Int = startvar lastLine: Int? = startwhile (lastLine != null) {if (this[lastLine] == '\n') {lastLine += 1;lastKnownLine = lastLinecontinue;}val foundChange = CHANGE_PATTERN.matchEntire(this.substringUntil(lastLine, '\n'))if (foundChange != null) {lastKnownLine = lastLine + foundChange.range.lastval symbol = foundChange.groupValues[1]val data = foundChange.groupValues[2]changes += LineChange(LineChangeType.fromSymbol(symbol), data)lastLine = if (lastKnownLine.endsInNewLine(this)) {null} else {lastKnownLine}} else {// Single \ is used sometimes to represent an end of a change.if (lastKnownLine < this.length && this[lastKnownLine] == '\\' && lastKnownLine + 1 < this.length) {lastKnownLine += 1}lastLine = null}}return changes to lastKnownLine
}
}fun FileDelHunk.fileDelHunkAsSource() = "FileDelHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"inode = ${this.inode?.toString() ?: "null"},\n" +"lines = ${lines.asSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun FileAddHunk.fileAddHunkAsSource() = "FileAddHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"rootPath = \"$rootPath\",\n" +"lines = ${lines.asSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun FileUnDelHunk.fileUnDelHunkAsSource() = "FileUnDelHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"inode = ${this.inode?.toString() ?: "null"},\n" +"lines = ${lines.asSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun MoveHunk.moveHunkAsSource() = "MoveHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"older = \"$older\",\n" +"new = \"$new\",\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun EditHunk.editHunkAsSource() = "EditHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"startLine = $startLine,\n" +"inode = ${inode?.toString() ?: "null"},\n" +"lines = ${lines.asSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun ReplacementHunk.replacementHunkAsSource() = "ReplacementHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"startLine = $startLine,\n" +"inode = ${inode?.toString() ?: "null"},\n" +"lines = ${lines.asSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun NameConflictHunk.nameConflictHunkAsSource() = "NameConflictHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"inode = ${inode?.toString() ?: "null"},\n" +"deletedNames = ${deletedNames.stringListAsSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun OrderConflictHunk.orderConflictHunkAsSource() = "OrderConflictHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"index = ${index?.toString() ?: "null"},\n" +"inode = ${inode?.toString() ?: "null"},\n" +"lines = ${lines.asSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun ZombieResurrectHunk.zombieResurrectHunkAsSource() = "ZombieResurrectHunk(\n" +"hunkNumber = $hunkNumber,\n" +"type = ${this.type.hunkTypeAsSource()},\n" +"path = \"$path\",\n" +"inode = ${inode?.toString() ?: "null"},\n" +"lines = ${lines.asSource()},\n" +"meta = ${meta?.metaAsSource() ?: "null"},\n" +"flags = ${flags.flagsAsSource()},\n" +"context = ${context?.contextAsSource() ?: "null"},\n" +")"fun HunkType.hunkTypeAsSource() = "HunkType.${this.name}"fun List<LineChange>.asSource() =if(this.isEmpty()) "emptyList()"else "listOf(${this.joinToString(separator = ", ") { it.lineChangeAsSource() }})"fun List<Flag>.flagsAsSource() =if(this.isEmpty()) "emptyList()"else "listOf(${this.joinToString(separator = ", ") { it.flagsAsSource() }})"fun Meta.metaAsSource() = "Meta(\"${StringEscapeUtils.escapeJava(plain)}\")"fun Flag.flagsAsSource() = "Flag(\"${StringEscapeUtils.escapeJava(plain)}\")"fun List<String>.stringListAsSource() =if (this.isEmpty()) "emptyList()"else "listOf(\"${this.joinToString(separator = ", ") { StringEscapeUtils.escapeJava(it) }}\")"fun Context.contextAsSource() = "Context(" +"up = ${this.up.doublesAsSource()}, " +"new = ${this.new?.intRangeAsSource() ?: "null"}, " +"down = ${this.down.doublesAsSource()}" +")"fun LineChange.lineChangeAsSource() = "LineChange(" +"type = LineChangeType.${type.name}, " +"data = \"${StringEscapeUtils.escapeJava(data)}\"" +")"
message = 'Initial CahngeProvider'timestamp = '2021-03-29T01:50:38.537749028Z'[[authors]]name = 'Jonathan H. R. Lopes'# Dependencies[2] PGNTR2EPCZBOWI67LKY6AN5B3RGIEOQ6NTTXGODLESKDSPWV26KQC[3]+FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC[4]+OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC[5]+GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC[*] NTRPUMVQHUIQZ6O72NJ72XFYTZWZOSDA6CSKMUCGKFVNE3KIDYYQC# Hunks1. File addition: "revision" in "src/main/kotlin/com/github/jonathanxd/dracon" +dxup 3.107, new 0:102. File addition: "PijulRevisionNumber.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/revision"up 0.11, new 12:36+ package com.github.jonathanxd.dracon.revision++ import com.intellij.openapi.vcs.changes.patch.BlobIndexUtil+ import com.intellij.openapi.vcs.history.ShortVcsRevisionNumber+ import com.intellij.openapi.vcs.history.VcsRevisionNumber+ import java.time.LocalDateTime+ import java.time.ZonedDateTime++ class PijulRevisionNumber(val hash: String, val timestamp: ZonedDateTime) : ShortVcsRevisionNumber {++ val NOT_COMMITTED_HASH = BlobIndexUtil.NOT_COMMITTED_HASH++ override fun compareTo(other: VcsRevisionNumber): Int {+ if (this === other) return 0++ if (other is PijulRevisionNumber) {+ return this.timestamp.compareTo(other.timestamp)+ }++ return -1+ }++ override fun asString(): String = this.hash++ override fun toShortString(): String = this.asString()+ }\3. File addition: "provider" in "src/main/kotlin/com/github/jonathanxd/dracon" +dxup 3.107, new 843:8534. File addition: "PijulChangeProvider.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/provider"up 0.854, new 855:879+ package com.github.jonathanxd.dracon.provider++ import com.intellij.openapi.progress.ProgressIndicator+ import com.intellij.openapi.project.Project+ import com.intellij.openapi.vcs.VcsKey+ import com.intellij.openapi.vcs.changes.*++ class PijulChangeProvider(val project: Project, val key: VcsKey) : ChangeProvider {+ override fun getChanges(+ dirtyScope: VcsDirtyScope,+ builder: ChangelistBuilder,+ progress: ProgressIndicator,+ addGate: ChangeListManagerGate+ ) {+ if (project.isDisposed) return+ val dirtDirs = dirtyScope.recursivelyDirtyDirectories++ println(dirtDirs)+ }++ override fun isModifiedDocumentTrackingRequired(): Boolean = true+ }\5. Edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt:13 3.4428up 3.4473, new 1584:1698, down 3.4473+ import com.github.jonathanxd.dracon.log.PijulLog+ import com.github.jonathanxd.dracon.revision.PijulRevisionNumber6. Edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt:82 3.4428up 3.6839, new 1699:1950, down 3.6839+ @RequiresBackgroundThread+ fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber>++ @RequiresBackgroundThread+ fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog>7. File addition: "PijulLog.kt" in "src/main/kotlin/com/github/jonathanxd/dracon/log"up 3.6847, new 1951:1964+ package com.github.jonathanxd.dracon.log++ import java.time.LocalDateTime+ import java.time.ZonedDateTime+ import java.time.format.DateTimeFormatter+ import java.time.format.ResolverStyle++++++++ class Author(val name: String?, val fullName: String?, val email: String?)+ class PijulLog(val entries: List<PijulLogEntry>)++ class PijulLogEntry(+ val changeHash: String,+ val message: String,+ val date: ZonedDateTime,+ val authors: List<Author>+ )+++ val MESSAGE_PATTERN = Regex("message = '(.*)'\\n")+ val TIME_PATTERN = Regex("timestamp = '(.*)'\\n")+ val AUTHORS_SECTION_PATTERN = Regex("\\[\\[authors\\]\\]\n")+ val AUTHOR_PATTERN = Regex("name = '(.*)'\\n")++ fun String.parseChange(hash: String): PijulLogEntry {+ val message = MESSAGE_PATTERN.find(this)?.groupValues?.get(1)!!+ val timestamp = TIME_PATTERN.find(this)?.groupValues?.get(1)!!+ val authorsSection = AUTHORS_SECTION_PATTERN.find(this)?.range?.endInclusive+ val authors = mutableListOf<Author>()++ if (authorsSection != null) {++ var lastFound: Int? = authorsSection+ while (lastFound != null) {+ val foundAuthor = AUTHOR_PATTERN.find(this, lastFound)+ val foundAuthorGroup = foundAuthor?.groupValues?.get(1)++ if (foundAuthorGroup != null) {+ authors += Author(foundAuthorGroup, null, null)+ lastFound = if (foundAuthor.range.last + 1 < this.length && this[foundAuthor.range.last + 1] == '\n') {+ null+ } else {+ foundAuthor.range.last+ }+ } else {+ lastFound = null+ }+ }+ }++ return PijulLogEntry(hash, message, timestamp.parseAsLocalDateTime(), authors)+ }++ val RFC3339_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS]XXX")+ .withResolverStyle(ResolverStyle.LENIENT)++ fun String.parseAsLocalDateTime(): ZonedDateTime {+ return ZonedDateTime.parse(this, RFC3339_FORMATTER)+ }\8. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:13 3.10972up 3.11015, new 4000:4103, down 3.11015+ import com.github.jonathanxd.dracon.log.PijulLog+ import com.github.jonathanxd.dracon.log.PijulLogEntry9. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:16 3.10972up 3.11075, new 4104:4156, down 3.11075+ import com.github.jonathanxd.dracon.log.parseChange10. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:20 3.10972up 4.2779, new 4157:4222, down 3.11119+ import com.github.jonathanxd.dracon.revision.PijulRevisionNumber11. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:106 3.10972up 3.13610, new 4223:4490, down 3.13610++ override fun log(project: Project, root: VirtualFile): PijulOperationResult<PijulLog> {+ val rootPath = Paths.get(VcsUtil.getFilePath(root).path)++ val logHashExecution = this.execPijul(project, rootPath, listOf("log", "--hash-only"), delay = 10L)12. Edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt:112 3.10972up 3.13611, new 4491:5952, down 3.13611+ 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!!) {+ val change = this.doExecutionWithMapper("change-$hash", this.execPijul(project, rootPath, listOf("change", hash), delay = 10L)) {+ it.parseChange(hash)+ }++ if (change.statusCode !is SuccessStatusCode) {+ return change as PijulOperationResult<PijulLog>+ }+ }++ return PijulOperationResult(hashes.operation, hashes.statusCode, PijulLog(entries))+ }+ }++ override fun latestRevisionNumber(project: Project, root: VirtualFile): PijulOperationResult<PijulRevisionNumber> {+ /*val root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file)+ ?: return PijulOperationResult("file_status", SuccessStatusCode, FileStatus.UNKNOWN)*/+ val rootPath = Paths.get(VcsUtil.getFilePath(root).path)++ val log = this.log(project, root)++ return PijulOperationResult(log.operation, log.statusCode,+ log.result?.entries?.firstOrNull()?.let {+ PijulRevisionNumber(it.changeHash, it.date)+ }+ )+ }+13. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:14 3.24146up 3.24239, new 5953:6018, down 3.24239+ import com.github.jonathanxd.dracon.provider.PijulChangeProvider14. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:20 3.24146up 3.24475, new 6019:6074, down 3.24475+ import com.intellij.openapi.vcs.changes.ChangeProvider15. Edit in src/main/kotlin/com/github/jonathanxd/dracon/PijulVcs.kt:46 3.24146up 3.25153, new 6075:6178, down 3.25153+ override fun getChangeProvider(): ChangeProvider =+ PijulChangeProvider(this.project, KEY)+16. File addition: "dada.toml" in "/"up 1.0, new 6179:619017. Edit in build.gradle.kts:33 5.13712up 5.14803, new 6193:6231, down 5.14803+ maven(url = "https://jitpack.io")18. Edit in build.gradle.kts:36 5.13712up 5.14820, new 6232:6394, down 5.14820+ implementation("org.tomlj:tomlj:1.0.0")+ implementation("com.github.JonathanxD.JwIUtils:JwIUtils:4.17.0") {+ exclude(group = "org.jetbrains")+ }19. Replacement in README.md:21 5.17946B:BD 2.59651 -> 2.59651:59660/2up 2.59651, new 6395:8239- - [ ] ...+ - [ ] ...+++ # FAQ++ ## Why Dracon and not Pijul for IDEA (or something like that)?++ Because Dracon is not an official plugin, and I want to make sure that it is clear to anyone who uses this plugin. Dracon is an individual project being developed by [Jonathan H. R. Lopes](https://github.com/JonathanxD) which does not have any relation with Pijul creator. Also, Dracon uses MIT license, allowing to anyone, and that includes the Pijul creator, to contribute and create your own version of Dracon, if wish to.++ ## Why Dracon is developed against IntelliJ EAP instead of a stable releases?++ Dracon is being developed based in actual [HG4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/hg4idea) and [Git4Idea](https://github.com/JetBrains/intellij-community/tree/master/plugins/git4idea) plugins, as well based in current code of [IntelliJ IDEA Community](https://github.com/JetBrains/intellij-community/), and is planned to be released by the end of first semester of 2021, when release time comes, the IntelliJ EAP will be already released as stable, also I don't want to support older releases of IntelliJ IDEA because this involves basing on deprecated code, and this is my first IDEA plugin, so the more I focus the present than the past, more I deliver in terms of better and stable features.++ ## Could I contribute to Dracon development?++ I'm currently focused on learning how IDEA Plugin platform works and how DVCS is implemented in IDEA, so until Dracon is officially released, I will not be accepting contributions, but Dracon is licensed under MIT, you are free to create your own version of Dracon and developing it on your own.++ ## When Dracon will be official released?++ I will be periodically releasing Dragon experimental builds, but Dracon stable release is planned by the end of first semester of 2021.\
testImplementation(platform("org.junit:junit-bom:5.7.1"))testImplementation("org.junit.jupiter:junit-jupiter")testImplementation("io.kotest:kotest-runner-junit5:4.4.3")testImplementation("io.kotest:kotest-assertions-core:4.4.3")testImplementation("io.kotest:kotest-property:4.4.3")}tasks.withType<Test> {useJUnitPlatform()