diff --git a/build.gradle.kts b/build.gradle.kts index bc59e54..9d3bd2a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,7 @@ repositories { dependencies { implementation(compose.desktop.currentOs) implementation("org.eclipse.jgit:org.eclipse.jgit:5.13.0.202109080827-r") + implementation("org.apache.sshd:sshd-core:2.7.0") } tasks.withType() { diff --git a/src/main/kotlin/credentials/CredentialsStateManager.kt b/src/main/kotlin/credentials/CredentialsStateManager.kt new file mode 100644 index 0000000..1acdea3 --- /dev/null +++ b/src/main/kotlin/credentials/CredentialsStateManager.kt @@ -0,0 +1,11 @@ +package credentials + +class CredentialsStateManager { + +} + +sealed class CredentialsState { + object CredentialsRequested: CredentialsState() + object CredentialsDenied: CredentialsState() + data class CredentialsAccepted(val user: String, val password: String): CredentialsState() +} \ No newline at end of file diff --git a/src/main/kotlin/credentials/GProcess.kt b/src/main/kotlin/credentials/GProcess.kt new file mode 100644 index 0000000..671cc68 --- /dev/null +++ b/src/main/kotlin/credentials/GProcess.kt @@ -0,0 +1,65 @@ +package credentials + +import org.apache.sshd.client.channel.ChannelExec +import org.apache.sshd.client.session.ClientSession +import java.io.InputStream +import java.io.OutputStream +import java.io.PipedInputStream +import java.io.PipedOutputStream + +class GProcess : Process() { + private lateinit var channel: ChannelExec + private val outputStream = PipedOutputStream() + private val inputStream = PipedInputStream() + private val errorOutputStream = PipedOutputStream() + private val pipedInputStream = PipedInputStream(outputStream) + private val pipedOutputStream = PipedOutputStream(inputStream) + private val pipedErrorInputStream = PipedInputStream(errorOutputStream) + + override fun getOutputStream(): OutputStream { + return pipedOutputStream + } + + override fun getInputStream(): InputStream { + return pipedInputStream + } + + override fun getErrorStream(): InputStream { + return pipedErrorInputStream + } + + override fun waitFor(): Int { + if (isRunning()) + Thread.sleep(100) + + return exitValue() + } + + override fun exitValue(): Int { + check(!isRunning()) + + return channel.exitStatus + } + + override fun destroy() { + if (channel.isOpen) { + channel.close() + } + } + + private fun isRunning(): Boolean { + return channel.exitStatus < 0 && channel.isOpen + } + + fun setup(session: ClientSession, commandName: String) { + val channel = session.createExecChannel(commandName) + channel.out = outputStream + channel.err = errorOutputStream + channel.`in` = inputStream + + channel.open().verify() + + this.channel = channel + } + +} \ No newline at end of file diff --git a/src/main/kotlin/credentials/GRemoteSession.kt b/src/main/kotlin/credentials/GRemoteSession.kt new file mode 100644 index 0000000..3fd1f0d --- /dev/null +++ b/src/main/kotlin/credentials/GRemoteSession.kt @@ -0,0 +1,45 @@ +package credentials + +import org.apache.sshd.client.SshClient +import org.apache.sshd.client.future.ConnectFuture +import org.eclipse.jgit.transport.RemoteSession +import org.eclipse.jgit.transport.URIish +import java.io.PipedInputStream +import java.io.PipedOutputStream + +private const val DEFAULT_SSH_PORT = 22 + +class GRemoteSession : RemoteSession { + private val client = SshClient.setUpDefaultClient() + + private var connectFuture: ConnectFuture? = null + + override fun exec(commandName: String, timeout: Int): Process { + println(commandName) + val connectFuture = checkNotNull(connectFuture) + val session = connectFuture.clientSession + session.auth().verify() + + val process = GProcess() + process.setup(session, commandName) + return process + } + + override fun disconnect() { + client.close() + } + + fun setup(uri: URIish) { + client.open() + + val port = if (uri.port == -1) { + DEFAULT_SSH_PORT + } else + uri.port + + val connectFuture = client.connect(uri.user, uri.host, port) + connectFuture.await() + + this.connectFuture = connectFuture + } +} \ No newline at end of file diff --git a/src/main/kotlin/credentials/GSessionManager.kt b/src/main/kotlin/credentials/GSessionManager.kt new file mode 100644 index 0000000..fa66a25 --- /dev/null +++ b/src/main/kotlin/credentials/GSessionManager.kt @@ -0,0 +1,29 @@ +package credentials + +import org.eclipse.jgit.transport.CredentialsProvider +import org.eclipse.jgit.transport.RemoteSession +import org.eclipse.jgit.transport.SshSessionFactory +import org.eclipse.jgit.transport.URIish +import org.eclipse.jgit.util.FS + +class GSessionManager { + fun generateSshSessionFactory(): SshSessionFactory { + return object : SshSessionFactory() { + override fun getSession( + uri: URIish, + credentialsProvider: CredentialsProvider?, + fs: FS?, + tms: Int + ): RemoteSession { + val remoteSession = GRemoteSession() + remoteSession.setup(uri) + return remoteSession + } + + override fun getType(): String { + return "ssh" //TODO What should be the value of this? + } + + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/git/CloneManager.kt b/src/main/kotlin/git/CloneManager.kt new file mode 100644 index 0000000..e1d2c26 --- /dev/null +++ b/src/main/kotlin/git/CloneManager.kt @@ -0,0 +1,56 @@ +package git + +import credentials.GRemoteSession +import credentials.GSessionManager +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.transport.* +import org.eclipse.jgit.util.FS +import java.io.File +import java.io.IOException + +private const val REMOTE_URL = "" + +class CloneManager { + private val sessionManager = GSessionManager() + + + fun cloneTest() { + // prepare a new folder for the cloned repository + + // prepare a new folder for the cloned repository + val localPath = File.createTempFile("TestGitRepository", "") + if (!localPath.delete()) { + throw IOException("Could not delete temporary file $localPath") + } + + Git.cloneRepository() + .setURI(REMOTE_URL) + .setDirectory(localPath) + .setTransportConfigCallback { + if (it is SshTransport) { + it.sshSessionFactory = sessionManager.generateSshSessionFactory() + } else if (it is HttpTransport) { + it.credentialsProvider = object : CredentialsProvider() { + override fun isInteractive(): Boolean { + return true + } + + override fun supports(vararg items: CredentialItem?): Boolean { + println(items) + + return true + } + + override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean { + return true + } + + } + } + } + .call().use { result -> + // Note: the call() returns an opened repository already which needs to be closed to avoid file handle leaks! + println("Having repository: " + result.repository.directory) + } + } +} \ No newline at end of file