parent
96cbdba8d9
commit
7de332be87
10 changed files with 156 additions and 10 deletions
|
@ -24,12 +24,15 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
val jgit = "6.4.0.202211300538-r"
|
||||
|
||||
implementation(compose.desktop.currentOs)
|
||||
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
|
||||
implementation(compose.desktop.components.splitPane)
|
||||
implementation(compose("org.jetbrains.compose.ui:ui-util"))
|
||||
implementation(compose("org.jetbrains.compose.components:components-animatedimage"))
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.4.0.202211300538-r")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:$jgit")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit.gpg.bc:$jgit")
|
||||
implementation("org.apache.sshd:sshd-core:2.9.0")
|
||||
implementation("com.google.dagger:dagger:2.44.2")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
||||
|
|
|
@ -24,9 +24,11 @@ sealed class CredentialsState {
|
|||
object None : CredentialsState()
|
||||
sealed class CredentialsRequested : CredentialsState()
|
||||
object SshCredentialsRequested : CredentialsRequested()
|
||||
object GpgCredentialsRequested : CredentialsRequested()
|
||||
object HttpCredentialsRequested : CredentialsRequested()
|
||||
object CredentialsDenied : CredentialsState()
|
||||
sealed class CredentialsAccepted : CredentialsState()
|
||||
data class SshCredentialsAccepted(val password: String) : CredentialsAccepted()
|
||||
data class GpgCredentialsAccepted(val password: String) : CredentialsAccepted()
|
||||
data class HttpCredentialsAccepted(val user: String, val password: String) : CredentialsAccepted()
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.jetpackduba.gitnuro.credentials
|
||||
|
||||
import org.eclipse.jgit.transport.CredentialItem
|
||||
import org.eclipse.jgit.transport.CredentialsProvider
|
||||
import org.eclipse.jgit.transport.URIish
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PASSWORD_FIELD_IDENTIFIER = "Passphrase"
|
||||
|
||||
class GpgCredentialsProvider @Inject constructor(
|
||||
private val credentialsStateManager: CredentialsStateManager,
|
||||
) : CredentialsProvider() {
|
||||
override fun isInteractive(): Boolean = true
|
||||
|
||||
override fun supports(vararg items: CredentialItem?): Boolean {
|
||||
println(items)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean {
|
||||
val item = items.firstOrNull {
|
||||
it?.promptText == PASSWORD_FIELD_IDENTIFIER
|
||||
}
|
||||
|
||||
if (item != null && item is CredentialItem.Password) {
|
||||
credentialsStateManager.updateState(CredentialsState.GpgCredentialsRequested)
|
||||
|
||||
var credentials = credentialsStateManager.currentCredentialsState
|
||||
|
||||
while (credentials is CredentialsState.GpgCredentialsRequested) {
|
||||
credentials = credentialsStateManager.currentCredentialsState
|
||||
}
|
||||
|
||||
if (credentials is CredentialsState.GpgCredentialsAccepted) {
|
||||
item.value = credentials.password.toCharArray()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +1,24 @@
|
|||
package com.jetpackduba.gitnuro.git.workspace
|
||||
|
||||
import com.jetpackduba.gitnuro.credentials.GpgCredentialsProvider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import org.eclipse.jgit.transport.CredentialItem
|
||||
import org.eclipse.jgit.transport.CredentialsProvider
|
||||
import org.eclipse.jgit.transport.URIish
|
||||
import javax.inject.Inject
|
||||
|
||||
class DoCommitUseCase @Inject constructor() {
|
||||
class DoCommitUseCase @Inject constructor(
|
||||
private val gpgCredentialsProvider: GpgCredentialsProvider,
|
||||
) {
|
||||
suspend operator fun invoke(git: Git, message: String, amend: Boolean): RevCommit = withContext(Dispatchers.IO) {
|
||||
git.commit()
|
||||
.setMessage(message)
|
||||
.setAllowEmpty(amend) // Only allow empty commits when amending
|
||||
.setAmend(amend)
|
||||
.setCredentialsProvider(gpgCredentialsProvider)
|
||||
.call()
|
||||
}
|
||||
}
|
|
@ -22,9 +22,7 @@ import androidx.compose.ui.unit.sp
|
|||
import com.jetpackduba.gitnuro.LoadingRepository
|
||||
import com.jetpackduba.gitnuro.LocalTabScope
|
||||
import com.jetpackduba.gitnuro.credentials.CredentialsState
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.CloneDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.PasswordDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.UserPasswordDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.settings.SettingsDialog
|
||||
import com.jetpackduba.gitnuro.viewmodels.RepositorySelectionStatus
|
||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
||||
|
@ -191,7 +189,7 @@ fun CredentialsDialog(gitManager: TabViewModel) {
|
|||
}
|
||||
)
|
||||
} else if (credentialsState == CredentialsState.SshCredentialsRequested) {
|
||||
PasswordDialog(
|
||||
SshPasswordDialog(
|
||||
onReject = {
|
||||
gitManager.credentialsDenied()
|
||||
},
|
||||
|
@ -199,5 +197,14 @@ fun CredentialsDialog(gitManager: TabViewModel) {
|
|||
gitManager.sshCredentialsAccepted(password)
|
||||
}
|
||||
)
|
||||
} else if (credentialsState == CredentialsState.GpgCredentialsRequested) {
|
||||
GpgPasswordDialog(
|
||||
onReject = {
|
||||
gitManager.credentialsDenied()
|
||||
},
|
||||
onAccept = { password ->
|
||||
gitManager.gpgCredentialsAccepted(password)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.jetpackduba.gitnuro.ui.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||
|
||||
@Composable
|
||||
fun GpgPasswordDialog(
|
||||
onReject: () -> Unit,
|
||||
onAccept: (password: String) -> Unit
|
||||
) {
|
||||
PasswordDialog(
|
||||
"Introduce your GPG key's password",
|
||||
"Your GPG key is protected with a password",
|
||||
"key.svg",
|
||||
onReject,
|
||||
onAccept,
|
||||
)
|
||||
}
|
|
@ -25,8 +25,11 @@ import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
|||
|
||||
@Composable
|
||||
fun PasswordDialog(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
icon: String,
|
||||
onReject: () -> Unit,
|
||||
onAccept: (password: String) -> Unit
|
||||
onAccept: (password: String) -> Unit,
|
||||
) {
|
||||
var passwordField by remember { mutableStateOf("") }
|
||||
val passwordFieldFocusRequester = remember { FocusRequester() }
|
||||
|
@ -39,7 +42,7 @@ fun PasswordDialog(
|
|||
) {
|
||||
|
||||
Icon(
|
||||
painterResource("lock.svg"),
|
||||
painterResource(icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
|
@ -48,7 +51,7 @@ fun PasswordDialog(
|
|||
)
|
||||
|
||||
Text(
|
||||
text = "Introduce your SSH key's password",
|
||||
text = title,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
|
@ -56,7 +59,7 @@ fun PasswordDialog(
|
|||
)
|
||||
|
||||
Text(
|
||||
text = "Your SSH key is protected with a password",
|
||||
text = subtitle,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp),
|
||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.jetpackduba.gitnuro.ui.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||
|
||||
@Composable
|
||||
fun SshPasswordDialog(
|
||||
onReject: () -> Unit,
|
||||
onAccept: (password: String) -> Unit
|
||||
) {
|
||||
PasswordDialog(
|
||||
"Introduce your SSH key's password",
|
||||
"Your SSH key is protected with a password",
|
||||
"lock.svg",
|
||||
onReject,
|
||||
onAccept,
|
||||
)
|
||||
}
|
|
@ -441,6 +441,10 @@ class TabViewModel @Inject constructor(
|
|||
abortRebaseUseCase(git)
|
||||
rebaseInteractiveViewModel = null // shouldn't be necessary but just to make sure
|
||||
}
|
||||
|
||||
fun gpgCredentialsAccepted(password: String) {
|
||||
credentialsStateManager.updateState(CredentialsState.GpgCredentialsAccepted(password))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
1
src/main/resources/key.svg
Normal file
1
src/main/resources/key.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"/></g></svg>
|
After Width: | Height: | Size: 404 B |
Loading…
Reference in a new issue