OMZXJL6QA6INENIEAARSWYFHOPMLTP4WRCVI646GQVJVWCH3LENQC
A7IL6I3VCB3F3QMYQFPXBL33DN5XDGGQPZIBZRHYSQBSIUBBFQJQC
7L5LODGZ7AN4ZULDJZMLALD7PL6E57VZSNNSG67SFJARUJGCT47QC
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC
GGYFPXND4VBCROZZXTKAP7Y4JOP2OOYQAFVLMUE7SLFM225EUSIAC
MTPTFTHGAOKQGRUDXC55AM6XJHZZZ5EF6FPVXKFVCUYVXJNEANYQC
37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC
FRFFQV7VNYKGCA7ZAOSRPC2HHYTAIZ6AGGR7A5QEV6QPAQGFDYGAC
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
Q7FXTHVUPVAFMNY277C3NFJO3VXLZU5G6C6UYSD5QPURHSG3A7OQC
6CR2EFUN7JXFHCBTNX3WWOOP4WFOCFO6KSPEBN6V6J5HFZO2LHNQC
5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC
QXUEMZ3B2FUHFUC7ZZHJMH5FVWLEMEYXUMFA6JNXTJKIVZNMRIOAC
ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
package com.github.jonathanxd.dracon.test
import com.github.jonathanxd.dracon.push.parsePijulPushChanges
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
class DraconPushChangesTest : ShouldSpec({
should("correctly parse a 'pijul push' text with example credit") {
val text = """
# Please select the changes to push. The lines that contain just a
# valid hash, and no other character (except possibly a newline), will
# be pushed.
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
Dependencies: Q7FXTHVUPVAFMNY277C3NFJO3VXLZU5G6C6UYSD5QPURHSG3A7OQC EAGIDXOLFTHZMZ77ZWAM7MVVUBBXJMZD7RZUNBHJPYRFGGKILGVAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-03-31 22:43:40.062881279 UTC
Improved support for revisions
5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
Dependencies: ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-01 00:07:39.373781350 UTC
Add support to view files affected by a revision
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
Dependencies: 5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-01 17:04:54.758320581 UTC
- Add Show History to Pijul menu
- Always ignore .idea and .pijul in tracking.
- Make findPijul a generic function to allow to find editor-server.
- Only show one revision for directories.
- Add `Hunk::resolvePath(Path)` to resolve the affected file to a Java NIO Path.
- Fix StringOutOfBounds in Change Parsering Algorithm
- Use editor-server instead of copie for interfacing with `pijul record` file.
- Fix FileStatus provider not returning correctly for untracked files.
- Add CommittedChangesProvider for Pijul.
Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
Dependencies: B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-02 21:31:47.581509792 UTC
Remove use of coroutines, which was blocking IntelliJ UI in larger repositories
Improvements for bigger repositories, now Dracon caches the changes that happened in a revision in a file, so everytime Dracon needs to query the changes of a revision, it loads directly from memory instead of doing a full-scan in Pijul repository. For tiny projects it is not a problem, but for medium ones it takes more than five minutes to scan the entire repository (and it was tested with a repo of only 700 records, however there was changes that had more than 60.000 lines).
The cache file is saved in IntelliJ Data Path (project specific) and is compressed with gzip, so it will not use so much disk space (the cost worths the gains).
ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
Dependencies: Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-03 03:18:36.900275901 UTC
More caches, better and generic cache code.
Now Dracon listen to file changes to drop cached data.
Implemented caches:
- File contents in specific revision (never dropped)
- Pijul ls and Pijul diff results
- File Revision and File changes by patch
- some others..
Dracon is incredible fast now, but still will take some time for bigger repos.
2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC
Dependencies: ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-04 03:15:58.713132084 UTC
Add auto installation support and cache content of ContentRevision
37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
Dependencies: A7JOR7M3BUKXYMMAR43IK4GXFXGI33HM6H6LZHDES3HWW6GYFAYAC G54TB4QZZ6OYC2KZTB7KUVW3NJ6VASH7WPNFU2IATPZWM3POIRNQC 2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-04 12:07:50.967878750 UTC
Improved caching code a lot
A7IL6I3VCB3F3QMYQFPXBL33DN5XDGGQPZIBZRHYSQBSIUBBFQJQC
Dependencies: 37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-04 18:38:01.065118605 UTC
More files to support .ignore
""".trimIndent()
val push = text.parsePijulPushChanges()
push.toString() shouldBe "PijulPushChanges(entries=[PijulPushEntry(hash=ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC, dependencies=[Q7FXTHVUPVAFMNY277C3NFJO3VXLZU5G6C6UYSD5QPURHSG3A7OQC, EAGIDXOLFTHZMZ77ZWAM7MVVUBBXJMZD7RZUNBHJPYRFGGKILGVAC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-03-31T22:43:40.062881279Z[Etc/UTC], message= Improved support for revisions\n" +
"), PijulPushEntry(hash=5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC, dependencies=[ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-04-01T00:07:39.373781350Z[Etc/UTC], message= Add support to view files affected by a revision\n" +
"), PijulPushEntry(hash=B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC, dependencies=[5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-04-01T17:04:54.758320581Z[Etc/UTC], message= - Add Show History to Pijul menu\n" +
" - Always ignore .idea and .pijul in tracking.\n" +
" - Make findPijul a generic function to allow to find editor-server.\n" +
" - Only show one revision for directories.\n" +
" - Add `Hunk::resolvePath(Path)` to resolve the affected file to a Java NIO Path.\n" +
" - Fix StringOutOfBounds in Change Parsering Algorithm\n" +
" - Use editor-server instead of copie for interfacing with `pijul record` file.\n" +
" - Fix FileStatus provider not returning correctly for untracked files.\n" +
" - Add CommittedChangesProvider for Pijul.\n" +
"), PijulPushEntry(hash=Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC, dependencies=[B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-04-02T21:31:47.581509792Z[Etc/UTC], message= Remove use of coroutines, which was blocking IntelliJ UI in larger repositories\n" +
"\n" +
" Improvements for bigger repositories, now Dracon caches the changes that happened in a revision in a file, so everytime Dracon needs to query the changes of a revision, it loads directly from memory instead of doing a full-scan in Pijul repository. For tiny projects it is not a problem, but for medium ones it takes more than five minutes to scan the entire repository (and it was tested with a repo of only 700 records, however there was changes that had more than 60.000 lines).\n" +
"\n" +
" The cache file is saved in IntelliJ Data Path (project specific) and is compressed with gzip, so it will not use so much disk space (the cost worths the gains).\n" +
"\n" +
"), PijulPushEntry(hash=ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC, dependencies=[Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-04-03T03:18:36.900275901Z[Etc/UTC], message= More caches, better and generic cache code.\n" +
"\n" +
"\n" +
" Now Dracon listen to file changes to drop cached data.\n" +
"\n" +
" Implemented caches:\n" +
"\n" +
" - File contents in specific revision (never dropped)\n" +
" - Pijul ls and Pijul diff results\n" +
" - File Revision and File changes by patch\n" +
" - some others..\n" +
"\n" +
"\n" +
" Dracon is incredible fast now, but still will take some time for bigger repos.\n" +
"), PijulPushEntry(hash=2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC, dependencies=[ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-04-04T03:15:58.713132084Z[Etc/UTC], message= Add auto installation support and cache content of ContentRevision\n" +
"), PijulPushEntry(hash=37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC, dependencies=[A7JOR7M3BUKXYMMAR43IK4GXFXGI33HM6H6LZHDES3HWW6GYFAYAC, G54TB4QZZ6OYC2KZTB7KUVW3NJ6VASH7WPNFU2IATPZWM3POIRNQC, 2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-04-04T12:07:50.967878750Z[Etc/UTC], message= Improved caching code a lot\n" +
"), PijulPushEntry(hash=A7IL6I3VCB3F3QMYQFPXBL33DN5XDGGQPZIBZRHYSQBSIUBBFQJQC, dependencies=[37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC], authors=[Author(name=Jonathan, fullName=Jonathan H. R. Lopes, email=jhrldev@gmail.com)], date=2021-04-04T18:38:01.065118605Z[Etc/UTC], message= More files to support .ignore\n" +
"\n" +
")])"
}
})
remote.invalid.remote.text=Invalid remote server {0}...
package com.github.jonathanxd.dracon.push
import com.github.jonathanxd.dracon.log.PijulHash
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vfs.VfsUtil
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.VcsUserImpl
import java.nio.file.Path
class PijulVcsFullCommitDetails(
val root: Path,
val project: Project,
val entry: PijulPushEntry,
val changeRetriever: (String) -> List<Change>
) : VcsFullCommitDetails {
override fun getId(): Hash =
PijulHash(this.entry.hash)
override fun getParents(): MutableList<Hash> =
this.entry.dependencies.map(::PijulHash).toMutableList()
override fun getTimestamp(): Long = this.entry.date.toEpochSecond()
override fun getRoot(): VirtualFile =
VirtualFileManager.getInstance().findFileByNioPath(this.root)!!
override fun getSubject(): String =
if (this.entry.message.isNotEmpty()) ""
else this.entry.message.split("\n")[0]
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.hash).toMutableList()
override fun getChanges(parent: Int): MutableCollection<Change> =
this.changeRetriever(this.parents[parent].asString()).toMutableList()
}
package com.github.jonathanxd.dracon.push
import com.intellij.dvcs.push.PushTarget
import com.intellij.openapi.util.NlsSafe
data class PijulTarget(@NlsSafe val target: String, @NlsSafe val channel: String) : PushTarget {
override fun hasSomethingToPush(): Boolean = true
override fun getPresentation(): String = this.target
override fun toString(): String = this.getPresentation()
}
package com.github.jonathanxd.dracon.push
import com.github.jonathanxd.dracon.context.PijulVcsContext
import com.github.jonathanxd.dracon.dialog.PijulChangesDialog
import com.github.jonathanxd.dracon.i18n.DraconBundle
import com.github.jonathanxd.dracon.pijul.NonZeroExitStatusCode
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.github.jonathanxd.dracon.pijul.pijul
import com.github.jonathanxd.dracon.repository.PijulRepository
import com.intellij.dvcs.push.PushSpec
import com.intellij.dvcs.push.Pusher
import com.intellij.dvcs.push.VcsPushOptionValue
import com.intellij.notification.Notification
import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
class PijulPusher : Pusher<PijulRepository, PijulPushSource, PijulTarget>() {
private val PUSH_GROUP =
NotificationGroup.createIdWithTitle("Push Changes", DraconBundle.message("push.notification.group.id"))
override fun push(
pushSpecs: Map<PijulRepository, PushSpec<PijulPushSource, PijulTarget>>,
vcsPushOptionValue: VcsPushOptionValue?, force: Boolean
) {
for ((repository, pijulSpec) in pushSpecs) {
val destination: PijulTarget = pijulSpec.target
val source: PijulPushSource = pijulSpec.source
val project: Project = repository.project
val ctx = project.service<PijulVcsContext>()
val push = pijul(project).push(
project,
ctx.root,
source.channel,
destination.channel,
destination.target
) {
it.connectAndRetrieveContent()
if (it.connected()) {
PijulChangesDialog(project, ctx.root, it, it.content(),
DraconBundle.message("action.Pijul.Push.text"),
DraconBundle.message("push.button.text")).showAndGet()
}
}
if (push.statusCode is SuccessStatusCode) {
Notifications.Bus.notify(
Notification(
PUSH_GROUP,
DraconBundle.message("push.notification.title"),
DraconBundle.message("push.notification.success"),
NotificationType.ERROR,
),
project
)
} else {
push.statusCode as NonZeroExitStatusCode
Notifications.Bus.notify(
Notification(
PUSH_GROUP,
DraconBundle.message("push.notification.title"),
DraconBundle.message("push.notification.failure", push.statusCode.exitCode, push.statusCode.message),
NotificationType.INFORMATION,
),
project
)
}
}
}
}
package com.github.jonathanxd.dracon.push
import com.github.jonathanxd.dracon.i18n.DraconBundle
import com.github.jonathanxd.dracon.repository.PijulRepository
import com.intellij.dvcs.DvcsUtil
import com.intellij.dvcs.push.PushTargetPanel
import com.intellij.dvcs.push.VcsError
import com.intellij.dvcs.push.ui.PushTargetEditorListener
import com.intellij.dvcs.push.ui.PushTargetTextField
import com.intellij.dvcs.push.ui.VcsEditableTextComponent
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.text.HtmlChunk
import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.ColoredTreeCellRenderer
import com.intellij.util.textCompletion.TextFieldWithCompletion
import com.intellij.ui.SimpleTextAttributes
import java.awt.BorderLayout
class PijulPushTargetPanel(repository: PijulRepository, source: PijulPushSource, defaultTarget: PijulTarget?) :
PushTargetPanel<PijulTarget>() {
private val myRepository: PijulRepository
private val myBranchName: @NlsSafe String?
private val myDestTargetPanel: TextFieldWithCompletion
private val myTargetRenderedComponent: VcsEditableTextComponent
override fun render(
renderer: ColoredTreeCellRenderer,
isSelected: Boolean,
isActive: Boolean,
forceRenderedText: String?
) {
if (forceRenderedText != null) {
myDestTargetPanel.text = forceRenderedText
renderer.append(forceRenderedText)
return
}
val targetText = myDestTargetPanel.text
if (StringUtil.isEmptyOrSpaces(targetText)) {
renderer.append(
DraconBundle.message("action.Pijul.push.enter.remote"),
SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES,
myTargetRenderedComponent
)
}
myTargetRenderedComponent.setSelected(isSelected)
myTargetRenderedComponent.setTransparent(!isActive)
myTargetRenderedComponent.render(renderer)
}
override fun getValue(): PijulTarget {
return createValidPushTarget()
}
private fun createValidPushTarget(): PijulTarget {
return PijulTarget(myDestTargetPanel.text, myBranchName!!)
}
override fun fireOnCancel() {
myDestTargetPanel.text = myTargetRenderedComponent.text
}
override fun fireOnChange() {
myTargetRenderedComponent.updateLinkText(myDestTargetPanel.text)
}
override fun verify(): ValidationInfo? {
return if (StringUtil.isEmptyOrSpaces(myDestTargetPanel.text)) {
ValidationInfo(VcsError.createEmptyTargetError(DvcsUtil.getShortRepositoryName(myRepository)).text, this)
} else null
}
override fun setFireOnChangeAction(action: Runnable) {
// no extra changing components => ignore
}
override fun addTargetEditorListener(listener: PushTargetEditorListener) {
myDestTargetPanel.addDocumentListener(object : DocumentListener {
override fun documentChanged(e: DocumentEvent) {
listener.onTargetInEditModeChanged(myDestTargetPanel.text)
}
})
}
init {
layout = BorderLayout()
isOpaque = false
myRepository = repository
myBranchName = source.channel
val targetVariants: List<String> = repository.pijulConfig.remotes.keys.toList()
val defaultText = defaultTarget?.presentation ?: ""
myTargetRenderedComponent = VcsEditableTextComponent(HtmlChunk.link("", defaultText).toString(), null)
myDestTargetPanel = PushTargetTextField(repository.project, targetVariants, defaultText)
add(myDestTargetPanel, BorderLayout.CENTER)
}
}
package com.github.jonathanxd.dracon.push
import com.github.jonathanxd.dracon.pijulVcs
import com.github.jonathanxd.dracon.repository.PijulRepository
import com.github.jonathanxd.dracon.repository.PijulRepositoryManager
import com.intellij.dvcs.push.OutgoingCommitsProvider
import com.intellij.dvcs.push.PushSupport
import com.intellij.dvcs.push.PushTargetPanel
import com.intellij.dvcs.push.Pusher
import com.intellij.dvcs.repo.RepositoryManager
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.AbstractVcs
class PijulPushSupport(val project: Project) : PushSupport<PijulRepository, PijulPushSource, PijulTarget>() {
private val vcs = pijulVcs(project)
override fun getVcs(): AbstractVcs = this.vcs
override fun getPusher(): Pusher<PijulRepository, PijulPushSource, PijulTarget> =
PijulPusher()
override fun getOutgoingCommitsProvider(): OutgoingCommitsProvider<PijulRepository, PijulPushSource, PijulTarget> =
PijulOutgoingChangesProvider()
override fun getDefaultTarget(repository: PijulRepository): PijulTarget? {
return repository.pijulConfig.defaultRemote?.let {
PijulTarget(it, repository.currentBranchName!!)
}
}
override fun getSource(repository: PijulRepository): PijulPushSource {
return PijulPushSource(repository.currentBranchName!!)
}
override fun getRepositoryManager(): RepositoryManager<PijulRepository> =
this.project.service<PijulRepositoryManager>()
override fun createTargetPanel(
repository: PijulRepository,
source: PijulPushSource,
defaultTarget: PijulTarget?
): PushTargetPanel<PijulTarget> =
PijulPushTargetPanel(repository, source, defaultTarget)
override fun isForcePushAllowed(repo: PijulRepository, target: PijulTarget?): Boolean = false
override fun isSilentForcePushAllowed(target: PijulTarget): Boolean = false
override fun saveSilentForcePushTarget(target: PijulTarget) {
}
}
package com.github.jonathanxd.dracon.push
import com.intellij.dvcs.push.PushSource
class PijulPushSource(val channel: String) : PushSource {
override fun getPresentation(): String = this.channel
}
package com.github.jonathanxd.dracon.push
import com.github.jonathanxd.dracon.log.Author
import com.intellij.vcs.log.VcsUser
import org.tomlj.Toml
import java.io.Serializable
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
data class PijulPushChanges(val entries: List<PijulPushEntry>): Serializable {
companion object {
const val serialVersionUID = 1L
}
}
data class PijulPushEntry(val hash: String,
val dependencies: List<String>,
val authors: List<Author>,
// TODO: Parse date
val date: ZonedDateTime,
val message: String): Serializable {
companion object {
const val serialVersionUID = 1L
}
}
fun String.parseDate(): ZonedDateTime {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSSSSSSSS] z")
return ZonedDateTime.parse(this, formatter)
}
fun String.parsePijulPushChanges(): PijulPushChanges {
val dependenciesPrefix = " Dependencies: "
val authorPrefix = " Author: "
val datePrefix = " Date: "
val spacePrefix = " "
val iter = this.lines().listIterator()
val entries = mutableListOf<PijulPushEntry>()
while (iter.hasNext()) {
val line = iter.next()
if (line.startsWith("#") || line.isEmpty()) {
continue
}
if (line[0] != ' ') {
val hash = line
// Skip empty line
iter.next()
var next = iter.next()
val dependencies = mutableListOf<String>()
if (next.startsWith(dependenciesPrefix)) {
dependencies.addAll(next.substring(dependenciesPrefix.length).split(" "))
next = iter.next()
}
val authors = mutableListOf<Author>()
if (next.startsWith(authorPrefix)) {
val authorsToml = "authors = ${next.substring(authorPrefix.length)}"
val toml = Toml.parse(authorsToml)
val authorsArray = toml.getArray("authors")
for (i in 0 until authorsArray!!.size()) {
val authorTable = authorsArray.getTable(i)
authors.add(
Author(
authorTable.getString("name"),
authorTable.getString("full_name"),
authorTable.getString("email")
)
)
}
next = iter.next()
}
val date = if (next.startsWith(datePrefix)) {
val date = next.substring(datePrefix.length)
next = iter.next()
date
} else {
""
}
val messages = mutableListOf<String>()
if (next.trim().isEmpty()) {
if (iter.hasNext()) {
next = iter.next()
while (next.startsWith(spacePrefix) || next.trim().isEmpty()) {
messages += if (next.trim().isEmpty()) {
""
} else {
next.substring(spacePrefix.length)
}
if (!iter.hasNext()) break
next = iter.next()
}
if (next.isNotEmpty() && next[0] != ' ') {
iter.previous()
}
}
}
entries.add(PijulPushEntry(
hash,
dependencies,
authors,
date.parseDate(),
messages.joinToString("\n")
))
}
}
return PijulPushChanges(entries)
}
package com.github.jonathanxd.dracon.push
import com.github.jonathanxd.dracon.changes.PijulCommittedChangeList
import com.github.jonathanxd.dracon.context.PijulVcsContext
import com.github.jonathanxd.dracon.i18n.DraconBundle
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.github.jonathanxd.dracon.pijul.pijul
import com.github.jonathanxd.dracon.repository.PijulRepository
import com.intellij.dvcs.push.OutgoingCommitsProvider
import com.intellij.dvcs.push.OutgoingResult
import com.intellij.dvcs.push.PushSpec
import com.intellij.dvcs.push.VcsError
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.vcs.log.VcsFullCommitDetails
class PijulOutgoingChangesProvider : OutgoingCommitsProvider<PijulRepository, PijulPushSource, PijulTarget>() {
private val logger = Logger.getInstance(PijulOutgoingChangesProvider::class.java)
override fun getOutgoingCommits(
repository: PijulRepository,
pushSpec: PushSpec<PijulPushSource, PijulTarget>,
initial: Boolean
): OutgoingResult {
val project = repository.project
val ctx = project.service<PijulVcsContext>()
var content = ""
val errors = mutableListOf<VcsError>()
val fromChannel = pushSpec.source.channel
val toChannel = pushSpec.target.channel
val toRemote = pushSpec.target.target
val r = pijul(project).push(project, ctx.root, fromChannel, toChannel, toRemote) {
it.connectAndRetrieveContent()
if (it.connected()) {
content = it.content()
it.write("")
it.close()
} else {
errors.add(VcsError(DraconBundle.message("remote.invalid.remote.text", pushSpec.target.target)))
content = ""
}
}
if (content.isEmpty()) {
return OutgoingResult(emptyList(), errors)
} else {
try {
val parsed = content.parsePijulPushChanges()
val commits: List<VcsFullCommitDetails> = parsed.entries.map {
PijulVcsFullCommitDetails(
ctx.root,
project,
it
) {
val changes = mutableListOf<PijulCommittedChangeList>()
pijul(project).changes(project, ctx.root, it, -1, {
true
}) {
changes += it
}
changes.single().changes.toList()
}
}
return OutgoingResult(commits, errors)
} catch (t: Throwable) {
logger.error(t)
}
}
return OutgoingResult(emptyList(), errors)
}
}
/*
Example of changes (this is not hard to parse, but I will not do that now):
# Please select the changes to push. The lines that contain just a
# valid hash, and no other character (except possibly a newline), will
# be pushed.
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
Dependencies: Q7FXTHVUPVAFMNY277C3NFJO3VXLZU5G6C6UYSD5QPURHSG3A7OQC EAGIDXOLFTHZMZ77ZWAM7MVVUBBXJMZD7RZUNBHJPYRFGGKILGVAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-03-31 22:43:40.062881279 UTC
Improved support for revisions
5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
Dependencies: ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-01 00:07:39.373781350 UTC
Add support to view files affected by a revision
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
Dependencies: 5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-01 17:04:54.758320581 UTC
- Add Show History to Pijul menu
- Always ignore .idea and .pijul in tracking.
- Make findPijul a generic function to allow to find editor-server.
- Only show one revision for directories.
- Add `Hunk::resolvePath(Path)` to resolve the affected file to a Java NIO Path.
- Fix StringOutOfBounds in Change Parsering Algorithm
- Use editor-server instead of copie for interfacing with `pijul record` file.
- Fix FileStatus provider not returning correctly for untracked files.
- Add CommittedChangesProvider for Pijul.
Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
Dependencies: B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-02 21:31:47.581509792 UTC
Remove use of coroutines, which was blocking IntelliJ UI in larger repositories
Improvements for bigger repositories, now Dracon caches the changes that happened in a revision in a file, so everytime Dracon needs to query the changes of a revision, it loads directly from memory instead of doing a full-scan in Pijul repository. For tiny projects it is not a problem, but for medium ones it takes more than five minutes to scan the entire repository (and it was tested with a repo of only 700 records, however there was changes that had more than 60.000 lines).
The cache file is saved in IntelliJ Data Path (project specific) and is compressed with gzip, so it will not use so much disk space (the cost worths the gains).
ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
Dependencies: Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-03 03:18:36.900275901 UTC
More caches, better and generic cache code.
Now Dracon listen to file changes to drop cached data.
Implemented caches:
- File contents in specific revision (never dropped)
- Pijul ls and Pijul diff results
- File Revision and File changes by patch
- some others..
Dracon is incredible fast now, but still will take some time for bigger repos.
2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC
Dependencies: ISO7J5ZH5UB7NFZKTKKJQHQHCP4DWQ3F7SM2NDMVYJAGGIKDLX4QC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-04 03:15:58.713132084 UTC
Add auto installation support and cache content of ContentRevision
37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
Dependencies: A7JOR7M3BUKXYMMAR43IK4GXFXGI33HM6H6LZHDES3HWW6GYFAYAC G54TB4QZZ6OYC2KZTB7KUVW3NJ6VASH7WPNFU2IATPZWM3POIRNQC 2N67RQZCVGL6GYJJLM2US4YVCEIUK25AHCLD66C7HR4PPTNUOCWAC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-04 12:07:50.967878750 UTC
Improved caching code a lot
A7IL6I3VCB3F3QMYQFPXBL33DN5XDGGQPZIBZRHYSQBSIUBBFQJQC
Dependencies: 37OJKSWJFDRHNWQW6P7HSZX6OWZWVNCJ2IFT42O5TANQF7VOVX6AC
Author: [{ name = "Jonathan", full_name = "Jonathan H. R. Lopes", email = "jhrldev@gmail.com" }]
Date: 2021-04-04 18:38:01.065118605 UTC
More files to support .ignore
*/
@RequiresBackgroundThread
fun push(project: Project, root: Path, editorServerConsumer: (EditorServer) -> Unit): PijulOperationResult<String>
@RequiresBackgroundThread
fun push(project: Project, root: Path,
fromChannel: String? = null,
toChannel: String? = null,
repository: String? = null,
editorServerConsumer: (EditorServer) -> Unit): PijulOperationResult<String>
package com.github.jonathanxd.dracon.log
import com.intellij.vcs.log.Hash
class PijulHash(val hash: String) : Hash {
override fun asString(): String = this.hash
override fun toShortString(): String = this.hash.substring(0, 10)
}
package com.github.jonathanxd.dracon.dialog
import com.github.jonathanxd.dracon.completion.PijulRecordFileTextCompletionProvider
import com.github.jonathanxd.dracon.editor.EditorServer
import com.github.jonathanxd.dracon.i18n.DraconBundle
import com.github.jonathanxd.dracon.pijul.NonZeroExitStatusCode
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.github.jonathanxd.dracon.pijul.pijul
import com.intellij.CommonBundle
import com.intellij.ide.IdeBundle
import com.intellij.lang.Language
import com.intellij.notification.Notification
import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogEarthquakeShaker
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.DialogWrapper.DialogWrapperAction
import com.intellij.openapi.ui.DialogWrapper.IdeModalityType
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.openapi.wm.WindowManager
import com.intellij.psi.PsiFile
import com.intellij.ui.EditorTextField
import com.intellij.ui.LanguageTextField
import com.intellij.ui.TextFieldWithAutoCompletion
import com.intellij.ui.TextFieldWithAutoCompletionListProvider
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.textCompletion.TextCompletionUtil
import org.jetbrains.annotations.Nullable
import java.awt.*
import java.awt.event.*
import java.beans.PropertyChangeEvent
import java.beans.PropertyChangeListener
import java.lang.Boolean
import java.nio.file.Path
import javax.swing.*
open class PijulExpertRecordDialog(val project: Project,
val vcsRoot: Path,
val text: String) : DialogWrapper(project, true) {
private val EXPERT_MODE_GROUP =
NotificationGroup.createIdWithTitle("Expert Mode", DraconBundle.message("expert.mode.notification.group.id"))
init {
init()
title = DraconBundle.message("action.Pijul.ExpertRecord.text")
}
//private lateinit var editor: EditorTextField
//private lateinit var field: TextFieldWithAutoCompletion<String>
private lateinit var editor: CustomEditorField
override fun createCenterPanel(): JComponent {
val dialogPanel = JPanel(BorderLayout())
val tomlLanguage = Language.findLanguageByID("TOML")
val ext = FileTypeRegistry.getInstance().getFileTypeByExtension("toml")
val doc = LanguageTextField.createDocument(
this.text,
tomlLanguage,
this.project,
RecordDocumentCreator()
)
editor = CustomEditorField(tomlLanguage, project, this.text)
/*val field = TextFieldWithAutoCompletion(this.project, Provider(), true, this.text)
field.setOneLineMode(false)
field.isViewer = false*/
//editor = EditorTextField(doc, this.project, ext, false, false)
editor.preferredSize = Dimension(800, 600)
editor.setOneLineMode(false)
editor.isViewer = false
editor.isVisible = true
editor.isEnabled = true
//field.preferredSize = Dimension(800, 600)
dialogPanel.add(editor, BorderLayout.CENTER)
return dialogPanel
}
override fun createActions(): Array<Action> {
return arrayOf(RecordAction(), this.cancelAction)
}
override fun doOKAction() {
if (record.statusCode is SuccessStatusCode) {
val hash = record.result!!.substring("Hash:".length).trim()
Notifications.Bus.notify(
Notification(
EXPERT_MODE_GROUP,
DraconBundle.message("expert.mode.notification.title"),
DraconBundle.message("expert.mode.notification.success", hash),
NotificationType.ERROR,
),
this.project
)
} else {
record.statusCode as NonZeroExitStatusCode
Notifications.Bus.notify(
Notification(
EXPERT_MODE_GROUP,
DraconBundle.message("expert.mode.notification.title"),
DraconBundle.message("expert.mode.notification.failure", record.statusCode.exitCode, record.statusCode.message),
NotificationType.INFORMATION,
),
this.project
)
super.doOKAction()
}
protected inner class RecordAction() : DialogWrapper.DialogWrapperAction(DraconBundle.message("expert.mode.button.record")) {
override fun doAction(e: ActionEvent?) {
val infoList: List<ValidationInfo> = doValidateAll()
if (infoList.isNotEmpty()) {
val info = infoList[0]
if (info.component != null && info.component!!.isVisible) {
IdeFocusManager.getInstance(null).requestFocus(info.component!!, true)
}
updateErrorInfo(infoList)
startTrackingValidation()
if (ContainerUtil.exists(
infoList
) { info1: ValidationInfo -> !info1.okEnabled }
) return
}
doOKAction()
}
init {
addPropertyChangeListener { evt: PropertyChangeEvent ->
if (NAME == evt.propertyName) {
repaint()
}
}
}
}
class Provider : TextFieldWithAutoCompletionListProvider<String>(listOf("author", "[[authors]]", "authors", "name", "full_name", "email")) {
override fun getLookupString(item: String): String {
return item
}
}
class RecordDocumentCreator : TextCompletionUtil.DocumentWithCompletionCreator(
TextFieldWithAutoCompletion.StringsCompletionProvider(listOf("author", "[[authors]]", "authors", "name", "full_name", "email"),null),
true
)
}
class CustomEditorField(language: Language?, project: Project, s: String) : LanguageTextField(language, project, s, PijulExpertRecordDialog.RecordDocumentCreator(), false) {
override fun createEditor(): EditorEx {
val editor = super.createEditor()
editor.setVerticalScrollbarVisible(true)
editor.setHorizontalScrollbarVisible(true)
editor.setCaretEnabled(true)
editor.isViewer = false
editor.isOneLineMode = false
val settings = editor.settings
settings.isLineNumbersShown = true
settings.isAutoCodeFoldingEnabled = true
settings.isFoldingOutlineShown = true
settings.isAllowSingleLogicalLineFolding = true
settings.isRightMarginShown=true
return editor
}
}
}*/
this.editorServer.write(this.editor.text)
this.editorServer.close()
/*val record = pijul(this.project).recordFromString(this.project, this.vcsRoot, this.editor.text)
val editorServer: EditorServer,
package com.github.jonathanxd.dracon.dialog
import com.github.jonathanxd.dracon.editor.EditorServer
import com.github.jonathanxd.dracon.i18n.DraconBundle
import com.intellij.lang.Language
import com.intellij.notification.NotificationGroup
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.NlsContexts.DialogTitle
import com.intellij.openapi.util.NlsContexts.Button
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.ui.LanguageTextField
import com.intellij.ui.TextFieldWithAutoCompletion
import com.intellij.ui.TextFieldWithAutoCompletionListProvider
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.textCompletion.TextCompletionUtil
import java.awt.*
import java.awt.event.*
import java.beans.PropertyChangeEvent
import java.nio.file.Path
import javax.swing.*
open class PijulChangesDialog(val project: Project,
val vcsRoot: Path,
val editorServer: EditorServer,
val text: String,
@DialogTitle val title_: String,
@Button val button_: String) : DialogWrapper(project, true) {
init {
init()
title = title_
}
private lateinit var editor: CustomEditorField
override fun createCenterPanel(): JComponent {
val dialogPanel = JPanel(BorderLayout())
val tomlLanguage = Language.findLanguageByID("TOML")
editor = CustomEditorField(tomlLanguage, project, this.text)
editor.preferredSize = Dimension(800, 600)
editor.setOneLineMode(false)
editor.isViewer = false
editor.isVisible = true
editor.isEnabled = true
dialogPanel.add(editor, BorderLayout.CENTER)
return dialogPanel
}
override fun createActions(): Array<Action> {
return arrayOf(RecordAction(), this.cancelAction)
}
override fun doOKAction() {
this.editorServer.write(this.editor.text)
this.editorServer.close()
super.doOKAction()
}
protected inner class RecordAction() : DialogWrapper.DialogWrapperAction(button_) {
override fun doAction(e: ActionEvent?) {
val infoList: List<ValidationInfo> = doValidateAll()
if (infoList.isNotEmpty()) {
val info = infoList[0]
if (info.component != null && info.component!!.isVisible) {
IdeFocusManager.getInstance(null).requestFocus(info.component!!, true)
}
updateErrorInfo(infoList)
startTrackingValidation()
if (ContainerUtil.exists(
infoList
) { info1: ValidationInfo -> !info1.okEnabled }
) return
}
doOKAction()
}
init {
addPropertyChangeListener { evt: PropertyChangeEvent ->
if (NAME == evt.propertyName) {
repaint()
}
}
}
}
class Provider : TextFieldWithAutoCompletionListProvider<String>(listOf("author", "[[authors]]", "authors", "name", "full_name", "email")) {
override fun getLookupString(item: String): String {
return item
}
}
class RecordDocumentCreator : TextCompletionUtil.DocumentWithCompletionCreator(
TextFieldWithAutoCompletion.StringsCompletionProvider(listOf("author", "[[authors]]", "authors", "name", "full_name", "email"),null),
true
)
}
class CustomEditorField(language: Language?, project: Project, s: String) : LanguageTextField(language, project, s, PijulChangesDialog.RecordDocumentCreator(), false) {
override fun createEditor(): EditorEx {
val editor = super.createEditor()
editor.setVerticalScrollbarVisible(true)
editor.setHorizontalScrollbarVisible(true)
editor.setCaretEnabled(true)
editor.isViewer = false
editor.isOneLineMode = false
val settings = editor.settings
settings.isLineNumbersShown = true
settings.isAutoCodeFoldingEnabled = true
settings.isFoldingOutlineShown = true
settings.isAllowSingleLogicalLineFolding = true
settings.isRightMarginShown=true
return editor
}
}
package com.github.jonathanxd.dracon.config
import com.intellij.ide.plugins.PluginManager
import com.intellij.openapi.diagnostic.Logger
import org.tomlj.Toml
import java.nio.file.Files
import java.nio.file.Path
class PijulConfig(val root: Path) {
private val logger = Logger.getInstance(PijulConfig::class.java)
var currentChannel: String = ""
var extraDependencies: List<String> = mutableListOf()
var defaultRemote: String? = null
var remotes: Map<String, String> = mutableMapOf()
init {
this.load(root)
}
fun load(root: Path) {
val configPath = root.resolve(".pijul").resolve("config")
if (!Files.exists(configPath)) {
this.logger.error("Could not find $configPath.")
return
}
val toml = Toml.parse(configPath)
if (toml.hasErrors()) {
this.logger.error("Errors found in .pijul/config TOML file.")
for (error in toml.errors()) {
this.logger.error(error)
}
}
this.currentChannel = toml.getString("current_channel")!!
val exDps = toml.getArray("extra_dependencies")!!
this.extraDependencies = List(exDps.size()) {
exDps.getString(it)
}
this.defaultRemote = toml.getString("default_remote")
this.remotes = toml.getTable("remotes")?.toMap() as? Map<String, String> ?: emptyMap()
}
}
this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, rootPath, listOf("ls"), log = false)) {
val trackedFiles = it.split("\n")
this.doExecutionWithMapper(
"file_status_from_ls",
this.createExecPijulOperation(project, rootPath, listOf("ls"), log = false)
) {
val trackedFiles = it.split("\n")
val fPath = Paths.get(VcsUtil.getFilePath(file.path).path).relativeTo(rootPath).toString()
if (trackedFiles.contains(fPath)) {
FileStatus.NOT_CHANGED
} else {
FileStatus.UNKNOWN
val fPath = Paths.get(VcsUtil.getFilePath(file.path).path).relativeTo(rootPath).toString()
if (trackedFiles.contains(fPath)) {
FileStatus.NOT_CHANGED
} else {
FileStatus.UNKNOWN
}
this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, root, listOf("ls"), log = false)) {
this.doExecutionWithMapper(
"file_status_from_ls",
this.createExecPijulOperation(project, root, listOf("ls"), log = false)
) {
this.doExecutionWithMapper("file_status_from_ls", this.createExecPijulOperation(project, root, listOf("ls"), log = false)) {
this.doExecutionWithMapper(
"file_status_from_ls",
this.createExecPijulOperation(project, root, listOf("ls"), log = false)
) {
return this.doExecutionWithMapper("tracked_files_from_ls", this.createExecPijulOperation(project, root, listOf("ls"), log = false)) {
it.split("\n").map {
root.resolve(it)
}
return this.doExecutionWithMapper(
"tracked_files_from_ls",
this.createExecPijulOperation(project, root, listOf("ls"), log = false)
) {
it.split("\n").map {
root.resolve(it)
return PijulOperationResult("rollbackTo_${hash}", NonZeroExitStatusCode(-1, "Could not find hash $hash in Pijul log."), false)
return PijulOperationResult(
"rollbackTo_${hash}",
NonZeroExitStatusCode(-1, "Could not find hash $hash in Pijul log."),
false
)
it
}
}
override fun push(
project: Project,
root: Path,
editorServerConsumer: (EditorServer) -> Unit
): PijulOperationResult<String> {
val arguments = mutableListOf("push")
val operation = this.createPainlessExecPijulWithEditorServer(
this.project,
root,
arguments,
editorServerConsumer
)
return this.doExecutionWithMapper("push", operation) {
override fun push(
project: Project,
root: Path,
fromChannel: String?,
toChannel: String?,
repository: String?,
editorServerConsumer: (EditorServer) -> Unit
): PijulOperationResult<String> {
val arguments = mutableListOf("push")
if (fromChannel != null) {
arguments.add("--from-channel")
arguments.add(fromChannel)
}
if (toChannel != null) {
arguments.add("--to-channel")
arguments.add(toChannel)
}
if (repository != null) {
arguments.add(repository)
}
val operation = this.createPainlessExecPijulWithEditorServer(
this.project,
root,
arguments,
editorServerConsumer
)
return this.doExecutionWithMapper("push", operation) {
it
}
}
}
override fun changes(
project: Project,
root: Path,
revision: String?,
maxCount: Int,
filter: (PijulLogEntry) -> Boolean,
consumer: (PijulCommittedChangeList) -> Unit
): PijulOperationResult<Unit> {
val allRevisions = pijul(this.project).allRevisions(this.project, root).result!!
val log = this.log(this.project, root)
return log.result?.entries?.filter {
filter(it)
}?.filter {
revision == null || it.revision.hash == revision
}?.map { entry ->
PijulCommittedChangeList(
entry.message.trimMiddle(20),
entry.message,
entry.authors.firstOrNull()?.name ?: "No author",
Date.from(entry.date.toInstant()),
entry.hunks.filterIsInstance<HunkWithPath>().groupBy { it.resolvePath(root) }.map { (p, hunks) ->
val hunk = hunks.firstOrNull { it is EditHunk }
?: hunks.firstOrNull { it is ReplacementHunk }
?: hunks.firstOrNull { it is FileDelHunk }
?: hunks.firstOrNull { it is FileAddHunk }
?: hunks.first()
val beforeRevision =
if (hunk is FileAddHunk) null
else findARevisionBefore(entry.changeHash, allRevisions)?.let {
PijulContentRevision(
root,
hunk.resolvePath(root),
it,
this.project
)
}
val afterRevision =
if (hunk is FileDelHunk) null
else PijulContentRevision(
root,
hunk.resolvePath(root),
entry.revision,
this.project
)
Change(
beforeRevision,
afterRevision,
hunk.status
)
}.toMutableList(),
entry.revision,
false,
pijulVcs(this.project)
)
}?.let { if (maxCount == -1) it else it.take(maxCount) }?.forEach {
consumer(it)
}?.let {
PijulOperationResult("changes", SuccessStatusCode, Unit)
} ?: PijulOperationResult("changes", log.statusCode, Unit)
}
fun findARevisionBefore(current: String, allRevisions: List<PijulRevisionNumber>): PijulRevisionNumber? {
for (i in allRevisions.indices) {
if (current == allRevisions[i].hash) {
return if (i + 1 >= allRevisions.size) {
null
} else {
allRevisions[i + 1]
}
}
}
return null
val change = this.doExecutionWithMapper("diff", this.createExecPijulOperation(project, rootPath, listOf("diff"))) {
val change =
this.doExecutionWithMapper("diff", this.createExecPijulOperation(project, rootPath, listOf("diff"))) {
if (it.isEmpty()) null
else it.parseChange("")
}
return change
}
override fun diff(project: Project, root: Path): PijulOperationResult<PijulLogEntry> {
val change = this.doExecutionWithMapper("diff", this.createExecPijulOperation(project, root, listOf("diff"))) {
fun <T> doExecutionWithMapper(name: String,
execution: PijulExecution,
regularStreamStringMapper: (String) -> T?): PijulOperationResult<T> {
fun <T> doExecutionWithMapper(
name: String,
execution: PijulExecution,
regularStreamStringMapper: (String) -> T?
): PijulOperationResult<T> {
fun <T> doExecutionWithMapperEvenFailed(name: String,
execution: PijulExecution,
regularStreamStringMapper: (String) -> T?): PijulOperationResult<T> {
fun <T> doExecutionWithMapperEvenFailed(
name: String,
execution: PijulExecution,
regularStreamStringMapper: (String) -> T?
): PijulOperationResult<T> {
private fun createExecPijulOperation(project: Project,
dir: Path,
args: List<String>,
log: Boolean = true): PijulExecution {
private fun createExecPijulOperation(
project: Project,
dir: Path,
args: List<String>,
log: Boolean = true
): PijulExecution {
private fun createPainlessExecPijulOperation(project: Project,
dir: Path,
args: List<String>): PijulExecution {
private fun createPainlessExecPijulOperation(
project: Project,
dir: Path,
args: List<String>
): PijulExecution {
private fun <K: Any> createPainlessExecPijulOperationWithCache(project: Project,
dir: Path,
args: List<String>,
key: K): PijulExecution {
private fun <K : Any> createPainlessExecPijulOperationWithCache(
project: Project,
dir: Path,
args: List<String>,
key: K
): PijulExecution {
private fun createPainlessExecPijulWithCopieOperation(project: Project,
dir: Path,
copiePath: Path,
copieMode: CopieMode,
args: List<String>): PijulExecution {
private fun createPainlessExecPijulWithCopieOperation(
project: Project,
dir: Path,
copiePath: Path,
copieMode: CopieMode,
args: List<String>
): PijulExecution {
private fun createPainlessExecPijulWithEditorServer(project: Project,
dir: Path,
args: List<String>,
editorServerConsumer: (EditorServer) -> Unit): PijulExecution {
private fun createPainlessExecPijulWithEditorServer(
project: Project,
dir: Path,
args: List<String>,
editorServerConsumer: (EditorServer) -> Unit
): PijulExecution {
class PijulCommittedChangesProvider(val project: Project) : CommittedChangesProvider<CommittedChangeList, ChangeBrowserSettings> {
class PijulCommittedChangesProvider(val project: Project) :
CommittedChangesProvider<CommittedChangeList, ChangeBrowserSettings> {
val channel = pijul(this.project).channel(this.project, ctx.root).result?.channels?.firstOrNull { it.current }?.name ?: "unknown"
val channel =
pijul(this.project).channel(this.project, ctx.root).result?.channels?.firstOrNull { it.current }?.name
?: "unknown"
}?.filter {
revision == null || it.revision.hash == revision.asString()
}?.map { entry ->
PijulCommittedChangeList(
entry.message.trimMiddle(20),
entry.message,
entry.authors.firstOrNull()?.name ?: "No author",
Date.from(entry.date.toInstant()),
entry.hunks.filterIsInstance<HunkWithPath>().groupBy { it.resolvePath(ctx.root) }.map { (p, hunks) ->
val hunk = hunks.firstOrNull { it is EditHunk }
?: hunks.firstOrNull { it is ReplacementHunk }
?: hunks.firstOrNull { it is FileDelHunk }
?: hunks.firstOrNull { it is FileAddHunk }
?: hunks.first()
val beforeRevision =
if (hunk is FileAddHunk) null
else findARevisionBefore(entry.changeHash, allRevisions)?.let {
PijulContentRevision(
ctx.root,
hunk.resolvePath(ctx.root),
it,
this.project
)
}
val afterRevision =
if (hunk is FileDelHunk) null
else PijulContentRevision(
ctx.root,
hunk.resolvePath(ctx.root),
entry.revision,
this.project
)
Change(
beforeRevision,
afterRevision,
hunk.status
)
}.toMutableList(),
entry.revision,
false,
pijulVcs(this.project)
)
}?.let { if (maxCount == -1) it else it.take(maxCount) }?.forEach {
}) {
fun findARevisionBefore(current: String, allRevisions: List<PijulRevisionNumber>): PijulRevisionNumber? {
for (i in allRevisions.indices) {
if (current == allRevisions[i].hash) {
return if (i + 1 >= allRevisions.size) {
null
} else {
allRevisions[i + 1]
}
}
}
return null
}
return this.cache.queryOrLoadAsync(fileRev) {
loadStateInRevision(
rev.hash,
this.project,
return this.cache.queryOrLoadAsyncExpanded(fileRev) {
val revisions = loadStateInEveryRevisionForAllFiles(
listOf(rev.hash),
project,
file
).toByteArray(Charsets.UTF_8)
EmptyProgressIndicator()
)
val revisionsForHash = revisions[rev.hash]!!
val stringKeys = revisionsForHash.mapKeys { (k, _) -> k.filePathAsString() }
stringKeys[it.filePath]!! to stringKeys.map { (k, v) -> FileRevisionRef(k, rev.hash) to v }
this.inMemory[key] = computeEntry
this.manager.write(this.inMemory)
computeEntry
} finally {
this.lock.unlock()
}.value
}, this.updateExecutor)
}
}
fun queryOrLoadAsyncExpanded(key: K, compute: (K) -> Pair<V, List<Pair<K, V>>>): CompletableFuture<V> {
if (this.inMemory.containsKey(key)) {
return CompletableFuture.completedFuture(this.inMemory[key]!!.value)
} else {
return CompletableFuture.supplyAsync({
this.lock.lock()
try {
if (this.inMemory.containsKey(key)) {
return@supplyAsync this.inMemory[key]!!.value
}
val instant = Instant.now()
val computed = compute(key)
val computeEntry = CachedValue(instant, computed.first)
package com.github.jonathanxd.dracon.actions
import com.github.jonathanxd.dracon.dialog.PijulChangesDialog
import com.github.jonathanxd.dracon.i18n.DraconBundle
import com.github.jonathanxd.dracon.pijul.NonZeroExitStatusCode
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.github.jonathanxd.dracon.pijul.pijul
import com.intellij.notification.Notification
import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.vcs.ProjectLevelVcsManager
class PijulPushRecord : DumbAwareAction() {
private val PUSH_GROUP =
NotificationGroup.createIdWithTitle("Push Changes", DraconBundle.message("push.notification.group.id"))
override fun actionPerformed(e: AnActionEvent) {
val project = e.project!!
val vcsManager = ProjectLevelVcsManager.getInstance(project)
val root = vcsManager.allVcsRoots.first().path.toNioPath()
val record = pijul(project).push(project, root) {
it.connectAndRetrieveContent()
if (it.connected()) {
PijulChangesDialog(project, root, it, it.content(),
DraconBundle.message("action.Pijul.Push.text"),
DraconBundle.message("push.button.text")).showAndGet()
}
}
if (record.statusCode is SuccessStatusCode) {
Notifications.Bus.notify(
Notification(
PUSH_GROUP,
DraconBundle.message("push.notification.title"),
DraconBundle.message("push.notification.success"),
NotificationType.ERROR,
),
project
)
} else {
record.statusCode as NonZeroExitStatusCode
Notifications.Bus.notify(
Notification(
PUSH_GROUP,
DraconBundle.message("push.notification.title"),
DraconBundle.message("push.notification.failure", record.statusCode.exitCode, record.statusCode.message),
NotificationType.INFORMATION,
),
project
)
}
}
}
/*if (recordString.result != null) {
val toml = FileTypeRegistry.getInstance().getFileTypeByExtension("toml")
//val editor = EditorTextField(recordString.result, this.project, )
*//*
val psiFile = PsiDocumentManager.getInstance(editor.getProject()).getPsiFile(editor.getDocument())
val element = psiFile!!.findElementAt(editor.getCaretModel().getOffset())
val code: PsiExpressionCodeFragment = JavaCodeFragmentFactory.getInstance(editor.getProject())
.createExpressionCodeFragment("", element, null, true)
val document: Document? = PsiDocumentManager.getInstance(editor.getProject()).getDocument(code)
val myInput = EditorTextField(document, editor.getProject(), JavaFileType.INSTANCE)
*//*
PijulExpertRecordDialog(project, root, recordString.result).showAndGet()
*//*FileEditorManager.getInstance(project).openTextEditor(
OpenFileDescriptor(project, LightVirtualFile("record", toml, recordString.result)),
true
)*//*
}*/