Compare commits
1 Commits
main
...
jgit_7.0.0
Author | SHA1 | Date | |
---|---|---|---|
|
4c42f796ce |
78
.github/workflows/release.yml
vendored
78
.github/workflows/release.yml
vendored
@ -124,42 +124,42 @@ jobs:
|
|||||||
Output/Gitnuro*.zip
|
Output/Gitnuro*.zip
|
||||||
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||||
|
|
||||||
# build_macos:
|
build_macos:
|
||||||
# runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
# steps:
|
steps:
|
||||||
# - uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
# - uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
# with:
|
with:
|
||||||
# toolchain: nightly
|
toolchain: nightly
|
||||||
# - run: cargo install cargo-kotars --git https://github.com/JetpackDuba/kotars
|
- run: cargo install cargo-kotars --git https://github.com/JetpackDuba/kotars
|
||||||
# - name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
# uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
# with:
|
with:
|
||||||
# java-version: '17'
|
java-version: '17'
|
||||||
# distribution: 'corretto'
|
distribution: 'corretto'
|
||||||
# architecture: x64
|
architecture: x64
|
||||||
# - name: Build with Gradle
|
- name: Build with Gradle
|
||||||
# uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
||||||
# with:
|
with:
|
||||||
# arguments: createDistributable
|
arguments: createDistributable
|
||||||
# - name: Create output directory
|
- name: Create output directory
|
||||||
# run: mkdir Output
|
run: mkdir Output
|
||||||
# - name: MacOS DMG
|
- name: MacOS DMG
|
||||||
# working-directory: build/compose/binaries/main/app/
|
working-directory: build/compose/binaries/main/app/
|
||||||
# run: zip -r ../../../../../Output/Gitnuro_macos_${{github.ref_name}}.zip .
|
run: zip -r ../../../../../Output/Gitnuro_macos_${{github.ref_name}}.zip .
|
||||||
# - name: Generate SHA256 Checksum
|
- name: Generate SHA256 Checksum
|
||||||
# working-directory: ./Output/
|
working-directory: ./Output/
|
||||||
# run: find . -type f -exec bash -c "shasum -a 256 {} > {}.sum " \;
|
run: find . -type f -exec bash -c "shasum -a 256 {} > {}.sum " \;
|
||||||
# - name: Release
|
- name: Release
|
||||||
# uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
# if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
# with:
|
with:
|
||||||
# body: "Beta release"
|
body: "Beta release"
|
||||||
# prerelease: true
|
prerelease: true
|
||||||
# draft: true
|
draft: true
|
||||||
# repository: JetpackDuba/Gitnuro
|
repository: JetpackDuba/Gitnuro
|
||||||
# with:
|
with:
|
||||||
# files: |
|
files: |
|
||||||
# Output/Gitnuro*.zip
|
Output/Gitnuro*.zip
|
||||||
# Output/Gitnuro*.sum
|
Output/Gitnuro*.sum
|
||||||
# token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
@ -8,8 +8,6 @@
|
|||||||
environment,
|
environment,
|
||||||
please read [its documentation](https://www.rust-lang.org/). `cargo` and `rustc` must be available in the path in
|
please read [its documentation](https://www.rust-lang.org/). `cargo` and `rustc` must be available in the path in
|
||||||
order to build Gitnuro properly.
|
order to build Gitnuro properly.
|
||||||
- **cargo-kotars:** A tool to run autogenerated bindings from Rust to Kotlin. You can install it using
|
|
||||||
`cargo install cargo-kotars --git https://github.com/JetpackDuba/kotars`
|
|
||||||
- **Perl:** Perl is required to build openssl (which is required for LibSSH to work).
|
- **Perl:** Perl is required to build openssl (which is required for LibSSH to work).
|
||||||
- **Packages for Linux ARM64/aarch64**: You need to install the `aarch64-linux-gnu-gcc` package to cross compile the
|
- **Packages for Linux ARM64/aarch64**: You need to install the `aarch64-linux-gnu-gcc` package to cross compile the
|
||||||
Rust components to ARM from x86_64. You will also need to use `rustup` to add a new
|
Rust components to ARM from x86_64. You will also need to use `rustup` to add a new
|
||||||
|
@ -12,17 +12,17 @@ plugins {
|
|||||||
kotlin("jvm") version "2.0.0"
|
kotlin("jvm") version "2.0.0"
|
||||||
kotlin("plugin.serialization") version "2.0.20"
|
kotlin("plugin.serialization") version "2.0.20"
|
||||||
id("com.google.devtools.ksp") version "2.0.20-1.0.24"
|
id("com.google.devtools.ksp") version "2.0.20-1.0.24"
|
||||||
id("org.jetbrains.compose") version "1.7.0"
|
id("org.jetbrains.compose") version "1.7.0-beta02"
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.0.20"
|
id("org.jetbrains.kotlin.plugin.compose") version "2.0.20"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember to update Constants.APP_VERSION when changing this version
|
// Remember to update Constants.APP_VERSION when changing this version
|
||||||
val projectVersion = "1.4.1"
|
val projectVersion = "1.4.0-beta01"
|
||||||
|
|
||||||
val projectName = "Gitnuro"
|
val projectName = "Gitnuro"
|
||||||
|
|
||||||
// Required for JPackage, as it doesn't accept additional suffixes after the version.
|
// Required for JPackage, as it doesn't accept additional suffixes after the version.
|
||||||
val projectVersionSimplified = "1.4.1"
|
val projectVersionSimplified = "1.4.0"
|
||||||
|
|
||||||
val rustGeneratedSource = "${layout.buildDirectory.get()}/generated/source/uniffi/main/com/jetpackduba/gitnuro/java"
|
val rustGeneratedSource = "${layout.buildDirectory.get()}/generated/source/uniffi/main/com/jetpackduba/gitnuro/java"
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val jgit = "6.9.0.202403050737-r"
|
val jgit = "7.0.0.202409031743-r"
|
||||||
|
|
||||||
if (currentOs() == OS.LINUX && isLinuxAarch64) {
|
if (currentOs() == OS.LINUX && isLinuxAarch64) {
|
||||||
implementation(compose.desktop.linux_arm64)
|
implementation(compose.desktop.linux_arm64)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
isLinuxAarch64=false
|
isLinuxAarch64=false
|
||||||
useCross=false
|
useCross=false
|
||||||
isRustRelease=true
|
isRustRelease=false
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"appVersion": "1.4.0",
|
"appVersion": "1.3.1",
|
||||||
"appCode": 14,
|
"appCode": 11,
|
||||||
"downloadUrl": "https://github.com/JetpackDuba/Gitnuro/releases/tag/v1.4.0"
|
"downloadUrl": "https://github.com/JetpackDuba/Gitnuro/releases/tag/v1.3.1"
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.Density
|
import androidx.compose.ui.unit.Density
|
||||||
@ -26,8 +25,6 @@ import com.jetpackduba.gitnuro.di.DaggerAppComponent
|
|||||||
import com.jetpackduba.gitnuro.extensions.preferenceValue
|
import com.jetpackduba.gitnuro.extensions.preferenceValue
|
||||||
import com.jetpackduba.gitnuro.extensions.toWindowPlacement
|
import com.jetpackduba.gitnuro.extensions.toWindowPlacement
|
||||||
import com.jetpackduba.gitnuro.git.AppGpgSigner
|
import com.jetpackduba.gitnuro.git.AppGpgSigner
|
||||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
|
||||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
|
||||||
import com.jetpackduba.gitnuro.logging.printError
|
import com.jetpackduba.gitnuro.logging.printError
|
||||||
import com.jetpackduba.gitnuro.managers.AppStateManager
|
import com.jetpackduba.gitnuro.managers.AppStateManager
|
||||||
import com.jetpackduba.gitnuro.managers.TempFilesManager
|
import com.jetpackduba.gitnuro.managers.TempFilesManager
|
||||||
@ -46,7 +43,8 @@ import com.jetpackduba.gitnuro.ui.components.TabInformation
|
|||||||
import com.jetpackduba.gitnuro.ui.context_menu.AppPopupMenu
|
import com.jetpackduba.gitnuro.ui.context_menu.AppPopupMenu
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.settings.ProxyType
|
import com.jetpackduba.gitnuro.ui.dialogs.settings.ProxyType
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.eclipse.jgit.lib.GpgSigner
|
import org.eclipse.jgit.lib.Signer
|
||||||
|
import org.eclipse.jgit.lib.SignerFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.net.Authenticator
|
import java.net.Authenticator
|
||||||
@ -67,9 +65,6 @@ class App {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var appSettingsRepository: AppSettingsRepository
|
lateinit var appSettingsRepository: AppSettingsRepository
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var appGpgSigner: AppGpgSigner
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appEnvInfo: AppEnvInfo
|
lateinit var appEnvInfo: AppEnvInfo
|
||||||
|
|
||||||
@ -111,8 +106,6 @@ class App {
|
|||||||
|
|
||||||
tabsManager.loadPersistedTabs()
|
tabsManager.loadPersistedTabs()
|
||||||
|
|
||||||
GpgSigner.setDefault(appGpgSigner)
|
|
||||||
|
|
||||||
if (dirToOpen != null)
|
if (dirToOpen != null)
|
||||||
addDirTab(dirToOpen)
|
addDirTab(dirToOpen)
|
||||||
|
|
||||||
@ -274,38 +267,7 @@ class App {
|
|||||||
|
|
||||||
if (currentTab != null) {
|
if (currentTab != null) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.background(MaterialTheme.colors.background)
|
||||||
.background(MaterialTheme.colors.background)
|
|
||||||
.onPreviewKeyEvent {
|
|
||||||
when {
|
|
||||||
it.matchesBinding(KeybindingOption.OPEN_NEW_TAB) -> {
|
|
||||||
tabsManager.addNewEmptyTab()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
it.matchesBinding(KeybindingOption.CLOSE_CURRENT_TAB) -> {
|
|
||||||
tabsManager.closeTab(currentTab)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
it.matchesBinding(KeybindingOption.CHANGE_CURRENT_TAB_LEFT) -> {
|
|
||||||
val tabToSelect = tabs.getOrNull(tabs.indexOf(currentTab) - 1)
|
|
||||||
if (tabToSelect != null) {
|
|
||||||
tabsManager.selectTab(tabToSelect)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
it.matchesBinding(KeybindingOption.CHANGE_CURRENT_TAB_RIGHT) -> {
|
|
||||||
val tabToSelect = tabs.getOrNull(tabs.indexOf(currentTab) + 1)
|
|
||||||
if (tabToSelect != null) {
|
|
||||||
tabsManager.selectTab(tabToSelect)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Tabs(
|
Tabs(
|
||||||
tabsInformationList = tabs,
|
tabsInformationList = tabs,
|
||||||
|
@ -22,8 +22,8 @@ object AppConstants {
|
|||||||
const val APP_NAME = "Gitnuro"
|
const val APP_NAME = "Gitnuro"
|
||||||
const val APP_DESCRIPTION =
|
const val APP_DESCRIPTION =
|
||||||
"Gitnuro is a Git client that allows you to manage multiple repositories with a modern experience and live visual representation of your repositories' state."
|
"Gitnuro is a Git client that allows you to manage multiple repositories with a modern experience and live visual representation of your repositories' state."
|
||||||
const val APP_VERSION = "1.4.1"
|
const val APP_VERSION = "1.4.0-beta01"
|
||||||
const val APP_VERSION_CODE = 15
|
const val APP_VERSION_CODE = 12
|
||||||
const val VERSION_CHECK_URL = "https://raw.githubusercontent.com/JetpackDuba/Gitnuro/main/latest.json"
|
const val VERSION_CHECK_URL = "https://raw.githubusercontent.com/JetpackDuba/Gitnuro/main/latest.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package com.jetpackduba.gitnuro.credentials
|
|||||||
|
|
||||||
import com.jetpackduba.gitnuro.exceptions.NotSupportedHelper
|
import com.jetpackduba.gitnuro.exceptions.NotSupportedHelper
|
||||||
import com.jetpackduba.gitnuro.git.remote_operations.CredentialsCache
|
import com.jetpackduba.gitnuro.git.remote_operations.CredentialsCache
|
||||||
import com.jetpackduba.gitnuro.logging.printError
|
|
||||||
import com.jetpackduba.gitnuro.logging.printLog
|
import com.jetpackduba.gitnuro.logging.printLog
|
||||||
import com.jetpackduba.gitnuro.managers.IShellManager
|
import com.jetpackduba.gitnuro.managers.IShellManager
|
||||||
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
||||||
@ -260,8 +259,7 @@ class HttpCredentialsProvider @AssistedInject constructor(
|
|||||||
val credentialHelperPath = uriSpecificCredentialHelper ?: genericCredentialHelper ?: return null
|
val credentialHelperPath = uriSpecificCredentialHelper ?: genericCredentialHelper ?: return null
|
||||||
|
|
||||||
if (credentialHelperPath == "cache" || credentialHelperPath == "store") {
|
if (credentialHelperPath == "cache" || credentialHelperPath == "store") {
|
||||||
printError(TAG, "Invalid credentials helper: \"$credentialHelperPath\" is not yet supported")
|
throw NotSupportedHelper("Invalid credentials helper: \"$credentialHelperPath\" is not yet supported")
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Try to use "git-credential-manager-core" when "manager-core" is detected. Works for linux but requires testing for mac/windows
|
// TODO Try to use "git-credential-manager-core" when "manager-core" is detected. Works for linux but requires testing for mac/windows
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.jetpackduba.gitnuro.exceptions
|
||||||
|
|
||||||
|
class ConflictsException(message: String) : GitnuroException(message)
|
@ -1,6 +1,11 @@
|
|||||||
package com.jetpackduba.gitnuro.exceptions
|
package com.jetpackduba.gitnuro.exceptions
|
||||||
|
|
||||||
fun codeToMessage(code: Int): String {
|
class WatcherInitException(
|
||||||
|
code: Int,
|
||||||
|
message: String = codeToMessage(code),
|
||||||
|
) : GitnuroException(message)
|
||||||
|
|
||||||
|
private fun codeToMessage(code: Int): String {
|
||||||
return when (code) {
|
return when (code) {
|
||||||
1 /*is WatcherInitException.Generic*/, 2 /*is WatcherInitException.Io*/ -> "Could not watch directory. Check if it exists and you have read permissions."
|
1 /*is WatcherInitException.Generic*/, 2 /*is WatcherInitException.Io*/ -> "Could not watch directory. Check if it exists and you have read permissions."
|
||||||
3 /*is WatcherInitException.PathNotFound*/ -> "Path not found, check if your repository still exists"
|
3 /*is WatcherInitException.PathNotFound*/ -> "Path not found, check if your repository still exists"
|
||||||
|
@ -4,10 +4,7 @@ import com.jetpackduba.gitnuro.credentials.GpgCredentialsProvider
|
|||||||
import org.bouncycastle.openpgp.PGPException
|
import org.bouncycastle.openpgp.PGPException
|
||||||
import org.eclipse.jgit.api.errors.CanceledException
|
import org.eclipse.jgit.api.errors.CanceledException
|
||||||
import org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner
|
import org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner
|
||||||
import org.eclipse.jgit.lib.CommitBuilder
|
import org.eclipse.jgit.lib.*
|
||||||
import org.eclipse.jgit.lib.GpgConfig
|
|
||||||
import org.eclipse.jgit.lib.ObjectBuilder
|
|
||||||
import org.eclipse.jgit.lib.PersonIdent
|
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider
|
import org.eclipse.jgit.transport.CredentialsProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
@ -15,47 +12,57 @@ import javax.inject.Provider
|
|||||||
private const val INVALID_PASSWORD_MESSAGE = "Is the entered passphrase correct?"
|
private const val INVALID_PASSWORD_MESSAGE = "Is the entered passphrase correct?"
|
||||||
|
|
||||||
class AppGpgSigner @Inject constructor(
|
class AppGpgSigner @Inject constructor(
|
||||||
private val gpgCredentialsProvider: Provider<GpgCredentialsProvider>,
|
private val gpgCredentials: GpgCredentialsProvider,
|
||||||
) : BouncyCastleGpgSigner() {
|
) : BouncyCastleGpgSigner() {
|
||||||
|
|
||||||
override fun sign(
|
override fun sign(
|
||||||
commit: CommitBuilder,
|
repository: Repository?,
|
||||||
gpgSigningKey: String,
|
config: GpgConfig?,
|
||||||
committer: PersonIdent,
|
data: ByteArray?,
|
||||||
|
committer: PersonIdent?,
|
||||||
|
signingKey: String?,
|
||||||
credentialsProvider: CredentialsProvider?
|
credentialsProvider: CredentialsProvider?
|
||||||
) {
|
): GpgSignature {
|
||||||
super.sign(commit, gpgSigningKey, committer, gpgCredentialsProvider.get())
|
return try {
|
||||||
|
var gpgSignature: GpgSignature? = null
|
||||||
|
retryIfWrongPassphrase { isRetry ->
|
||||||
|
gpgCredentials.isRetry = isRetry
|
||||||
|
gpgSignature = super.sign(repository, config, data, committer, signingKey, gpgCredentials)
|
||||||
|
gpgCredentials.savePasswordInMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
gpgSignature!!
|
||||||
|
} catch (ex: CanceledException) {
|
||||||
|
println("Signing cancelled")
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canLocateSigningKey(
|
override fun canLocateSigningKey(
|
||||||
gpgSigningKey: String,
|
repository: Repository?,
|
||||||
committer: PersonIdent,
|
config: GpgConfig?,
|
||||||
|
committer: PersonIdent?,
|
||||||
|
signingKey: String?,
|
||||||
credentialsProvider: CredentialsProvider?
|
credentialsProvider: CredentialsProvider?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return super.canLocateSigningKey(gpgSigningKey, committer, gpgCredentialsProvider.get())
|
return super.canLocateSigningKey(repository, config, committer, signingKey, gpgCredentials)
|
||||||
}
|
|
||||||
|
|
||||||
override fun canLocateSigningKey(
|
|
||||||
gpgSigningKey: String,
|
|
||||||
committer: PersonIdent,
|
|
||||||
credentialsProvider: CredentialsProvider?,
|
|
||||||
config: GpgConfig?
|
|
||||||
): Boolean {
|
|
||||||
return super.canLocateSigningKey(gpgSigningKey, committer, gpgCredentialsProvider.get(), config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun signObject(
|
override fun signObject(
|
||||||
`object`: ObjectBuilder,
|
repository: Repository?,
|
||||||
gpgSigningKey: String?,
|
config: GpgConfig?,
|
||||||
committer: PersonIdent,
|
`object`: ObjectBuilder?,
|
||||||
credentialsProvider: CredentialsProvider?,
|
committer: PersonIdent?,
|
||||||
config: GpgConfig?
|
signingKey: String?,
|
||||||
|
credentialsProvider: CredentialsProvider?
|
||||||
) {
|
) {
|
||||||
val gpgCredentialsProvider = gpgCredentialsProvider.get()
|
val gpgCredentialsProvider = gpgCredentials
|
||||||
|
|
||||||
try {
|
try {
|
||||||
retryIfWrongPassphrase { isRetry ->
|
retryIfWrongPassphrase { isRetry ->
|
||||||
gpgCredentialsProvider.isRetry = isRetry
|
gpgCredentialsProvider.isRetry = isRetry
|
||||||
super.signObject(`object`, gpgSigningKey, committer, gpgCredentialsProvider, config)
|
super.signObject(repository, config, `object`, committer, signingKey, credentialsProvider)
|
||||||
gpgCredentialsProvider.savePasswordInMemory()
|
gpgCredentialsProvider.savePasswordInMemory()
|
||||||
}
|
}
|
||||||
} catch (ex: CanceledException) {
|
} catch (ex: CanceledException) {
|
||||||
|
@ -3,6 +3,7 @@ package com.jetpackduba.gitnuro.git
|
|||||||
import FileWatcher
|
import FileWatcher
|
||||||
import WatchDirectoryNotifier
|
import WatchDirectoryNotifier
|
||||||
import com.jetpackduba.gitnuro.di.TabScope
|
import com.jetpackduba.gitnuro.di.TabScope
|
||||||
|
import com.jetpackduba.gitnuro.exceptions.WatcherInitException
|
||||||
import com.jetpackduba.gitnuro.git.workspace.GetIgnoreRulesUseCase
|
import com.jetpackduba.gitnuro.git.workspace.GetIgnoreRulesUseCase
|
||||||
import com.jetpackduba.gitnuro.system.systemSeparator
|
import com.jetpackduba.gitnuro.system.systemSeparator
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -24,8 +25,8 @@ class FileChangesWatcher @Inject constructor(
|
|||||||
private val getIgnoreRulesUseCase: GetIgnoreRulesUseCase,
|
private val getIgnoreRulesUseCase: GetIgnoreRulesUseCase,
|
||||||
private val tabScope: CoroutineScope,
|
private val tabScope: CoroutineScope,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
private val _changesNotifier = MutableSharedFlow<WatcherEvent>()
|
private val _changesNotifier = MutableSharedFlow<Boolean>()
|
||||||
val changesNotifier: SharedFlow<WatcherEvent> = _changesNotifier
|
val changesNotifier: SharedFlow<Boolean> = _changesNotifier
|
||||||
private val fileWatcher = FileWatcher.new()
|
private val fileWatcher = FileWatcher.new()
|
||||||
private var shouldKeepLooping = true
|
private var shouldKeepLooping = true
|
||||||
|
|
||||||
@ -70,16 +71,14 @@ class FileChangesWatcher @Inject constructor(
|
|||||||
|
|
||||||
if (!areAllPathsIgnored) {
|
if (!areAllPathsIgnored) {
|
||||||
println("Emitting changes $hasGitIgnoreChanged")
|
println("Emitting changes $hasGitIgnoreChanged")
|
||||||
_changesNotifier.emit(WatcherEvent.RepositoryChanged(hasGitDirChanged))
|
_changesNotifier.emit(hasGitDirChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(code: Int) {
|
override fun onError(code: Int) {
|
||||||
tabScope.launch {
|
throw WatcherInitException(code)
|
||||||
_changesNotifier.emit(WatcherEvent.WatchInitError(code))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,8 +91,3 @@ class FileChangesWatcher @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface WatcherEvent {
|
|
||||||
data class RepositoryChanged(val hasGitDirChanged: Boolean) : WatcherEvent
|
|
||||||
data class WatchInitError(val code: Int) : WatcherEvent
|
|
||||||
}
|
|
@ -15,12 +15,9 @@ class SaveAuthorUseCase @Inject constructor() {
|
|||||||
repoConfig.load()
|
repoConfig.load()
|
||||||
|
|
||||||
if (globalConfig is FileBasedConfig) {
|
if (globalConfig is FileBasedConfig) {
|
||||||
val canonicalConfigFile = globalConfig.file.canonicalFile
|
globalConfig.setStringProperty("user", null, "name", newAuthorInfo.globalName)
|
||||||
val globalRepoConfig = FileBasedConfig(canonicalConfigFile, git.repository.fs)
|
globalConfig.setStringProperty("user", null, "email", newAuthorInfo.globalEmail)
|
||||||
|
globalConfig.save()
|
||||||
globalRepoConfig.setStringProperty("user", null, "name", newAuthorInfo.globalName)
|
|
||||||
globalRepoConfig.setStringProperty("user", null, "email", newAuthorInfo.globalEmail)
|
|
||||||
globalRepoConfig.save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.setStringProperty("user", null, "name", newAuthorInfo.name)
|
config.setStringProperty("user", null, "name", newAuthorInfo.name)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.jetpackduba.gitnuro.git.branches
|
package com.jetpackduba.gitnuro.git.branches
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.exceptions.ConflictsException
|
||||||
import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException
|
import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -9,23 +9,19 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class SetTrackingBranchUseCase @Inject constructor() {
|
class SetTrackingBranchUseCase @Inject constructor() {
|
||||||
operator fun invoke(git: Git, ref: Ref, remoteName: String?, remoteBranch: Ref?) {
|
operator fun invoke(git: Git, ref: Ref, remoteName: String?, remoteBranch: Ref?) {
|
||||||
invoke(git, ref.simpleName, remoteName, remoteBranch?.simpleName)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun invoke(git: Git, refName: String, remoteName: String?, remoteBranchName: String?) {
|
|
||||||
val repository: Repository = git.repository
|
val repository: Repository = git.repository
|
||||||
val config: StoredConfig = repository.config
|
val config: StoredConfig = repository.config
|
||||||
|
|
||||||
if (remoteName == null || remoteBranchName == null) {
|
if (remoteName == null || remoteBranch == null) {
|
||||||
config.unset("branch", refName, "remote")
|
config.unset("branch", ref.simpleName, "remote")
|
||||||
config.unset("branch", refName, "merge")
|
config.unset("branch", ref.simpleName, "merge")
|
||||||
} else {
|
} else {
|
||||||
config.setString("branch", refName, "remote", remoteName)
|
config.setString("branch", ref.simpleName, "remote", remoteName)
|
||||||
config.setString(
|
config.setString(
|
||||||
"branch",
|
"branch",
|
||||||
refName,
|
ref.simpleName,
|
||||||
"merge",
|
"merge",
|
||||||
BranchesConstants.UPSTREAM_BRANCH_CONFIG_PREFIX + remoteBranchName
|
BranchesConstants.UPSTREAM_BRANCH_CONFIG_PREFIX + remoteBranch.simpleName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
package com.jetpackduba.gitnuro.git.rebase
|
package com.jetpackduba.gitnuro.git.rebase
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.exceptions.ConflictsException
|
||||||
import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException
|
import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.MergeResult
|
||||||
import org.eclipse.jgit.api.RebaseCommand
|
import org.eclipse.jgit.api.RebaseCommand
|
||||||
import org.eclipse.jgit.api.RebaseResult
|
import org.eclipse.jgit.api.RebaseResult
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
typealias IsMultiStep = Boolean
|
|
||||||
|
|
||||||
class RebaseBranchUseCase @Inject constructor() {
|
class RebaseBranchUseCase @Inject constructor() {
|
||||||
suspend operator fun invoke(git: Git, ref: Ref): IsMultiStep = withContext(Dispatchers.IO) {
|
suspend operator fun invoke(git: Git, ref: Ref) = withContext(Dispatchers.IO) {
|
||||||
val rebaseResult = git.rebase()
|
val rebaseResult = git.rebase()
|
||||||
.setOperation(RebaseCommand.Operation.BEGIN)
|
.setOperation(RebaseCommand.Operation.BEGIN)
|
||||||
.setUpstream(ref.objectId)
|
.setUpstream(ref.objectId)
|
||||||
@ -22,10 +22,10 @@ class RebaseBranchUseCase @Inject constructor() {
|
|||||||
throw UncommittedChangesDetectedException("Rebase failed, the repository contains uncommitted changes.")
|
throw UncommittedChangesDetectedException("Rebase failed, the repository contains uncommitted changes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rebaseResult.status == RebaseResult.Status.UNCOMMITTED_CHANGES) {
|
when (rebaseResult.status) {
|
||||||
throw UncommittedChangesDetectedException("Merge failed, makes sure you repository doesn't contain uncommitted changes.")
|
RebaseResult.Status.UNCOMMITTED_CHANGES -> throw UncommittedChangesDetectedException("Merge failed, makes sure you repository doesn't contain uncommitted changes.")
|
||||||
}
|
RebaseResult.Status.CONFLICTS -> throw ConflictsException("Rebase produced conflicts, please fix them to continue.")
|
||||||
|
else -> {}
|
||||||
return@withContext rebaseResult.status == RebaseResult.Status.STOPPED || rebaseResult.status == RebaseResult.Status.CONFLICTS
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ class HandleTransportUseCase @Inject constructor(
|
|||||||
private val sessionManager: GSessionManager,
|
private val sessionManager: GSessionManager,
|
||||||
private val httpCredentialsProvider: HttpCredentialsFactory,
|
private val httpCredentialsProvider: HttpCredentialsFactory,
|
||||||
) {
|
) {
|
||||||
suspend operator fun <R> invoke(git: Git?, block: suspend CredentialsHandler.() -> R): R {
|
suspend operator fun invoke(git: Git?, block: suspend CredentialsHandler.() -> Unit) {
|
||||||
var cache: CredentialsCache? = null
|
var cache: CredentialsCache? = null
|
||||||
|
|
||||||
val credentialsHandler = object : CredentialsHandler {
|
val credentialsHandler = object : CredentialsHandler {
|
||||||
@ -37,10 +37,8 @@ class HandleTransportUseCase @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = credentialsHandler.block()
|
credentialsHandler.block()
|
||||||
cache?.cacheCredentialsIfNeeded()
|
cache?.cacheCredentialsIfNeeded()
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package com.jetpackduba.gitnuro.git.remote_operations
|
|
||||||
|
|
||||||
import org.eclipse.jgit.api.MergeResult
|
|
||||||
import org.eclipse.jgit.api.PullResult
|
|
||||||
import org.eclipse.jgit.api.RebaseResult
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
typealias PullHasConflicts = Boolean
|
|
||||||
|
|
||||||
class HasPullResultConflictsUseCase @Inject constructor() {
|
|
||||||
operator fun invoke(isRebase: Boolean, pullResult: PullResult): PullHasConflicts {
|
|
||||||
if (!pullResult.isSuccessful) {
|
|
||||||
if (
|
|
||||||
pullResult.mergeResult?.mergeStatus == MergeResult.MergeStatus.CONFLICTING ||
|
|
||||||
pullResult.rebaseResult?.status == RebaseResult.Status.CONFLICTS ||
|
|
||||||
pullResult.rebaseResult?.status == RebaseResult.Status.STOPPED
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRebase) {
|
|
||||||
val message = when (pullResult.rebaseResult.status) {
|
|
||||||
RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommitted changes"
|
|
||||||
else -> "Pull failed"
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -4,15 +4,15 @@ import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.RebaseResult
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider
|
import org.eclipse.jgit.transport.CredentialsProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PullBranchUseCase @Inject constructor(
|
class PullBranchUseCase @Inject constructor(
|
||||||
private val handleTransportUseCase: HandleTransportUseCase,
|
private val handleTransportUseCase: HandleTransportUseCase,
|
||||||
private val appSettingsRepository: AppSettingsRepository,
|
private val appSettingsRepository: AppSettingsRepository,
|
||||||
private val hasPullResultConflictsUseCase: HasPullResultConflictsUseCase,
|
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(git: Git, pullType: PullType): PullHasConflicts = withContext(Dispatchers.IO) {
|
suspend operator fun invoke(git: Git, pullType: PullType) = withContext(Dispatchers.IO) {
|
||||||
val pullWithRebase = when (pullType) {
|
val pullWithRebase = when (pullType) {
|
||||||
PullType.REBASE -> true
|
PullType.REBASE -> true
|
||||||
PullType.MERGE -> false
|
PullType.MERGE -> false
|
||||||
@ -27,7 +27,19 @@ class PullBranchUseCase @Inject constructor(
|
|||||||
.setCredentialsProvider(CredentialsProvider.getDefault())
|
.setCredentialsProvider(CredentialsProvider.getDefault())
|
||||||
.call()
|
.call()
|
||||||
|
|
||||||
return@handleTransportUseCase hasPullResultConflictsUseCase(pullWithRebase, pullResult)
|
if (!pullResult.isSuccessful) {
|
||||||
|
var message = "Pull failed"
|
||||||
|
|
||||||
|
if (pullWithRebase) {
|
||||||
|
message = when (pullResult.rebaseResult.status) {
|
||||||
|
RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommitted changes"
|
||||||
|
RebaseResult.Status.CONFLICTS -> "Pull with rebase has conflicts, fix them to continue"
|
||||||
|
else -> message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,34 +2,42 @@ package com.jetpackduba.gitnuro.git.remote_operations
|
|||||||
|
|
||||||
import com.jetpackduba.gitnuro.extensions.remoteName
|
import com.jetpackduba.gitnuro.extensions.remoteName
|
||||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||||
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.RebaseResult
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider
|
import org.eclipse.jgit.transport.CredentialsProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PullFromSpecificBranchUseCase @Inject constructor(
|
class PullFromSpecificBranchUseCase @Inject constructor(
|
||||||
private val handleTransportUseCase: HandleTransportUseCase,
|
private val handleTransportUseCase: HandleTransportUseCase,
|
||||||
private val hasPullResultConflictsUseCase: HasPullResultConflictsUseCase,
|
|
||||||
private val appSettingsRepository: AppSettingsRepository,
|
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(git: Git, remoteBranch: Ref): PullHasConflicts =
|
suspend operator fun invoke(git: Git, rebase: Boolean, remoteBranch: Ref) = withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val pullWithRebase = appSettingsRepository.pullRebase
|
|
||||||
|
|
||||||
handleTransportUseCase(git) {
|
handleTransportUseCase(git) {
|
||||||
val pullResult = git
|
val pullResult = git
|
||||||
.pull()
|
.pull()
|
||||||
.setTransportConfigCallback { handleTransport(it) }
|
.setTransportConfigCallback { handleTransport(it) }
|
||||||
.setRemote(remoteBranch.remoteName)
|
.setRemote(remoteBranch.remoteName)
|
||||||
.setRemoteBranchName(remoteBranch.simpleName)
|
.setRemoteBranchName(remoteBranch.simpleName)
|
||||||
.setRebase(pullWithRebase)
|
.setRebase(rebase)
|
||||||
.setCredentialsProvider(CredentialsProvider.getDefault())
|
.setCredentialsProvider(CredentialsProvider.getDefault())
|
||||||
.call()
|
.call()
|
||||||
|
|
||||||
return@handleTransportUseCase hasPullResultConflictsUseCase(pullWithRebase, pullResult)
|
if (!pullResult.isSuccessful) {
|
||||||
|
var message =
|
||||||
|
"Pull failed" // TODO Remove messages from here and pass the result to a custom exception type
|
||||||
|
|
||||||
|
if (rebase) {
|
||||||
|
message = when (pullResult.rebaseResult.status) {
|
||||||
|
RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommitted changes"
|
||||||
|
RebaseResult.Status.CONFLICTS -> "Pull with rebase has conflicts, fix them to continue"
|
||||||
|
else -> message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package com.jetpackduba.gitnuro.git.remote_operations
|
package com.jetpackduba.gitnuro.git.remote_operations
|
||||||
|
|
||||||
import com.jetpackduba.gitnuro.git.branches.GetTrackingBranchUseCase
|
import com.jetpackduba.gitnuro.git.branches.GetTrackingBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.git.branches.SetTrackingBranchUseCase
|
|
||||||
import com.jetpackduba.gitnuro.git.branches.TrackingBranch
|
import com.jetpackduba.gitnuro.git.branches.TrackingBranch
|
||||||
import com.jetpackduba.gitnuro.git.isRejected
|
import com.jetpackduba.gitnuro.git.isRejected
|
||||||
import com.jetpackduba.gitnuro.git.statusMessage
|
import com.jetpackduba.gitnuro.git.statusMessage
|
||||||
@ -15,37 +14,24 @@ import org.eclipse.jgit.lib.ProgressMonitor
|
|||||||
import org.eclipse.jgit.transport.RefLeaseSpec
|
import org.eclipse.jgit.transport.RefLeaseSpec
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
import org.eclipse.jgit.transport.RefSpec
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class PushBranchUseCase @Inject constructor(
|
class PushBranchUseCase @Inject constructor(
|
||||||
private val handleTransportUseCase: HandleTransportUseCase,
|
private val handleTransportUseCase: HandleTransportUseCase,
|
||||||
private val getTrackingBranchUseCase: GetTrackingBranchUseCase,
|
private val getTrackingBranchUseCase: GetTrackingBranchUseCase,
|
||||||
private val setTrackingBranchUseCase: SetTrackingBranchUseCase,
|
|
||||||
private val appSettingsRepository: AppSettingsRepository,
|
private val appSettingsRepository: AppSettingsRepository,
|
||||||
) {
|
) {
|
||||||
|
// TODO This use case should also set the tracking branch to the new remote branch
|
||||||
suspend operator fun invoke(git: Git, force: Boolean, pushTags: Boolean) = withContext(Dispatchers.IO) {
|
suspend operator fun invoke(git: Git, force: Boolean, pushTags: Boolean) = withContext(Dispatchers.IO) {
|
||||||
val currentBranch = git.repository.branch
|
val currentBranch = git.repository.fullBranch
|
||||||
val fullCurrentBranch = git.repository.fullBranch
|
val tracking = getTrackingBranchUseCase(git, git.repository.branch)
|
||||||
|
|
||||||
val tracking = getTrackingBranchUseCase(git, currentBranch)
|
|
||||||
val refSpecStr = if (tracking != null) {
|
val refSpecStr = if (tracking != null) {
|
||||||
"$fullCurrentBranch:${Constants.R_HEADS}${tracking.branch}"
|
"$currentBranch:${Constants.R_HEADS}${tracking.branch}"
|
||||||
} else {
|
} else {
|
||||||
fullCurrentBranch
|
currentBranch
|
||||||
}
|
}
|
||||||
|
handleTransportUseCase(git) {
|
||||||
val remoteRefUpdate = handleTransportUseCase(git) {
|
|
||||||
push(git, tracking, refSpecStr, force, pushTags)
|
push(git, tracking, refSpecStr, force, pushTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (tracking == null && remoteRefUpdate != null) {
|
|
||||||
// [remoteRefUpdate.trackingRefUpdate.localName] should have the following format: refs/remotes/REMOTE_NAME/BRANCH_NAME
|
|
||||||
val remoteBranchPathSplit = remoteRefUpdate.trackingRefUpdate.localName.split("/")
|
|
||||||
val remoteName = remoteBranchPathSplit.getOrNull(2)
|
|
||||||
val remoteBranchName = remoteBranchPathSplit.takeLast(max(0, remoteBranchPathSplit.count() - 3)).joinToString("/")
|
|
||||||
setTrackingBranchUseCase(git, currentBranch, remoteName, remoteBranchName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun CredentialsHandler.push(
|
private suspend fun CredentialsHandler.push(
|
||||||
@ -131,7 +117,5 @@ class PushBranchUseCase @Inject constructor(
|
|||||||
|
|
||||||
throw Exception(error.toString())
|
throw Exception(error.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
return@withContext pushResult.firstOrNull()?.remoteUpdates?.firstOrNull()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.jetpackduba.gitnuro.git.workspace
|
package com.jetpackduba.gitnuro.git.workspace
|
||||||
|
|
||||||
import com.jetpackduba.gitnuro.extensions.isMerging
|
import com.jetpackduba.gitnuro.extensions.isMerging
|
||||||
|
import com.jetpackduba.gitnuro.git.AppGpgSigner
|
||||||
import com.jetpackduba.gitnuro.git.author.LoadAuthorUseCase
|
import com.jetpackduba.gitnuro.git.author.LoadAuthorUseCase
|
||||||
import com.jetpackduba.gitnuro.git.config.LoadSignOffConfigUseCase
|
import com.jetpackduba.gitnuro.git.config.LoadSignOffConfigUseCase
|
||||||
import com.jetpackduba.gitnuro.git.config.LocalConfigConstants
|
import com.jetpackduba.gitnuro.git.config.LocalConfigConstants
|
||||||
@ -15,7 +16,8 @@ import javax.inject.Inject
|
|||||||
class DoCommitUseCase @Inject constructor(
|
class DoCommitUseCase @Inject constructor(
|
||||||
private val loadSignOffConfigUseCase: LoadSignOffConfigUseCase,
|
private val loadSignOffConfigUseCase: LoadSignOffConfigUseCase,
|
||||||
private val loadAuthorUseCase: LoadAuthorUseCase,
|
private val loadAuthorUseCase: LoadAuthorUseCase,
|
||||||
private val getRepositoryStateUseCase: GetRepositoryStateUseCase
|
private val getRepositoryStateUseCase: GetRepositoryStateUseCase,
|
||||||
|
private val appGpgSigner: AppGpgSigner,
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(
|
||||||
git: Git,
|
git: Git,
|
||||||
@ -23,6 +25,7 @@ class DoCommitUseCase @Inject constructor(
|
|||||||
amend: Boolean,
|
amend: Boolean,
|
||||||
author: PersonIdent?,
|
author: PersonIdent?,
|
||||||
): RevCommit = withContext(Dispatchers.IO) {
|
): RevCommit = withContext(Dispatchers.IO) {
|
||||||
|
|
||||||
val signOffConfig = loadSignOffConfigUseCase(git.repository)
|
val signOffConfig = loadSignOffConfigUseCase(git.repository)
|
||||||
|
|
||||||
val finalMessage = if (signOffConfig.isEnabled) {
|
val finalMessage = if (signOffConfig.isEnabled) {
|
||||||
@ -40,6 +43,7 @@ class DoCommitUseCase @Inject constructor(
|
|||||||
val isMerging = state.isMerging
|
val isMerging = state.isMerging
|
||||||
|
|
||||||
git.commit()
|
git.commit()
|
||||||
|
.setSigner(appGpgSigner)
|
||||||
.setMessage(finalMessage)
|
.setMessage(finalMessage)
|
||||||
.setAllowEmpty(amend || isMerging) // Only allow empty commits when amending
|
.setAllowEmpty(amend || isMerging) // Only allow empty commits when amending
|
||||||
.setAmend(amend)
|
.setAmend(amend)
|
||||||
|
@ -69,29 +69,9 @@ enum class KeybindingOption {
|
|||||||
STASH_POP,
|
STASH_POP,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to open a repository
|
* Used to pop stash changes to workspace
|
||||||
*/
|
*/
|
||||||
OPEN_REPOSITORY,
|
OPEN_ANOTHER_REPOSITORY,
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to open a new tab
|
|
||||||
*/
|
|
||||||
OPEN_NEW_TAB,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to close current tab
|
|
||||||
*/
|
|
||||||
CLOSE_CURRENT_TAB,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to change current tab to the one in the left
|
|
||||||
*/
|
|
||||||
CHANGE_CURRENT_TAB_LEFT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to change current tab to the one in the right
|
|
||||||
*/
|
|
||||||
CHANGE_CURRENT_TAB_RIGHT,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -131,21 +111,9 @@ private fun baseKeybindings() = mapOf(
|
|||||||
KeybindingOption.STASH_POP to listOf(
|
KeybindingOption.STASH_POP to listOf(
|
||||||
Keybinding(key = Key.S, control = true, shift = true),
|
Keybinding(key = Key.S, control = true, shift = true),
|
||||||
),
|
),
|
||||||
KeybindingOption.OPEN_REPOSITORY to listOf(
|
KeybindingOption.OPEN_ANOTHER_REPOSITORY to listOf(
|
||||||
Keybinding(key = Key.O, control = true),
|
Keybinding(key = Key.O, control = true),
|
||||||
),
|
),
|
||||||
KeybindingOption.OPEN_NEW_TAB to listOf(
|
|
||||||
Keybinding(key = Key.T, control = true),
|
|
||||||
),
|
|
||||||
KeybindingOption.CLOSE_CURRENT_TAB to listOf(
|
|
||||||
Keybinding(key = Key.W, control = true),
|
|
||||||
),
|
|
||||||
KeybindingOption.CHANGE_CURRENT_TAB_LEFT to listOf(
|
|
||||||
Keybinding(key = Key.DirectionLeft, alt = true),
|
|
||||||
),
|
|
||||||
KeybindingOption.CHANGE_CURRENT_TAB_RIGHT to listOf(
|
|
||||||
Keybinding(key = Key.DirectionRight, alt = true),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun linuxKeybindings(): Map<KeybindingOption, List<Keybinding>> = baseKeybindings()
|
private fun linuxKeybindings(): Map<KeybindingOption, List<Keybinding>> = baseKeybindings()
|
||||||
|
@ -94,7 +94,7 @@ fun AppTab(
|
|||||||
|
|
||||||
is RepositorySelectionStatus.Open -> {
|
is RepositorySelectionStatus.Open -> {
|
||||||
RepositoryOpenPage(
|
RepositoryOpenPage(
|
||||||
repositoryOpenViewModel = repositorySelectionStatusValue.viewModel,
|
repositoryOpenViewModel = tabViewModel.repositoryOpenViewModel,
|
||||||
onShowSettingsDialog = { showSettingsDialog = true },
|
onShowSettingsDialog = { showSettingsDialog = true },
|
||||||
onShowCloneDialog = { showCloneDialog = true },
|
onShowCloneDialog = { showCloneDialog = true },
|
||||||
)
|
)
|
||||||
|
@ -153,7 +153,7 @@ fun RepositoryOpenPage(
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
it.matchesBinding(KeybindingOption.OPEN_REPOSITORY) -> {
|
it.matchesBinding(KeybindingOption.OPEN_ANOTHER_REPOSITORY) -> {
|
||||||
showOpenPopup = true
|
showOpenPopup = true
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,7 @@ fun UncommittedChanges(
|
|||||||
val doCommit = {
|
val doCommit = {
|
||||||
statusViewModel.commit(commitMessage)
|
statusViewModel.commit(commitMessage)
|
||||||
onStagedDiffEntrySelected(null)
|
onStagedDiffEntrySelected(null)
|
||||||
|
setCommitMessage("")
|
||||||
}
|
}
|
||||||
|
|
||||||
val canCommit = commitMessage.isNotEmpty() && stageStateUi.hasStagedFiles
|
val canCommit = commitMessage.isNotEmpty() && stageStateUi.hasStagedFiles
|
||||||
|
@ -25,9 +25,7 @@ import androidx.compose.ui.focus.onFocusChanged
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.*
|
||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
|
||||||
import androidx.compose.ui.input.key.type
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@ -121,24 +119,11 @@ fun WelcomeView(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
var showAdditionalInfo by remember { mutableStateOf(false) }
|
var showAdditionalInfo by remember { mutableStateOf(false) }
|
||||||
val searchFocusRequester = remember { FocusRequester() }
|
|
||||||
val welcomeViewFocusRequester = remember { FocusRequester() }
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.focusable(true)
|
.background(MaterialTheme.colors.surface),
|
||||||
.focusRequester(welcomeViewFocusRequester)
|
|
||||||
.background(MaterialTheme.colors.surface)
|
|
||||||
.onPreviewKeyEvent {
|
|
||||||
when {
|
|
||||||
it.matchesBinding(KeybindingOption.OPEN_REPOSITORY) -> {
|
|
||||||
onOpenRepository()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@ -171,7 +156,6 @@ fun WelcomeView(
|
|||||||
canRepositoriesBeRemoved = true,
|
canRepositoriesBeRemoved = true,
|
||||||
onOpenKnownRepository = onOpenKnownRepository,
|
onOpenKnownRepository = onOpenKnownRepository,
|
||||||
onRemoveRepositoryFromRecent = onRemoveRepositoryFromRecent,
|
onRemoveRepositoryFromRecent = onRemoveRepositoryFromRecent,
|
||||||
searchFieldFocusRequester = searchFocusRequester,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,14 +173,6 @@ fun WelcomeView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(recentlyOpenedRepositories.isEmpty()) {
|
|
||||||
if (recentlyOpenedRepositories.isEmpty()) {
|
|
||||||
welcomeViewFocusRequester.requestFocus()
|
|
||||||
} else {
|
|
||||||
searchFocusRequester.requestFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showAdditionalInfo) {
|
if (showAdditionalInfo) {
|
||||||
AppInfoDialog(
|
AppInfoDialog(
|
||||||
onClose = { showAdditionalInfo = false },
|
onClose = { showAdditionalInfo = false },
|
||||||
@ -311,7 +287,6 @@ fun RecentRepositories(
|
|||||||
canRepositoriesBeRemoved: Boolean,
|
canRepositoriesBeRemoved: Boolean,
|
||||||
onRemoveRepositoryFromRecent: (String) -> Unit,
|
onRemoveRepositoryFromRecent: (String) -> Unit,
|
||||||
onOpenKnownRepository: (String) -> Unit,
|
onOpenKnownRepository: (String) -> Unit,
|
||||||
searchFieldFocusRequester: FocusRequester,
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -332,7 +307,6 @@ fun RecentRepositories(
|
|||||||
canRepositoriesBeRemoved = canRepositoriesBeRemoved,
|
canRepositoriesBeRemoved = canRepositoriesBeRemoved,
|
||||||
onRemoveRepositoryFromRecent = onRemoveRepositoryFromRecent,
|
onRemoveRepositoryFromRecent = onRemoveRepositoryFromRecent,
|
||||||
onOpenKnownRepository = onOpenKnownRepository,
|
onOpenKnownRepository = onOpenKnownRepository,
|
||||||
searchFieldFocusRequester = searchFieldFocusRequester,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,7 +316,7 @@ fun RecentRepositories(
|
|||||||
fun RecentRepositoriesList(
|
fun RecentRepositoriesList(
|
||||||
recentlyOpenedRepositories: List<String>,
|
recentlyOpenedRepositories: List<String>,
|
||||||
canRepositoriesBeRemoved: Boolean,
|
canRepositoriesBeRemoved: Boolean,
|
||||||
searchFieldFocusRequester: FocusRequester,
|
searchFieldFocusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
onRemoveRepositoryFromRecent: (String) -> Unit,
|
onRemoveRepositoryFromRecent: (String) -> Unit,
|
||||||
onOpenKnownRepository: (String) -> Unit,
|
onOpenKnownRepository: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -21,6 +21,7 @@ import androidx.compose.ui.draw.alpha
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.jetpackduba.gitnuro.AppIcons
|
import com.jetpackduba.gitnuro.AppIcons
|
||||||
import com.jetpackduba.gitnuro.di.AppComponent
|
import com.jetpackduba.gitnuro.di.AppComponent
|
||||||
|
@ -32,9 +32,7 @@ fun ErrorDialog(
|
|||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
var showStackTrace by remember { mutableStateOf(false) }
|
var showStackTrace by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
MaterialDialog (
|
MaterialDialog {
|
||||||
onCloseRequested = onAccept,
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(580.dp)
|
.width(580.dp)
|
||||||
|
@ -390,7 +390,7 @@ fun Terminal(settingsViewModel: SettingsViewModel) {
|
|||||||
fun Logs(settingsViewModel: SettingsViewModel) {
|
fun Logs(settingsViewModel: SettingsViewModel) {
|
||||||
SettingButton(
|
SettingButton(
|
||||||
title = "Logs",
|
title = "Logs",
|
||||||
subtitle = "Open the logs folder",
|
subtitle = "View the logs folder",
|
||||||
buttonText = "Open folder",
|
buttonText = "Open folder",
|
||||||
onClick = {
|
onClick = {
|
||||||
settingsViewModel.openLogsFolderInFileExplorer()
|
settingsViewModel.openLogsFolderInFileExplorer()
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package com.jetpackduba.gitnuro.viewmodels
|
|
||||||
|
|
||||||
import com.jetpackduba.gitnuro.models.AuthorInfoSimple
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetAuthorInfoUseCase @Inject constructor() {
|
|
||||||
suspend operator fun invoke(git: Git) = withContext(Dispatchers.IO) {
|
|
||||||
val config = git.repository.config
|
|
||||||
config.load()
|
|
||||||
|
|
||||||
val userName = config.getString("user", null, "name")
|
|
||||||
val email = config.getString("user", null, "email")
|
|
||||||
|
|
||||||
AuthorInfoSimple(userName, email)
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,9 +9,10 @@ import com.jetpackduba.gitnuro.git.remote_operations.PullType
|
|||||||
import com.jetpackduba.gitnuro.git.remote_operations.PushBranchUseCase
|
import com.jetpackduba.gitnuro.git.remote_operations.PushBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.git.stash.PopLastStashUseCase
|
import com.jetpackduba.gitnuro.git.stash.PopLastStashUseCase
|
||||||
import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase
|
import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase
|
||||||
|
import com.jetpackduba.gitnuro.managers.AppStateManager
|
||||||
import com.jetpackduba.gitnuro.models.errorNotification
|
import com.jetpackduba.gitnuro.models.errorNotification
|
||||||
import com.jetpackduba.gitnuro.models.positiveNotification
|
import com.jetpackduba.gitnuro.models.positiveNotification
|
||||||
import com.jetpackduba.gitnuro.models.warningNotification
|
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
||||||
import com.jetpackduba.gitnuro.terminal.OpenRepositoryInTerminalUseCase
|
import com.jetpackduba.gitnuro.terminal.OpenRepositoryInTerminalUseCase
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -33,6 +34,8 @@ class GlobalMenuActionsViewModel @Inject constructor(
|
|||||||
private val popLastStashUseCase: PopLastStashUseCase,
|
private val popLastStashUseCase: PopLastStashUseCase,
|
||||||
private val stashChangesUseCase: StashChangesUseCase,
|
private val stashChangesUseCase: StashChangesUseCase,
|
||||||
private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase,
|
private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase,
|
||||||
|
settings: AppSettingsRepository,
|
||||||
|
appStateManager: AppStateManager,
|
||||||
) : IGlobalMenuActionsViewModel {
|
) : IGlobalMenuActionsViewModel {
|
||||||
override fun pull(pullType: PullType) = tabState.safeProcessing(
|
override fun pull(pullType: PullType) = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
@ -41,12 +44,10 @@ class GlobalMenuActionsViewModel @Inject constructor(
|
|||||||
refreshEvenIfCrashes = true,
|
refreshEvenIfCrashes = true,
|
||||||
taskType = TaskType.PULL,
|
taskType = TaskType.PULL,
|
||||||
) { git ->
|
) { git ->
|
||||||
if (pullBranchUseCase(git, pullType)) {
|
pullBranchUseCase(git, pullType)
|
||||||
warningNotification("Pull produced conflicts, fix them to continue")
|
|
||||||
} else {
|
|
||||||
positiveNotification("Pull completed")
|
positiveNotification("Pull completed")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchAll() = tabState.safeProcessing(
|
override fun fetchAll() = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
|
@ -1,10 +1,25 @@
|
|||||||
package com.jetpackduba.gitnuro.viewmodels
|
package com.jetpackduba.gitnuro.viewmodels
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.TaskType
|
||||||
|
import com.jetpackduba.gitnuro.git.RefreshType
|
||||||
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
|
import com.jetpackduba.gitnuro.git.remote_operations.FetchAllRemotesUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.remote_operations.PullBranchUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.remote_operations.PullType
|
||||||
|
import com.jetpackduba.gitnuro.git.remote_operations.PushBranchUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.stash.PopLastStashUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.workspace.StageUntrackedFileUseCase
|
||||||
import com.jetpackduba.gitnuro.managers.AppStateManager
|
import com.jetpackduba.gitnuro.managers.AppStateManager
|
||||||
|
import com.jetpackduba.gitnuro.models.errorNotification
|
||||||
|
import com.jetpackduba.gitnuro.models.positiveNotification
|
||||||
|
import com.jetpackduba.gitnuro.models.warningNotification
|
||||||
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
||||||
|
import com.jetpackduba.gitnuro.terminal.OpenRepositoryInTerminalUseCase
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MenuViewModel @Inject constructor(
|
class MenuViewModel @Inject constructor(
|
||||||
|
private val tabState: TabState,
|
||||||
private val globalMenuActionsViewModel: GlobalMenuActionsViewModel,
|
private val globalMenuActionsViewModel: GlobalMenuActionsViewModel,
|
||||||
settings: AppSettingsRepository,
|
settings: AppSettingsRepository,
|
||||||
appStateManager: AppStateManager,
|
appStateManager: AppStateManager,
|
||||||
|
@ -2,10 +2,16 @@ package com.jetpackduba.gitnuro.viewmodels
|
|||||||
|
|
||||||
import com.jetpackduba.gitnuro.SharedRepositoryStateManager
|
import com.jetpackduba.gitnuro.SharedRepositoryStateManager
|
||||||
import com.jetpackduba.gitnuro.TaskType
|
import com.jetpackduba.gitnuro.TaskType
|
||||||
import com.jetpackduba.gitnuro.exceptions.codeToMessage
|
import com.jetpackduba.gitnuro.credentials.CredentialsAccepted
|
||||||
|
import com.jetpackduba.gitnuro.credentials.CredentialsState
|
||||||
|
import com.jetpackduba.gitnuro.credentials.CredentialsStateManager
|
||||||
|
import com.jetpackduba.gitnuro.exceptions.WatcherInitException
|
||||||
import com.jetpackduba.gitnuro.git.*
|
import com.jetpackduba.gitnuro.git.*
|
||||||
import com.jetpackduba.gitnuro.git.branches.CreateBranchUseCase
|
import com.jetpackduba.gitnuro.git.branches.CreateBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
|
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
|
||||||
|
import com.jetpackduba.gitnuro.git.repository.InitLocalRepositoryUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.repository.OpenRepositoryUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.repository.OpenSubmoduleRepositoryUseCase
|
||||||
import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase
|
import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase
|
||||||
import com.jetpackduba.gitnuro.git.workspace.StageUntrackedFileUseCase
|
import com.jetpackduba.gitnuro.git.workspace.StageUntrackedFileUseCase
|
||||||
import com.jetpackduba.gitnuro.logging.printDebug
|
import com.jetpackduba.gitnuro.logging.printDebug
|
||||||
@ -25,20 +31,16 @@ import com.jetpackduba.gitnuro.ui.TabsManager
|
|||||||
import com.jetpackduba.gitnuro.ui.VerticalSplitPaneConfig
|
import com.jetpackduba.gitnuro.ui.VerticalSplitPaneConfig
|
||||||
import com.jetpackduba.gitnuro.updates.Update
|
import com.jetpackduba.gitnuro.updates.Update
|
||||||
import com.jetpackduba.gitnuro.updates.UpdatesRepository
|
import com.jetpackduba.gitnuro.updates.UpdatesRepository
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.api.errors.CheckoutConflictException
|
import org.eclipse.jgit.api.errors.CheckoutConflictException
|
||||||
import org.eclipse.jgit.blame.BlameResult
|
import org.eclipse.jgit.blame.BlameResult
|
||||||
|
import org.eclipse.jgit.lib.Repository
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
@ -59,7 +61,6 @@ class RepositoryOpenViewModel @Inject constructor(
|
|||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
val appStateManager: AppStateManager,
|
val appStateManager: AppStateManager,
|
||||||
private val fileChangesWatcher: FileChangesWatcher,
|
private val fileChangesWatcher: FileChangesWatcher,
|
||||||
private val getAuthorInfoUseCase: GetAuthorInfoUseCase,
|
|
||||||
private val createBranchUseCase: CreateBranchUseCase,
|
private val createBranchUseCase: CreateBranchUseCase,
|
||||||
private val stashChangesUseCase: StashChangesUseCase,
|
private val stashChangesUseCase: StashChangesUseCase,
|
||||||
private val stageUntrackedFileUseCase: StageUntrackedFileUseCase,
|
private val stageUntrackedFileUseCase: StageUntrackedFileUseCase,
|
||||||
@ -110,8 +111,6 @@ class RepositoryOpenViewModel @Inject constructor(
|
|||||||
var authorViewModel: AuthorViewModel? = null
|
var authorViewModel: AuthorViewModel? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private var hasGitDirChanged = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
tabScope.run {
|
tabScope.run {
|
||||||
launch {
|
launch {
|
||||||
@ -121,7 +120,7 @@ class RepositoryOpenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
watchRepositoryChanges()
|
watchRepositoryChanges(tabState.git)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,8 +138,13 @@ class RepositoryOpenViewModel @Inject constructor(
|
|||||||
tabsManager.addNewTabFromPath(directory, true, getWorkspacePathUseCase(git))
|
tabsManager.addNewTabFromPath(directory, true, getWorkspacePathUseCase(git))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadAuthorInfo(git: Git) {
|
private fun loadAuthorInfo(git: Git) {
|
||||||
_authorInfoSimple.value = getAuthorInfoUseCase(git)
|
val config = git.repository.config
|
||||||
|
config.load()
|
||||||
|
val userName = config.getString("user", null, "name")
|
||||||
|
val email = config.getString("user", null, "email")
|
||||||
|
|
||||||
|
_authorInfoSimple.value = AuthorInfoSimple(userName, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showAuthorInfoDialog() {
|
fun showAuthorInfoDialog() {
|
||||||
@ -160,27 +164,11 @@ class RepositoryOpenViewModel @Inject constructor(
|
|||||||
* the app by constantly running "git status" or even full refreshes.
|
* the app by constantly running "git status" or even full refreshes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private suspend fun watchRepositoryChanges() = tabScope.launch(Dispatchers.IO) {
|
private suspend fun watchRepositoryChanges(git: Git) = tabScope.launch(Dispatchers.IO) {
|
||||||
|
var hasGitDirChanged = false
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
fileChangesWatcher.changesNotifier.collect { watcherEvent ->
|
fileChangesWatcher.changesNotifier.collect { latestUpdateChangedGitDir ->
|
||||||
when (watcherEvent) {
|
|
||||||
is WatcherEvent.RepositoryChanged -> repositoryChanged(watcherEvent.hasGitDirChanged)
|
|
||||||
is WatcherEvent.WatchInitError -> {
|
|
||||||
val message = codeToMessage(watcherEvent.code)
|
|
||||||
errorsManager.addError(
|
|
||||||
newErrorNow(
|
|
||||||
exception = Exception(message),
|
|
||||||
taskType = TaskType.CHANGES_DETECTION,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun CoroutineScope.repositoryChanged(hasGitDirChanged: Boolean) {
|
|
||||||
val isOperationRunning = tabState.operationRunning
|
val isOperationRunning = tabState.operationRunning
|
||||||
|
|
||||||
if (!isOperationRunning) { // Only update if there isn't any process running
|
if (!isOperationRunning) { // Only update if there isn't any process running
|
||||||
@ -189,26 +177,44 @@ class RepositoryOpenViewModel @Inject constructor(
|
|||||||
val currentTimeMillis = System.currentTimeMillis()
|
val currentTimeMillis = System.currentTimeMillis()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasGitDirChanged &&
|
latestUpdateChangedGitDir &&
|
||||||
currentTimeMillis - tabState.lastOperation < MIN_TIME_AFTER_GIT_OPERATION
|
currentTimeMillis - tabState.lastOperation < MIN_TIME_AFTER_GIT_OPERATION
|
||||||
) {
|
) {
|
||||||
printDebug(TAG, "Git operation was executed recently, ignoring file system change")
|
printDebug(TAG, "Git operation was executed recently, ignoring file system change")
|
||||||
return
|
return@collect
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasGitDirChanged) {
|
if (latestUpdateChangedGitDir) {
|
||||||
this@RepositoryOpenViewModel.hasGitDirChanged = true
|
hasGitDirChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
updateApp(hasGitDirChanged)
|
updateApp(hasGitDirChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
this@RepositoryOpenViewModel.hasGitDirChanged = false
|
hasGitDirChanged = false
|
||||||
} else {
|
} else {
|
||||||
printDebug(TAG, "Ignored file events during operation")
|
printDebug(TAG, "Ignored file events during operation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileChangesWatcher.watchDirectoryPath(
|
||||||
|
repository = git.repository,
|
||||||
|
)
|
||||||
|
} catch (ex: WatcherInitException) {
|
||||||
|
val message = ex.message
|
||||||
|
if (message != null) {
|
||||||
|
errorsManager.addError(
|
||||||
|
newErrorNow(
|
||||||
|
exception = ex,
|
||||||
|
taskType = TaskType.CHANGES_DETECTION,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun updateApp(hasGitDirChanged: Boolean) {
|
private suspend fun updateApp(hasGitDirChanged: Boolean) {
|
||||||
if (hasGitDirChanged) {
|
if (hasGitDirChanged) {
|
||||||
|
@ -74,10 +74,8 @@ class SharedBranchesViewModel @Inject constructor(
|
|||||||
taskType = TaskType.REBASE_BRANCH,
|
taskType = TaskType.REBASE_BRANCH,
|
||||||
refreshEvenIfCrashes = true,
|
refreshEvenIfCrashes = true,
|
||||||
) { git ->
|
) { git ->
|
||||||
if (rebaseBranchUseCase(git, ref)) {
|
rebaseBranchUseCase(git, ref)
|
||||||
warningNotification("Rebase produced conflicts, please fix them to continue.")
|
|
||||||
} else {
|
|
||||||
positiveNotification("\"${ref.simpleName}\" rebased")
|
positiveNotification("\"${ref.simpleName}\" rebased")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -9,7 +9,6 @@ import com.jetpackduba.gitnuro.git.remote_operations.DeleteRemoteBranchUseCase
|
|||||||
import com.jetpackduba.gitnuro.git.remote_operations.PullFromSpecificBranchUseCase
|
import com.jetpackduba.gitnuro.git.remote_operations.PullFromSpecificBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase
|
import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.models.positiveNotification
|
import com.jetpackduba.gitnuro.models.positiveNotification
|
||||||
import com.jetpackduba.gitnuro.models.warningNotification
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -70,10 +69,12 @@ class SharedRemotesViewModel @Inject constructor(
|
|||||||
subtitle = "Pulling changes from ${branch.simpleName} to the current branch",
|
subtitle = "Pulling changes from ${branch.simpleName} to the current branch",
|
||||||
taskType = TaskType.PULL_FROM_BRANCH,
|
taskType = TaskType.PULL_FROM_BRANCH,
|
||||||
) { git ->
|
) { git ->
|
||||||
if (pullFromSpecificBranchUseCase(git = git, remoteBranch = branch)) {
|
pullFromSpecificBranchUseCase(
|
||||||
warningNotification("Pull produced conflicts, fix them to continue")
|
git = git,
|
||||||
} else {
|
rebase = false,
|
||||||
|
remoteBranch = branch,
|
||||||
|
)
|
||||||
|
|
||||||
positiveNotification("Pulled from \"${branch.simpleName}\"")
|
positiveNotification("Pulled from \"${branch.simpleName}\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -395,9 +395,7 @@ class StatusViewModel @Inject constructor(
|
|||||||
val personIdent = getPersonIdent(git)
|
val personIdent = getPersonIdent(git)
|
||||||
|
|
||||||
doCommitUseCase(git, commitMessage, amend, personIdent)
|
doCommitUseCase(git, commitMessage, amend, personIdent)
|
||||||
|
|
||||||
updateCommitMessage("")
|
updateCommitMessage("")
|
||||||
_commitMessageChangesFlow.emit("")
|
|
||||||
_isAmend.value = false
|
_isAmend.value = false
|
||||||
|
|
||||||
positiveNotification(if (isAmend.value) "Commit amended" else "New commit created")
|
positiveNotification(if (isAmend.value) "Commit amended" else "New commit created")
|
||||||
|
@ -1,37 +1,45 @@
|
|||||||
package com.jetpackduba.gitnuro.viewmodels
|
package com.jetpackduba.gitnuro.viewmodels
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.SharedRepositoryStateManager
|
||||||
import com.jetpackduba.gitnuro.TaskType
|
import com.jetpackduba.gitnuro.TaskType
|
||||||
import com.jetpackduba.gitnuro.credentials.CredentialsAccepted
|
import com.jetpackduba.gitnuro.credentials.CredentialsAccepted
|
||||||
import com.jetpackduba.gitnuro.credentials.CredentialsState
|
import com.jetpackduba.gitnuro.credentials.CredentialsState
|
||||||
import com.jetpackduba.gitnuro.credentials.CredentialsStateManager
|
import com.jetpackduba.gitnuro.credentials.CredentialsStateManager
|
||||||
import com.jetpackduba.gitnuro.git.FileChangesWatcher
|
import com.jetpackduba.gitnuro.exceptions.WatcherInitException
|
||||||
import com.jetpackduba.gitnuro.git.ProcessingState
|
import com.jetpackduba.gitnuro.git.*
|
||||||
import com.jetpackduba.gitnuro.git.RefreshType
|
import com.jetpackduba.gitnuro.git.branches.CreateBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.git.TabState
|
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
|
||||||
import com.jetpackduba.gitnuro.git.repository.InitLocalRepositoryUseCase
|
import com.jetpackduba.gitnuro.git.repository.InitLocalRepositoryUseCase
|
||||||
import com.jetpackduba.gitnuro.git.repository.OpenRepositoryUseCase
|
import com.jetpackduba.gitnuro.git.repository.OpenRepositoryUseCase
|
||||||
import com.jetpackduba.gitnuro.git.repository.OpenSubmoduleRepositoryUseCase
|
import com.jetpackduba.gitnuro.git.repository.OpenSubmoduleRepositoryUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.workspace.StageUntrackedFileUseCase
|
||||||
|
import com.jetpackduba.gitnuro.logging.printDebug
|
||||||
import com.jetpackduba.gitnuro.logging.printLog
|
import com.jetpackduba.gitnuro.logging.printLog
|
||||||
import com.jetpackduba.gitnuro.managers.AppStateManager
|
import com.jetpackduba.gitnuro.managers.AppStateManager
|
||||||
import com.jetpackduba.gitnuro.managers.ErrorsManager
|
import com.jetpackduba.gitnuro.managers.ErrorsManager
|
||||||
import com.jetpackduba.gitnuro.managers.newErrorNow
|
import com.jetpackduba.gitnuro.managers.newErrorNow
|
||||||
|
import com.jetpackduba.gitnuro.models.AuthorInfoSimple
|
||||||
|
import com.jetpackduba.gitnuro.models.errorNotification
|
||||||
|
import com.jetpackduba.gitnuro.models.positiveNotification
|
||||||
import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase
|
import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase
|
||||||
import com.jetpackduba.gitnuro.system.OpenUrlInBrowserUseCase
|
import com.jetpackduba.gitnuro.system.OpenUrlInBrowserUseCase
|
||||||
import com.jetpackduba.gitnuro.system.PickerType
|
import com.jetpackduba.gitnuro.system.PickerType
|
||||||
import com.jetpackduba.gitnuro.ui.IVerticalSplitPaneConfig
|
import com.jetpackduba.gitnuro.ui.IVerticalSplitPaneConfig
|
||||||
import com.jetpackduba.gitnuro.ui.SelectedItem
|
import com.jetpackduba.gitnuro.ui.SelectedItem
|
||||||
|
import com.jetpackduba.gitnuro.ui.TabsManager
|
||||||
import com.jetpackduba.gitnuro.ui.VerticalSplitPaneConfig
|
import com.jetpackduba.gitnuro.ui.VerticalSplitPaneConfig
|
||||||
import com.jetpackduba.gitnuro.updates.Update
|
import com.jetpackduba.gitnuro.updates.Update
|
||||||
import com.jetpackduba.gitnuro.updates.UpdatesRepository
|
import com.jetpackduba.gitnuro.updates.UpdatesRepository
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.errors.CheckoutConflictException
|
||||||
|
import org.eclipse.jgit.blame.BlameResult
|
||||||
import org.eclipse.jgit.lib.Repository
|
import org.eclipse.jgit.lib.Repository
|
||||||
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
import java.awt.Desktop
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
@ -67,6 +75,8 @@ class TabViewModel @Inject constructor(
|
|||||||
val errorsManager: ErrorsManager = tabState.errorsManager
|
val errorsManager: ErrorsManager = tabState.errorsManager
|
||||||
val selectedItem: StateFlow<SelectedItem> = tabState.selectedItem
|
val selectedItem: StateFlow<SelectedItem> = tabState.selectedItem
|
||||||
|
|
||||||
|
val repositoryOpenViewModel: RepositoryOpenViewModel = repositoryOpenViewModelProvider.get()
|
||||||
|
|
||||||
private val _repositorySelectionStatus = MutableStateFlow<RepositorySelectionStatus>(RepositorySelectionStatus.None)
|
private val _repositorySelectionStatus = MutableStateFlow<RepositorySelectionStatus>(RepositorySelectionStatus.None)
|
||||||
val repositorySelectionStatus: StateFlow<RepositorySelectionStatus>
|
val repositorySelectionStatus: StateFlow<RepositorySelectionStatus>
|
||||||
get() = _repositorySelectionStatus
|
get() = _repositorySelectionStatus
|
||||||
@ -105,6 +115,7 @@ class TabViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
repository.workTree // test if repository is valid
|
repository.workTree // test if repository is valid
|
||||||
|
_repositorySelectionStatus.value = RepositorySelectionStatus.Open(repository)
|
||||||
|
|
||||||
val path = if (directory.name == ".git") {
|
val path = if (directory.name == ".git") {
|
||||||
directory.parent
|
directory.parent
|
||||||
@ -115,9 +126,6 @@ class TabViewModel @Inject constructor(
|
|||||||
|
|
||||||
val git = Git(repository)
|
val git = Git(repository)
|
||||||
tabState.initGit(git)
|
tabState.initGit(git)
|
||||||
|
|
||||||
_repositorySelectionStatus.value = RepositorySelectionStatus.Open(repositoryOpenViewModelProvider.get())
|
|
||||||
|
|
||||||
tabState.refreshData(RefreshType.ALL_DATA)
|
tabState.refreshData(RefreshType.ALL_DATA)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
onRepositoryChanged(null)
|
onRepositoryChanged(null)
|
||||||
@ -188,5 +196,5 @@ class TabViewModel @Inject constructor(
|
|||||||
sealed class RepositorySelectionStatus {
|
sealed class RepositorySelectionStatus {
|
||||||
data object None : RepositorySelectionStatus()
|
data object None : RepositorySelectionStatus()
|
||||||
data class Opening(val path: String) : RepositorySelectionStatus()
|
data class Opening(val path: String) : RepositorySelectionStatus()
|
||||||
data class Open(val viewModel: RepositoryOpenViewModel) : RepositorySelectionStatus()
|
data class Open(val repository: Repository) : RepositorySelectionStatus()
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user