diff --git a/build.gradle.kts b/build.gradle.kts index 5f5d373..304c63d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,7 +44,7 @@ tasks.withType() { compose.desktop { application { mainClass = "MainKt" - +// nativeDistributions { includeAllModules = true targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.AppImage) diff --git a/src/main/kotlin/app/App.kt b/src/main/kotlin/app/App.kt index eb8fd4a..c0ac860 100644 --- a/src/main/kotlin/app/App.kt +++ b/src/main/kotlin/app/App.kt @@ -24,21 +24,15 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.zIndex import app.di.DaggerAppComponent -import app.git.GitManager import app.theme.AppTheme -import app.ui.AppTab import app.ui.components.RepositoriesTabPanel import app.ui.components.TabInformation import app.ui.dialogs.SettingsDialog import javax.inject.Inject -import javax.inject.Provider class Main { private val appComponent = DaggerAppComponent.create() - @Inject - lateinit var gitManagerProvider: Provider - @Inject lateinit var appStateManager: AppStateManager @@ -47,7 +41,7 @@ class Main { init { appComponent.inject(this) - + println("AppStateManagerReference $appStateManager") appStateManager.loadRepositoriesTabs() } @@ -200,19 +194,11 @@ class Main { ): TabInformation { return TabInformation( - title = tabName, - key = key - ) { - val gitManager = remember { gitManagerProvider.get() } - gitManager.onRepositoryChanged = { path -> - if (path == null) { - appStateManager.repositoryTabRemoved(key) - } else - appStateManager.repositoryTabChanged(key, path) - } - - AppTab(gitManager, path, tabName) - } + tabName = tabName, + key = key, + path = path, + appComponent = appComponent, + ) } } diff --git a/src/main/kotlin/app/di/AppComponent.kt b/src/main/kotlin/app/di/AppComponent.kt index 97578d8..19b6a2c 100644 --- a/src/main/kotlin/app/di/AppComponent.kt +++ b/src/main/kotlin/app/di/AppComponent.kt @@ -1,5 +1,6 @@ package app.di +import app.AppStateManager import app.Main import dagger.Component import javax.inject.Singleton @@ -8,4 +9,5 @@ import javax.inject.Singleton @Component interface AppComponent { fun inject(main: Main) + fun appStateManager(): AppStateManager } \ No newline at end of file diff --git a/src/main/kotlin/app/di/TabComponent.kt b/src/main/kotlin/app/di/TabComponent.kt new file mode 100644 index 0000000..0d7e172 --- /dev/null +++ b/src/main/kotlin/app/di/TabComponent.kt @@ -0,0 +1,10 @@ +package app.di + +import app.ui.components.TabInformation +import dagger.Component + +@TabScope +@Component(dependencies = [ AppComponent::class ]) +interface TabComponent { + fun inject(tabInformation: TabInformation) +} \ No newline at end of file diff --git a/src/main/kotlin/app/di/TabScope.kt b/src/main/kotlin/app/di/TabScope.kt new file mode 100644 index 0000000..0ca01d8 --- /dev/null +++ b/src/main/kotlin/app/di/TabScope.kt @@ -0,0 +1,7 @@ +package app.di + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class TabScope \ No newline at end of file diff --git a/src/main/kotlin/app/git/BranchesManager.kt b/src/main/kotlin/app/git/BranchesManager.kt index 039fa33..757b0f2 100644 --- a/src/main/kotlin/app/git/BranchesManager.kt +++ b/src/main/kotlin/app/git/BranchesManager.kt @@ -1,9 +1,12 @@ package app.git +import app.extensions.isBranch +import app.extensions.simpleName import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.CreateBranchCommand import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.ListBranchCommand import org.eclipse.jgit.api.MergeCommand @@ -32,17 +35,6 @@ class BranchesManager @Inject constructor() { return branchList.firstOrNull { it.name == branchName } } - suspend fun loadBranches(git: Git) = withContext(Dispatchers.IO) { - val branchList = getBranches(git) - - val branchName = git - .repository - .fullBranch - - _branches.value = branchList - _currentBranch.value = branchName - } - suspend fun getBranches(git: Git) = withContext(Dispatchers.IO) { return@withContext git .branchList() @@ -55,8 +47,6 @@ class BranchesManager @Inject constructor() { .setCreateBranch(true) .setName(branchName) .call() - - loadBranches(git) } suspend fun createBranchOnCommit(git: Git, branch: String, revCommit: RevCommit) = withContext(Dispatchers.IO) { @@ -95,4 +85,17 @@ class BranchesManager @Inject constructor() { .setListMode(ListBranchCommand.ListMode.REMOTE) .call() } + + suspend fun checkoutRef(git: Git, ref: Ref) = withContext(Dispatchers.IO) { + git.checkout().apply { + setName(ref.name) + if (ref.isBranch && ref.name.startsWith("refs/remotes/")) { + setCreateBranch(true) + setName(ref.simpleName) + setStartPoint(ref.objectId.name) + setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) + } + call() + } + } } \ No newline at end of file diff --git a/src/main/kotlin/app/git/LogManager.kt b/src/main/kotlin/app/git/LogManager.kt index 9bdb025..9b0aecb 100644 --- a/src/main/kotlin/app/git/LogManager.kt +++ b/src/main/kotlin/app/git/LogManager.kt @@ -21,17 +21,8 @@ import javax.inject.Inject class LogManager @Inject constructor( private val statusManager: StatusManager, - private val branchesManager: BranchesManager, ) { - private val _logStatus = MutableStateFlow(LogStatus.Loading) - - val logStatus: StateFlow - get() = _logStatus - - suspend fun loadLog(git: Git) = withContext(Dispatchers.IO) { - _logStatus.value = LogStatus.Loading - - val currentBranch = branchesManager.currentBranchRef(git) + suspend fun loadLog(git: Git, currentBranch: Ref?) = withContext(Dispatchers.IO) { val commitList = GraphCommitList() val repositoryState = git.repository.repositoryState @@ -45,7 +36,7 @@ class LogManager @Inject constructor( walk.markStartAllRefs(Constants.R_REMOTES) walk.markStartAllRefs(Constants.R_TAGS) - if (statusManager.checkHasUncommitedChanges(git)) + if (statusManager.hasUncommitedChanges(git)) commitList.addUncommitedChangesGraphCommit(logList.first()) commitList.source(walk) @@ -55,9 +46,8 @@ class LogManager @Inject constructor( ensureActive() } - val loadedStatus = LogStatus.Loaded(commitList, currentBranch) - _logStatus.value = loadedStatus + return@withContext commitList } suspend fun checkoutCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) { @@ -67,19 +57,6 @@ class LogManager @Inject constructor( .call() } - suspend fun checkoutRef(git: Git, ref: Ref) = withContext(Dispatchers.IO) { - git.checkout().apply { - setName(ref.name) - if (ref.isBranch && ref.name.startsWith("refs/remotes/")) { - setCreateBranch(true) - setName(ref.simpleName) - setStartPoint(ref.objectId.name) - setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) - } - call() - } - } - suspend fun revertCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) { git .revert() @@ -106,9 +83,4 @@ enum class ResetType { SOFT, MIXED, HARD, -} - -sealed class LogStatus { - object Loading : LogStatus() - class Loaded(val plotCommitList: GraphCommitList, val currentBranch: Ref?) : LogStatus() } \ No newline at end of file diff --git a/src/main/kotlin/app/git/RemotesManager.kt b/src/main/kotlin/app/git/RemotesManager.kt index fc6ef8a..afbe4a9 100644 --- a/src/main/kotlin/app/git/RemotesManager.kt +++ b/src/main/kotlin/app/git/RemotesManager.kt @@ -10,22 +10,18 @@ import org.eclipse.jgit.transport.RemoteConfig import javax.inject.Inject class RemotesManager @Inject constructor() { - private val _remotes = MutableStateFlow>(listOf()) - val remotes: StateFlow> - get() = _remotes + suspend fun loadRemotes(git: Git, allRemoteBranches: List) = withContext(Dispatchers.IO) { val remotes = git.remoteList() .call() - val remoteInfoList = remotes.map { remoteConfig -> + return@withContext remotes.map { remoteConfig -> val remoteBranches = allRemoteBranches.filter { branch -> branch.name.startsWith("refs/remotes/${remoteConfig.name}") } RemoteInfo(remoteConfig, remoteBranches) } - - _remotes.value = remoteInfoList } } diff --git a/src/main/kotlin/app/git/RepositoryManager.kt b/src/main/kotlin/app/git/RepositoryManager.kt new file mode 100644 index 0000000..27df2a9 --- /dev/null +++ b/src/main/kotlin/app/git/RepositoryManager.kt @@ -0,0 +1,12 @@ +package app.git + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import javax.inject.Inject + +class RepositoryManager @Inject constructor() { + suspend fun getRepositoryState(git: Git) = withContext(Dispatchers.IO) { + return@withContext git.repository.repositoryState + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/StatusManager.kt b/src/main/kotlin/app/git/StatusManager.kt index d6d8cee..17a9d11 100644 --- a/src/main/kotlin/app/git/StatusManager.kt +++ b/src/main/kotlin/app/git/StatusManager.kt @@ -12,7 +12,6 @@ import app.git.diff.Hunk import app.git.diff.LineType import app.theme.conflictFile import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext @@ -31,24 +30,9 @@ import javax.inject.Inject class StatusManager @Inject constructor( - private val branchesManager: BranchesManager, private val rawFileManagerFactory: RawFileManagerFactory, ) { - private val _stageStatus = MutableStateFlow(StageStatus.Loaded(listOf(), listOf())) - val stageStatus: StateFlow = _stageStatus - - private val _repositoryState = MutableStateFlow(RepositoryState.SAFE) - val repositoryState: StateFlow = _repositoryState - - private val _hasUncommitedChanges = MutableStateFlow(false) - val hasUncommitedChanges: StateFlow - get() = _hasUncommitedChanges - - suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) { - _hasUncommitedChanges.value = checkHasUncommitedChanges(git) - } - - suspend fun checkHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) { + suspend fun hasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) { val status = git .status() .call() @@ -56,77 +40,6 @@ class StatusManager @Inject constructor( return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges() } - suspend fun loadRepositoryStatus(git: Git) = withContext(Dispatchers.IO) { - _repositoryState.value = git.repository.repositoryState - } - - suspend fun loadStatus(git: Git) = withContext(Dispatchers.IO) { - val previousStatus = _stageStatus.value - _stageStatus.value = StageStatus.Loading - - try { - loadRepositoryStatus(git) - - loadHasUncommitedChanges(git) - val currentBranch = branchesManager.currentBranchRef(git) - val repositoryState = git.repository.repositoryState - - val staged = git - .diff() - .setShowNameAndStatusOnly(true).apply { - if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing) - setOldTree(EmptyTreeIterator()) // Required if the repository is empty - - setCached(true) - } - .call() - // TODO: Grouping and fitlering allows us to remove duplicates when conflicts appear, requires more testing (what happens in windows? /dev/null is a unix thing) - // TODO: Test if we should group by old path or new path - .groupBy { - if(it.newPath != "/dev/null") - it.newPath - else - it.oldPath - } - .map { - val entries = it.value - - val hasConflicts = - (entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing)) - - StatusEntry(entries.first(), isConflict = hasConflicts) - } - - ensureActive() - - val unstaged = git - .diff() - .setShowNameAndStatusOnly(true) - .call() - .groupBy { - if(it.oldPath != "/dev/null") - it.oldPath - else - it.newPath - } - .map { - val entries = it.value - - val hasConflicts = - (entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing)) - - StatusEntry(entries.first(), isConflict = hasConflicts) - } - - ensureActive() - _stageStatus.value = StageStatus.Loaded(staged, unstaged) - } catch (ex: Exception) { - _stageStatus.value = previousStatus - throw ex - } - - } - suspend fun stage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) { if (diffEntry.changeType == DiffEntry.ChangeType.DELETE) { git.rm() @@ -137,8 +50,6 @@ class StatusManager @Inject constructor( .addFilepattern(diffEntry.filePath) .call() } - - loadStatus(git) } suspend fun stageHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) { @@ -175,7 +86,7 @@ class StatusManager @Inject constructor( completedWithErrors = false - loadStatus(git) +// loadStatus(git) } finally { if (completedWithErrors) dirCache.unlock() @@ -226,7 +137,7 @@ class StatusManager @Inject constructor( completedWithErrors = false - loadStatus(git) +// loadStatus(git) } finally { if (completedWithErrors) dirCache.unlock() @@ -271,8 +182,6 @@ class StatusManager @Inject constructor( git.reset() .addPath(diffEntry.filePath) .call() - - loadStatus(git) } suspend fun commit(git: Git, message: String) = withContext(Dispatchers.IO) { @@ -280,8 +189,6 @@ class StatusManager @Inject constructor( .setMessage(message) .setAllowEmpty(false) .call() - - loadStatus(git) } suspend fun reset(git: Git, diffEntry: DiffEntry, staged: Boolean) = withContext(Dispatchers.IO) { @@ -297,15 +204,13 @@ class StatusManager @Inject constructor( .addPath(diffEntry.filePath) .call() - loadStatus(git) +// loadStatus(git) } suspend fun unstageAll(git: Git) = withContext(Dispatchers.IO) { git .reset() .call() - - loadStatus(git) } suspend fun stageAll(git: Git) = withContext(Dispatchers.IO) { @@ -313,15 +218,58 @@ class StatusManager @Inject constructor( .add() .addFilepattern(".") .call() + } - loadStatus(git) + suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) = withContext(Dispatchers.IO) { + return@withContext git + .diff() + .setShowNameAndStatusOnly(true).apply { + if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing) + setOldTree(EmptyTreeIterator()) // Required if the repository is empty + + setCached(true) + } + .call() + // TODO: Grouping and fitlering allows us to remove duplicates when conflicts appear, requires more testing (what happens in windows? /dev/null is a unix thing) + // TODO: Test if we should group by old path or new path + .groupBy { + if(it.newPath != "/dev/null") + it.newPath + else + it.oldPath + } + .map { + val entries = it.value + + val hasConflicts = + (entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing)) + + StatusEntry(entries.first(), isConflict = hasConflicts) + } + } + + suspend fun getUnstaged(git: Git, repositoryState: RepositoryState) = withContext(Dispatchers.IO) { + return@withContext git + .diff() + .setShowNameAndStatusOnly(true) + .call() + .groupBy { + if(it.oldPath != "/dev/null") + it.oldPath + else + it.newPath + } + .map { + val entries = it.value + + val hasConflicts = + (entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing)) + + StatusEntry(entries.first(), isConflict = hasConflicts) + } } } -sealed class StageStatus { - object Loading : StageStatus() - data class Loaded(val staged: List, val unstaged: List) : StageStatus() -} data class StatusEntry(val diffEntry: DiffEntry, val isConflict: Boolean) { val icon: ImageVector diff --git a/src/main/kotlin/app/git/TabState.kt b/src/main/kotlin/app/git/TabState.kt new file mode 100644 index 0000000..6b94497 --- /dev/null +++ b/src/main/kotlin/app/git/TabState.kt @@ -0,0 +1,100 @@ +package app.git + +import app.app.Error +import app.app.newErrorNow +import app.di.TabScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.eclipse.jgit.api.Git +import javax.inject.Inject +import kotlin.coroutines.cancellation.CancellationException + +@TabScope +class TabState @Inject constructor() { + var git: Git? = null + val safeGit: Git + get() { + val git = this.git + if (git == null) { +// _repositorySelectionStatus.value = RepositorySelectionStatus.None + throw CancellationException("Null git object") + } else + return git + } + + val mutex = Mutex() + + private val _refreshData = MutableSharedFlow() + val refreshData: Flow = _refreshData + suspend fun refreshData(refreshType: RefreshType) = _refreshData.emit(refreshType) + + private val _errors = MutableSharedFlow() + val errors: Flow = _errors + val managerScope = CoroutineScope(SupervisorJob()) + + + /** + * Property that indicates if a git operation is running + */ + @set:Synchronized + var operationRunning = false + + + private val _processing = MutableStateFlow(false) + val processing: StateFlow + get() = _processing + + fun safeProcessing(showError: Boolean = true, callback: suspend (git: Git) -> RefreshType) = + managerScope.launch { + mutex.withLock { + _processing.value = true + operationRunning = true + + try { + val refreshType = callback(safeGit) + + if (refreshType != RefreshType.NONE) + _refreshData.emit(refreshType) + } catch (ex: Exception) { + ex.printStackTrace() + + if (showError) + _errors.emit(newErrorNow(ex, ex.localizedMessage)) + } finally { + _processing.value = false + operationRunning = false + } + } + } + + fun runOperation(block: suspend (git: Git) -> RefreshType) = managerScope.launch { + operationRunning = true + try { + val refreshType = block(safeGit) + + if (refreshType != RefreshType.NONE) + _refreshData.emit(refreshType) + } finally { + operationRunning = false + } + } +} + +enum class RefreshType { + NONE, + ALL_DATA, + ONLY_LOG, + + /** + * Requires to update the status if currently selected and update the log if there has been a change + * in the "uncommited changes" state (if there were changes before but not anymore and vice-versa) + */ + UNCOMMITED_CHANGES, +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/TagsManager.kt b/src/main/kotlin/app/git/TagsManager.kt index 41fa547..12646d3 100644 --- a/src/main/kotlin/app/git/TagsManager.kt +++ b/src/main/kotlin/app/git/TagsManager.kt @@ -10,16 +10,8 @@ import org.eclipse.jgit.revwalk.RevCommit import javax.inject.Inject class TagsManager @Inject constructor() { - - private val _tags = MutableStateFlow>(listOf()) - val tags: StateFlow> - get() = _tags - - suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) { - val branchList = git.tagList().call() - - - _tags.value = branchList + suspend fun getTags(git: Git) = withContext(Dispatchers.IO) { + return@withContext git.tagList().call() } suspend fun createTagOnCommit(git: Git, tag: String, revCommit: RevCommit) = withContext(Dispatchers.IO) { diff --git a/src/main/kotlin/app/ui/AppTab.kt b/src/main/kotlin/app/ui/AppTab.kt index 0275e50..b240b9a 100644 --- a/src/main/kotlin/app/ui/AppTab.kt +++ b/src/main/kotlin/app/ui/AppTab.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.LoadingRepository import app.credentials.CredentialsState -import app.git.GitManager +import app.git.TabViewModel import app.git.RepositorySelectionStatus import app.ui.dialogs.PasswordDialog import app.ui.dialogs.UserPasswordDialog @@ -30,7 +30,7 @@ import kotlinx.coroutines.delay @OptIn(ExperimentalAnimationApi::class) @Composable fun AppTab( - gitManager: GitManager, + gitManager: TabViewModel, repositoryPath: String?, tabName: MutableState ) { @@ -101,7 +101,7 @@ fun AppTab( LoadingRepository() } is RepositorySelectionStatus.Open -> { - RepositoryOpenPage(gitManager = gitManager) + RepositoryOpenPage(tabViewModel = gitManager) } } } @@ -158,7 +158,7 @@ fun AppTab( } @Composable -fun CredentialsDialog(gitManager: GitManager) { +fun CredentialsDialog(gitManager: TabViewModel) { val credentialsState by gitManager.credentialsState.collectAsState() if (credentialsState == CredentialsState.HttpCredentialsRequested) { diff --git a/src/main/kotlin/app/ui/Branches.kt b/src/main/kotlin/app/ui/Branches.kt index 82b0822..5ff12d4 100644 --- a/src/main/kotlin/app/ui/Branches.kt +++ b/src/main/kotlin/app/ui/Branches.kt @@ -13,23 +13,23 @@ import androidx.compose.ui.unit.dp import app.MAX_SIDE_PANEL_ITEMS_HEIGHT import app.extensions.isLocal import app.extensions.simpleName -import app.git.GitManager +import app.git.TabViewModel import app.ui.components.ScrollableLazyColumn import app.ui.components.SideMenuEntry import app.ui.components.SideMenuSubentry import app.ui.components.entryHeight import app.ui.context_menu.branchContextMenuItems import app.ui.dialogs.MergeDialog +import app.viewmodels.BranchesViewModel import org.eclipse.jgit.lib.Ref @Composable fun Branches( - gitManager: GitManager, + branchesViewModel: BranchesViewModel, onBranchClicked: (Ref) -> Unit, - - ) { - val branches by gitManager.branches.collectAsState() - val currentBranch by gitManager.currentBranch.collectAsState() +) { + val branches by branchesViewModel.branches.collectAsState() + val currentBranch by branchesViewModel.currentBranch.collectAsState() val (mergeBranch, setMergeBranch) = remember { mutableStateOf(null) } Column { @@ -48,9 +48,9 @@ fun Branches( branch = branch, isCurrentBranch = currentBranch == branch.name, onBranchClicked = { onBranchClicked(branch) }, - onCheckoutBranch = { gitManager.checkoutRef(branch) }, + onCheckoutBranch = { branchesViewModel.checkoutRef(branch) }, onMergeBranch = { setMergeBranch(branch) }, - onDeleteBranch = { gitManager.deleteBranch(branch) }, + onDeleteBranch = { branchesViewModel.deleteBranch(branch) }, ) } } @@ -62,7 +62,7 @@ fun Branches( currentBranch, mergeBranchName = mergeBranch.name, onReject = { setMergeBranch(null) }, - onAccept = { ff -> gitManager.mergeBranch(mergeBranch, ff) } + onAccept = { ff -> branchesViewModel.mergeBranch(mergeBranch, ff) } ) } } diff --git a/src/main/kotlin/app/ui/CommitChanges.kt b/src/main/kotlin/app/ui/CommitChanges.kt index 76144a9..193af5b 100644 --- a/src/main/kotlin/app/ui/CommitChanges.kt +++ b/src/main/kotlin/app/ui/CommitChanges.kt @@ -3,7 +3,6 @@ package app.ui import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.Divider import androidx.compose.material.Icon @@ -12,14 +11,13 @@ import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.extensions.* -import app.git.GitManager +import app.git.TabViewModel import app.theme.headerBackground import app.theme.headerText import app.theme.primaryTextColor @@ -32,7 +30,7 @@ import org.eclipse.jgit.revwalk.RevCommit @Composable fun CommitChanges( - gitManager: GitManager, + gitManager: TabViewModel, commit: RevCommit, onDiffSelected: (DiffEntry) -> Unit ) { diff --git a/src/main/kotlin/app/ui/Diff.kt b/src/main/kotlin/app/ui/Diff.kt index e6dbd3a..f0786cd 100644 --- a/src/main/kotlin/app/ui/Diff.kt +++ b/src/main/kotlin/app/ui/Diff.kt @@ -16,24 +16,25 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.git.DiffEntryType -import app.git.GitManager +import app.git.TabViewModel import app.git.diff.Hunk import app.git.diff.LineType import app.theme.primaryTextColor import app.ui.components.ScrollableLazyColumn import app.ui.components.SecondaryButton +import app.viewmodels.DiffViewModel import org.eclipse.jgit.diff.DiffEntry @Composable -fun Diff(gitManager: GitManager, diffEntryType: DiffEntryType, onCloseDiffView: () -> Unit) { - var text by remember { mutableStateOf(listOf()) } +fun Diff( + diffViewModel: DiffViewModel, + onCloseDiffView: () -> Unit, +) { + val diffResultState = diffViewModel.diffResult.collectAsState() + val diffResult = diffResultState.value ?: return - LaunchedEffect(Unit) { - text = gitManager.diffFormat(diffEntryType) - - - if (text.isEmpty()) onCloseDiffView() - } + val diffEntryType = diffResult.diffEntryType + val hunks = diffResult.hunks Column( modifier = Modifier @@ -59,7 +60,7 @@ fun Diff(gitManager: GitManager, diffEntryType: DiffEntryType, onCloseDiffView: .fillMaxSize() // .padding(16.dp) ) { - itemsIndexed(text) { index, hunk -> + itemsIndexed(hunks) { index, hunk -> val hunksSeparation = if (index == 0) 0.dp else @@ -96,9 +97,9 @@ fun Diff(gitManager: GitManager, diffEntryType: DiffEntryType, onCloseDiffView: backgroundButton = color, onClick = { if (diffEntryType is DiffEntryType.StagedDiff) { - gitManager.unstageHunk(diffEntryType.diffEntry, hunk) + diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk) } else { - gitManager.stageHunk(diffEntryType.diffEntry, hunk) + diffViewModel.stageHunk(diffEntryType.diffEntry, hunk) } } ) diff --git a/src/main/kotlin/app/ui/Remotes.kt b/src/main/kotlin/app/ui/Remotes.kt index 708f784..43f9ea7 100644 --- a/src/main/kotlin/app/ui/Remotes.kt +++ b/src/main/kotlin/app/ui/Remotes.kt @@ -12,16 +12,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import app.MAX_SIDE_PANEL_ITEMS_HEIGHT import app.extensions.simpleVisibleName -import app.git.GitManager +import app.git.TabViewModel import app.git.RemoteInfo import app.ui.components.ScrollableLazyColumn import app.ui.components.SideMenuEntry import app.ui.components.SideMenuSubentry import app.ui.components.entryHeight +import app.viewmodels.RemotesViewModel @Composable -fun Remotes(gitManager: GitManager) { - val remotes by gitManager.remotes.collectAsState() +fun Remotes(remotesViewModel: RemotesViewModel) { + val remotes by remotesViewModel.remotes.collectAsState() Column { SideMenuEntry("Remotes") diff --git a/src/main/kotlin/app/ui/RepositoryOpen.kt b/src/main/kotlin/app/ui/RepositoryOpen.kt index 3a0b3bc..8f72c5f 100644 --- a/src/main/kotlin/app/ui/RepositoryOpen.kt +++ b/src/main/kotlin/app/ui/RepositoryOpen.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import app.git.DiffEntryType -import app.git.GitManager +import app.git.TabViewModel import app.ui.dialogs.NewBranchDialog import app.ui.log.Log import openRepositoryDialog @@ -18,15 +18,22 @@ import org.jetbrains.compose.splitpane.rememberSplitPaneState @OptIn(ExperimentalSplitPaneApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class) @Composable -fun RepositoryOpenPage(gitManager: GitManager) { +fun RepositoryOpenPage(tabViewModel: TabViewModel) { + val repositoryState by tabViewModel.repositoryState.collectAsState() + var diffSelected by remember { mutableStateOf(null) } + LaunchedEffect(diffSelected) { + diffSelected?.let { safeDiffSelected -> + tabViewModel.updatedDiffEntry(safeDiffSelected) + } + } + var showNewBranchDialog by remember { mutableStateOf(false) } val (selectedItem, setSelectedItem) = remember { mutableStateOf(SelectedItem.None) } - LaunchedEffect(selectedItem) { diffSelected = null } @@ -37,7 +44,7 @@ fun RepositoryOpenPage(gitManager: GitManager) { showNewBranchDialog = false }, onAccept = { branchName -> - gitManager.createBranch(branchName) + tabViewModel.branchesViewModel.createBranch(branchName) showNewBranchDialog = false } ) @@ -46,12 +53,12 @@ fun RepositoryOpenPage(gitManager: GitManager) { Column { GMenu( onRepositoryOpen = { - openRepositoryDialog(gitManager = gitManager) + openRepositoryDialog(gitManager = tabViewModel) }, - onPull = { gitManager.pull() }, - onPush = { gitManager.push() }, - onStash = { gitManager.stash() }, - onPopStash = { gitManager.popStash() }, + onPull = { tabViewModel.pull() }, + onPush = { tabViewModel.push() }, + onStash = { tabViewModel.stash() }, + onPopStash = { tabViewModel.popStash() }, onCreateBranch = { showNewBranchDialog = true } ) @@ -65,22 +72,22 @@ fun RepositoryOpenPage(gitManager: GitManager) { .fillMaxHeight() ) { Branches( - gitManager = gitManager, + branchesViewModel = tabViewModel.branchesViewModel, onBranchClicked = { - val commit = gitManager.findCommit(it.objectId) + val commit = tabViewModel.findCommit(it.objectId) setSelectedItem(SelectedItem.Ref(commit)) } ) - Remotes(gitManager = gitManager) + Remotes(remotesViewModel = tabViewModel.remotesViewModel) Tags( - gitManager = gitManager, + tagsViewModel = tabViewModel.tagsViewModel, onTagClicked = { - val commit = gitManager.findCommit(it.objectId) + val commit = tabViewModel.findCommit(it.objectId) setSelectedItem(SelectedItem.Ref(commit)) } ) Stashes( - gitManager = gitManager, + gitManager = tabViewModel, onStashSelected = { stash -> setSelectedItem(SelectedItem.Stash(stash)) } @@ -97,11 +104,13 @@ fun RepositoryOpenPage(gitManager: GitManager) { modifier = Modifier .fillMaxSize() ) { - Crossfade(targetState = diffSelected) { diffEntry -> - when (diffEntry) { +// Crossfade(targetState = diffSelected) { diffEntry -> + when (diffSelected) { null -> { Log( - gitManager = gitManager, + tabViewModel = tabViewModel, + repositoryState = repositoryState, + logViewModel = tabViewModel.logViewModel, selectedItem = selectedItem, onItemSelected = { setSelectedItem(it) @@ -110,12 +119,11 @@ fun RepositoryOpenPage(gitManager: GitManager) { } else -> { Diff( - gitManager = gitManager, - diffEntryType = diffEntry, + diffViewModel = tabViewModel.diffViewModel, onCloseDiffView = { diffSelected = null }) } } - } +// } } } @@ -126,8 +134,9 @@ fun RepositoryOpenPage(gitManager: GitManager) { ) { if (selectedItem == SelectedItem.UncommitedChanges) { UncommitedChanges( - gitManager = gitManager, + statusViewModel = tabViewModel.statusViewModel, selectedEntryType = diffSelected, + repositoryState = repositoryState, onStagedDiffEntrySelected = { diffEntry -> diffSelected = if (diffEntry != null) DiffEntryType.StagedDiff(diffEntry) @@ -140,7 +149,7 @@ fun RepositoryOpenPage(gitManager: GitManager) { ) } else if (selectedItem is SelectedItem.CommitBasedItem) { CommitChanges( - gitManager = gitManager, + gitManager = tabViewModel, commit = selectedItem.revCommit, onDiffSelected = { diffEntry -> diffSelected = DiffEntryType.CommitDiff(diffEntry) diff --git a/src/main/kotlin/app/ui/Stashes.kt b/src/main/kotlin/app/ui/Stashes.kt index 3337bce..29cbef0 100644 --- a/src/main/kotlin/app/ui/Stashes.kt +++ b/src/main/kotlin/app/ui/Stashes.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier -import app.git.GitManager +import app.git.TabViewModel import app.git.StashStatus import app.ui.components.ScrollableLazyColumn import app.ui.components.SideMenuEntry @@ -15,7 +15,7 @@ import org.eclipse.jgit.revwalk.RevCommit @Composable fun Stashes( - gitManager: GitManager, + gitManager: TabViewModel, onStashSelected: (commit: RevCommit) -> Unit, ) { val stashStatusState = gitManager.stashStatus.collectAsState() diff --git a/src/main/kotlin/app/ui/SystemDialogs.kt b/src/main/kotlin/app/ui/SystemDialogs.kt index 6579030..1996cbc 100644 --- a/src/main/kotlin/app/ui/SystemDialogs.kt +++ b/src/main/kotlin/app/ui/SystemDialogs.kt @@ -1,9 +1,9 @@ import app.extensions.runCommand -import app.git.GitManager +import app.git.TabViewModel import javax.swing.JFileChooser -fun openRepositoryDialog(gitManager: GitManager) { +fun openRepositoryDialog(gitManager: TabViewModel) { val os = System.getProperty("os.name") val appStateManager = gitManager.appStateManager val latestDirectoryOpened = appStateManager.latestOpenedRepositoryPath @@ -29,7 +29,7 @@ fun openRepositoryDialog(gitManager: GitManager) { } private fun openRepositoryDialog( - gitManager: GitManager, + gitManager: TabViewModel, latestDirectoryOpened: String ) { diff --git a/src/main/kotlin/app/ui/Tags.kt b/src/main/kotlin/app/ui/Tags.kt index 4c8d4d5..05546f1 100644 --- a/src/main/kotlin/app/ui/Tags.kt +++ b/src/main/kotlin/app/ui/Tags.kt @@ -13,20 +13,21 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import app.MAX_SIDE_PANEL_ITEMS_HEIGHT import app.extensions.simpleName -import app.git.GitManager +import app.git.TabViewModel import app.ui.components.ScrollableLazyColumn import app.ui.components.SideMenuEntry import app.ui.components.SideMenuSubentry import app.ui.components.entryHeight import app.ui.context_menu.tagContextMenuItems +import app.viewmodels.TagsViewModel import org.eclipse.jgit.lib.Ref @Composable fun Tags( - gitManager: GitManager, + tagsViewModel: TagsViewModel, onTagClicked: (Ref) -> Unit, ) { - val tagsState = gitManager.tags.collectAsState() + val tagsState = tagsViewModel.tags.collectAsState() val tags = tagsState.value Column { @@ -46,8 +47,8 @@ fun Tags( TagRow( tag = tag, onTagClicked = { onTagClicked(tag) }, - onCheckoutTag = { gitManager.checkoutRef(tag) }, - onDeleteTag = { gitManager.deleteTag(tag) } + onCheckoutTag = { tagsViewModel.checkoutRef(tag) }, + onDeleteTag = { tagsViewModel.deleteTag(tag) } ) } } diff --git a/src/main/kotlin/app/ui/UncommitedChanges.kt b/src/main/kotlin/app/ui/UncommitedChanges.kt index dc21225..a500476 100644 --- a/src/main/kotlin/app/ui/UncommitedChanges.kt +++ b/src/main/kotlin/app/ui/UncommitedChanges.kt @@ -28,36 +28,30 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.extensions.filePath -import app.extensions.icon -import app.extensions.iconColor import app.extensions.isMerging import app.git.DiffEntryType -import app.git.GitManager -import app.git.StageStatus import app.git.StatusEntry import app.theme.headerBackground import app.theme.headerText import app.theme.primaryTextColor import app.ui.components.ScrollableLazyColumn import app.ui.components.SecondaryButton +import app.viewmodels.StageStatus +import app.viewmodels.StatusViewModel import org.eclipse.jgit.diff.DiffEntry +import org.eclipse.jgit.lib.RepositoryState @OptIn(ExperimentalAnimationApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class) @Composable fun UncommitedChanges( - gitManager: GitManager, + statusViewModel: StatusViewModel, selectedEntryType: DiffEntryType?, + repositoryState: RepositoryState, onStagedDiffEntrySelected: (DiffEntry?) -> Unit, onUnstagedDiffEntrySelected: (DiffEntry) -> Unit, ) { - val stageStatusState = gitManager.stageStatus.collectAsState() + val stageStatusState = statusViewModel.stageStatus.collectAsState() val stageStatus = stageStatusState.value - val lastCheck by gitManager.lastTimeChecked.collectAsState() - val repositoryState by gitManager.repositoryState.collectAsState() - - LaunchedEffect(lastCheck) { - gitManager.loadStatus() - } val staged: List val unstaged: List @@ -83,7 +77,7 @@ fun UncommitedChanges( var commitMessage by remember { mutableStateOf("") } val doCommit = { - gitManager.commit(commitMessage) + statusViewModel.commit(commitMessage) onStagedDiffEntrySelected(null) commitMessage = "" } @@ -111,13 +105,13 @@ fun UncommitedChanges( diffEntries = staged, onDiffEntrySelected = onStagedDiffEntrySelected, onDiffEntryOptionSelected = { - gitManager.unstage(it) + statusViewModel.unstage(it) }, onReset = { diffEntry -> - gitManager.resetStaged(diffEntry) + statusViewModel.resetStaged(diffEntry) }, onAllAction = { - gitManager.unstageAll() + statusViewModel.unstageAll() } ) @@ -132,13 +126,13 @@ fun UncommitedChanges( diffEntries = unstaged, onDiffEntrySelected = onUnstagedDiffEntrySelected, onDiffEntryOptionSelected = { - gitManager.stage(it) + statusViewModel.stage(it) }, onReset = { diffEntry -> - gitManager.resetUnstaged(diffEntry) + statusViewModel.resetUnstaged(diffEntry) }, { - gitManager.stageAll() + statusViewModel.stageAll() }, allActionTitle = "Stage all" ) diff --git a/src/main/kotlin/app/ui/WelcomePage.kt b/src/main/kotlin/app/ui/WelcomePage.kt index 501cc9d..781f53f 100644 --- a/src/main/kotlin/app/ui/WelcomePage.kt +++ b/src/main/kotlin/app/ui/WelcomePage.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.extensions.dirName import app.extensions.dirPath -import app.git.GitManager +import app.git.TabViewModel import app.theme.primaryTextColor import app.theme.secondaryTextColor import app.ui.dialogs.CloneDialog @@ -33,7 +33,7 @@ import java.net.URI @OptIn(ExperimentalMaterialApi::class) @Composable fun WelcomePage( - gitManager: GitManager, + gitManager: TabViewModel, ) { val appStateManager = gitManager.appStateManager var showCloneView by remember { mutableStateOf(false) } diff --git a/src/main/kotlin/app/ui/components/RepositoriesTabPanel.kt b/src/main/kotlin/app/ui/components/RepositoriesTabPanel.kt index 377d3fb..0b8c213 100644 --- a/src/main/kotlin/app/ui/components/RepositoriesTabPanel.kt +++ b/src/main/kotlin/app/ui/components/RepositoriesTabPanel.kt @@ -20,8 +20,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import app.AppStateManager +import app.di.AppComponent +import app.di.DaggerTabComponent +import app.git.TabViewModel import app.theme.tabColorActive import app.theme.tabColorInactive +import app.ui.AppTab +import javax.inject.Inject @Composable @@ -48,7 +54,7 @@ fun RepositoriesTabPanel( ) { items(items = tabs) { tab -> Tab( - title = tab.title, + title = tab.tabName, selected = tab.key == selectedTabKey, onClick = { onTabSelected(tab.key) @@ -154,7 +160,33 @@ fun Tab(title: MutableState, selected: Boolean, onClick: () -> Unit, onC } class TabInformation( - val title: MutableState, + val tabName: MutableState, val key: Int, + val path: String?, + appComponent: AppComponent, +) { + @Inject + lateinit var gitManager: TabViewModel + + @Inject + lateinit var appStateManager: AppStateManager + val content: @Composable (TabInformation) -> Unit -) \ No newline at end of file + + init { + val tabComponent = DaggerTabComponent.builder() + .appComponent(appComponent) + .build() + tabComponent.inject(this) + + gitManager.onRepositoryChanged = { path -> + if (path == null) { + appStateManager.repositoryTabRemoved(key) + } else + appStateManager.repositoryTabChanged(key, path) + } + content = { + AppTab(gitManager, path, tabName) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/ui/dialogs/CloneDialog.kt b/src/main/kotlin/app/ui/dialogs/CloneDialog.kt index 5a50411..22c4140 100644 --- a/src/main/kotlin/app/ui/dialogs/CloneDialog.kt +++ b/src/main/kotlin/app/ui/dialogs/CloneDialog.kt @@ -12,13 +12,13 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.git.CloneStatus -import app.git.GitManager +import app.git.TabViewModel import app.theme.primaryTextColor import java.io.File @Composable fun CloneDialog( - gitManager: GitManager, + gitManager: TabViewModel, onClose: () -> Unit ) { val cloneStatus = gitManager.cloneStatus.collectAsState() diff --git a/src/main/kotlin/app/ui/log/Log.kt b/src/main/kotlin/app/ui/log/Log.kt index 7f9425e..2f6abbb 100644 --- a/src/main/kotlin/app/ui/log/Log.kt +++ b/src/main/kotlin/app/ui/log/Log.kt @@ -34,8 +34,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.extensions.* -import app.git.GitManager -import app.git.LogStatus +import app.git.TabViewModel import app.git.graph.GraphNode import app.theme.* import app.ui.SelectedItem @@ -47,6 +46,8 @@ import app.ui.dialogs.MergeDialog import app.ui.dialogs.NewBranchDialog import app.ui.dialogs.NewTagDialog import app.ui.dialogs.ResetBranchDialog +import app.viewmodels.LogStatus +import app.viewmodels.LogViewModel import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.RepositoryState import org.eclipse.jgit.revwalk.RevCommit @@ -70,13 +71,14 @@ private const val CANVAS_MIN_WIDTH = 100 ) @Composable fun Log( - gitManager: GitManager, + tabViewModel: TabViewModel, + logViewModel: LogViewModel, selectedItem: SelectedItem, onItemSelected: (SelectedItem) -> Unit, + repositoryState: RepositoryState, ) { - val logStatusState = gitManager.logStatus.collectAsState() + val logStatusState = logViewModel.logStatus.collectAsState() val logStatus = logStatusState.value - val repositoryState by gitManager.repositoryState.collectAsState() val showLogDialog = remember { mutableStateOf(LogDialog.None) } val selectedCommit = if (selectedItem is SelectedItem.CommitBasedItem) { @@ -86,6 +88,7 @@ fun Log( } if (logStatus is LogStatus.Loaded) { + val hasUncommitedChanges = logStatus.hasUncommitedChanges val commitList = logStatus.plotCommitList val scrollState = rememberLazyListState() @@ -102,7 +105,7 @@ fun Log( } LogDialogs( - gitManager, + logViewModel, currentBranch = logStatus.currentBranch, onResetShowLogDialog = { showLogDialog.value = LogDialog.None }, showLogDialog = showLogDialog.value, @@ -114,7 +117,7 @@ fun Log( .background(MaterialTheme.colors.background) .fillMaxSize() ) { - val hasUncommitedChanges by gitManager.hasUncommitedChanges.collectAsState() +// val hasUncommitedChanges by tabViewModel.hasUncommitedChanges.collectAsState() val weightMod = remember { mutableStateOf(0f) } var graphWidth = (CANVAS_MIN_WIDTH + weightMod.value).dp @@ -131,11 +134,12 @@ fun Log( .background(MaterialTheme.colors.background) .fillMaxSize(), ) { + //TODO: Shouldn't this be an item of the graph? if (hasUncommitedChanges) item { UncommitedChangesLine( selected = selectedItem == SelectedItem.UncommitedChanges, - hasPreviousCommits = commitList.count() > 0, + hasPreviousCommits = commitList.isNotEmpty(), graphWidth = graphWidth, weightMod = weightMod, repositoryState = repositoryState, @@ -146,7 +150,7 @@ fun Log( } items(items = commitList) { graphNode -> CommitLine( - gitManager = gitManager, + logViewModel = logViewModel, graphNode = graphNode, selected = selectedCommit?.name == graphNode.name, weightMod = weightMod, @@ -169,7 +173,7 @@ fun Log( @Composable fun LogDialogs( - gitManager: GitManager, + logViewModel: LogViewModel, onResetShowLogDialog: () -> Unit, showLogDialog: LogDialog, currentBranch: Ref?, @@ -179,7 +183,7 @@ fun LogDialogs( NewBranchDialog( onReject = onResetShowLogDialog, onAccept = { branchName -> - gitManager.createBranchOnCommit(branchName, showLogDialog.graphNode) + logViewModel.createBranchOnCommit(branchName, showLogDialog.graphNode) onResetShowLogDialog() } ) @@ -188,7 +192,7 @@ fun LogDialogs( NewTagDialog( onReject = onResetShowLogDialog, onAccept = { tagName -> - gitManager.createTagOnCommit(tagName, showLogDialog.graphNode) + logViewModel.createTagOnCommit(tagName, showLogDialog.graphNode) onResetShowLogDialog() } ) @@ -200,7 +204,7 @@ fun LogDialogs( mergeBranchName = showLogDialog.ref.simpleName, onReject = onResetShowLogDialog, onAccept = { ff -> - gitManager.mergeBranch(showLogDialog.ref, ff) + logViewModel.mergeBranch(showLogDialog.ref, ff) onResetShowLogDialog() } ) @@ -208,7 +212,7 @@ fun LogDialogs( is LogDialog.ResetBranch -> ResetBranchDialog( onReject = onResetShowLogDialog, onAccept = { resetType -> - gitManager.resetToCommit(showLogDialog.graphNode, resetType) + logViewModel.resetToCommit(showLogDialog.graphNode, resetType) onResetShowLogDialog() } ) @@ -324,7 +328,7 @@ fun UncommitedChangesLine( @Composable fun CommitLine( - gitManager: GitManager, + logViewModel: LogViewModel, graphNode: GraphNode, selected: Boolean, weightMod: MutableState, @@ -348,9 +352,7 @@ fun CommitLine( listOf( ContextMenuItem( label = "Checkout commit", - onClick = { - gitManager.checkoutCommit(graphNode) - }), + onClick = { logViewModel.checkoutCommit(graphNode) }), ContextMenuItem( label = "Create branch", onClick = showCreateNewBranch @@ -361,7 +363,7 @@ fun CommitLine( ), ContextMenuItem( label = "Revert commit", - onClick = { gitManager.revertCommit(graphNode) } + onClick = { logViewModel.revertCommit(graphNode) } ), ContextMenuItem( @@ -403,10 +405,10 @@ fun CommitLine( refs = commitRefs, nodeColor = nodeColor, currentBranch = currentBranch, - onCheckoutRef = { ref -> gitManager.checkoutRef(ref) }, + onCheckoutRef = { ref -> logViewModel.checkoutRef(ref) }, onMergeBranch = { ref -> onMergeBranch(ref) }, - onDeleteBranch = { ref -> gitManager.deleteBranch(ref) }, - onDeleteTag = { ref -> gitManager.deleteTag(ref) }, + onDeleteBranch = { ref -> logViewModel.deleteBranch(ref) }, + onDeleteTag = { ref -> logViewModel.deleteTag(ref) }, ) } } diff --git a/src/main/kotlin/app/viewmodels/BranchesViewModel.kt b/src/main/kotlin/app/viewmodels/BranchesViewModel.kt new file mode 100644 index 0000000..6ab8b6d --- /dev/null +++ b/src/main/kotlin/app/viewmodels/BranchesViewModel.kt @@ -0,0 +1,60 @@ +package app.viewmodels + +import app.git.BranchesManager +import app.git.RefreshType +import app.git.TabState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import org.eclipse.jgit.revwalk.RevCommit +import javax.inject.Inject + +class BranchesViewModel @Inject constructor( + private val branchesManager: BranchesManager, + private val tabState: TabState, +) { + private val _branches = MutableStateFlow>(listOf()) + val branches: StateFlow> + get() = _branches + + private val _currentBranch = MutableStateFlow("") + val currentBranch: StateFlow + get() = _currentBranch + + suspend fun loadBranches(git: Git) { + val branchesList = branchesManager.getBranches(git) + + _branches.value = branchesList + _currentBranch.value = branchesManager.currentBranchRef(git)?.name ?: "" + } + + fun createBranch(branchName: String) = tabState.safeProcessing { git -> + branchesManager.createBranch(git, branchName) + this.loadBranches(git) + + return@safeProcessing RefreshType.NONE + } + + fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing { git -> + branchesManager.mergeBranch(git, ref, fastForward) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun deleteBranch(branch: Ref) =tabState.safeProcessing { git -> + branchesManager.deleteBranch(git, branch) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun checkoutRef(ref: Ref) = tabState.safeProcessing { git -> + branchesManager.checkoutRef(git, ref) + + return@safeProcessing RefreshType.ALL_DATA + } + + suspend fun refresh(git: Git) { + loadBranches(git) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/viewmodels/DiffViewModel.kt b/src/main/kotlin/app/viewmodels/DiffViewModel.kt new file mode 100644 index 0000000..8dae798 --- /dev/null +++ b/src/main/kotlin/app/viewmodels/DiffViewModel.kt @@ -0,0 +1,44 @@ +package app.viewmodels + +import app.git.* +import app.git.diff.Hunk +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.diff.DiffEntry +import javax.inject.Inject + +class DiffViewModel @Inject constructor( + private val tabState: TabState, + private val diffManager: DiffManager, + private val statusManager: StatusManager, +) { + // TODO Maybe use a sealed class instead of a null to represent that a diff is not selected? + private val _diffResult = MutableStateFlow(null) + val diffResult: StateFlow = _diffResult + + suspend fun updateDiff(git: Git, diffEntryType: DiffEntryType) = withContext(Dispatchers.IO) { + _diffResult.value = null + + val hunks = diffManager.diffFormat(git, diffEntryType) + + _diffResult.value = DiffResult(diffEntryType, hunks) + } + + fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation { git -> + statusManager.stageHunk(git, diffEntry, hunk) + + return@runOperation RefreshType.UNCOMMITED_CHANGES + } + + fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation { git -> + statusManager.unstageHunk(git, diffEntry, hunk) + + return@runOperation RefreshType.UNCOMMITED_CHANGES + } +} + +data class DiffResult(val diffEntryType: DiffEntryType, val hunks: List) \ No newline at end of file diff --git a/src/main/kotlin/app/viewmodels/LogViewModel.kt b/src/main/kotlin/app/viewmodels/LogViewModel.kt new file mode 100644 index 0000000..24c26f1 --- /dev/null +++ b/src/main/kotlin/app/viewmodels/LogViewModel.kt @@ -0,0 +1,97 @@ +package app.viewmodels + +import app.git.* +import app.git.graph.GraphCommitList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import org.eclipse.jgit.revwalk.RevCommit +import javax.inject.Inject + +class LogViewModel @Inject constructor( + private val logManager: LogManager, + private val statusManager: StatusManager, + private val branchesManager: BranchesManager, + private val tagsManager: TagsManager, + private val tabState: TabState, +) { + private val _logStatus = MutableStateFlow(LogStatus.Loading) + + val logStatus: StateFlow + get() = _logStatus + + suspend fun loadLog(git: Git) { + _logStatus.value = LogStatus.Loading + + val currentBranch = branchesManager.currentBranchRef(git) + val log = logManager.loadLog(git, currentBranch) + val hasUncommitedChanges = statusManager.hasUncommitedChanges(git) + _logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch) + } + + fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing { git -> + logManager.checkoutCommit(git, revCommit) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing { git -> + logManager.revertCommit(git, revCommit) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing { git -> + logManager.resetToCommit(git, revCommit, resetType = resetType) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun checkoutRef(ref: Ref) = tabState.safeProcessing { git -> + branchesManager.checkoutRef(git, ref) + + return@safeProcessing RefreshType.ALL_DATA + } + + + fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing { git -> + branchesManager.createBranchOnCommit(git, branch, revCommit) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing { git -> + tagsManager.createTagOnCommit(git, tag, revCommit) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing { git -> + branchesManager.mergeBranch(git, ref, fastForward) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun deleteBranch(branch: Ref) =tabState.safeProcessing { git -> + branchesManager.deleteBranch(git, branch) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun deleteTag(tag: Ref) = tabState.safeProcessing { git -> + tagsManager.deleteTag(git, tag) + + return@safeProcessing RefreshType.ALL_DATA + } + + suspend fun refresh(git: Git) { + loadLog(git) + } +} + +sealed class LogStatus { + object Loading : LogStatus() + class Loaded(val hasUncommitedChanges: Boolean, val plotCommitList: GraphCommitList, val currentBranch: Ref?) : LogStatus() +} \ No newline at end of file diff --git a/src/main/kotlin/app/viewmodels/RemotesViewModel.kt b/src/main/kotlin/app/viewmodels/RemotesViewModel.kt new file mode 100644 index 0000000..eabaa21 --- /dev/null +++ b/src/main/kotlin/app/viewmodels/RemotesViewModel.kt @@ -0,0 +1,44 @@ +package app.viewmodels + +import app.git.BranchesManager +import app.git.RemoteInfo +import app.git.RemotesManager +import app.git.TabState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import org.eclipse.jgit.transport.RemoteConfig +import javax.inject.Inject + +class RemotesViewModel @Inject constructor( + private val remotesManager: RemotesManager, + private val branchesManager: BranchesManager, +) { + private val _remotes = MutableStateFlow>(listOf()) + val remotes: StateFlow> + get() = _remotes + + suspend fun loadRemotes(git: Git) = withContext(Dispatchers.IO) { + val remotes = git.remoteList() + .call() + val allRemoteBranches = branchesManager.remoteBranches(git) + + remotesManager.loadRemotes(git, allRemoteBranches) + val remoteInfoList = remotes.map { remoteConfig -> + val remoteBranches = allRemoteBranches.filter { branch -> + branch.name.startsWith("refs/remotes/${remoteConfig.name}") + } + RemoteInfo(remoteConfig, remoteBranches) + } + + _remotes.value = remoteInfoList + } + + suspend fun refresh(git: Git) = withContext(Dispatchers.IO) { + loadRemotes(git) + } +} + diff --git a/src/main/kotlin/app/viewmodels/StatusViewModel.kt b/src/main/kotlin/app/viewmodels/StatusViewModel.kt new file mode 100644 index 0000000..81df400 --- /dev/null +++ b/src/main/kotlin/app/viewmodels/StatusViewModel.kt @@ -0,0 +1,124 @@ +package app.viewmodels + +import app.git.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.diff.DiffEntry +import javax.inject.Inject + +class StatusViewModel @Inject constructor( + private val tabState: TabState, + private val statusManager: StatusManager, + private val branchesManager: BranchesManager, + private val repositoryManager: RepositoryManager, +) { + private val _stageStatus = MutableStateFlow(StageStatus.Loaded(listOf(), listOf())) + val stageStatus: StateFlow = _stageStatus + + + private val _hasUncommitedChanges = MutableStateFlow(false) + val hasUncommitedChanges: StateFlow + get() = _hasUncommitedChanges + + fun stage(diffEntry: DiffEntry) = tabState.runOperation { git -> + statusManager.stage(git, diffEntry) + loadStatus(git) + + return@runOperation RefreshType.NONE + } + + fun unstage(diffEntry: DiffEntry) = tabState.runOperation { git -> + statusManager.unstage(git, diffEntry) + loadStatus(git) + + return@runOperation RefreshType.NONE + } + + + + fun unstageAll() = tabState.safeProcessing { git -> + statusManager.unstageAll(git) + loadStatus(git) + + return@safeProcessing RefreshType.NONE + } + + fun stageAll() = tabState.safeProcessing { git -> + statusManager.stageAll(git) + loadStatus(git) + + return@safeProcessing RefreshType.NONE + } + + + + fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation { git -> + statusManager.reset(git, diffEntry, staged = true) + + return@runOperation RefreshType.UNCOMMITED_CHANGES + } + + fun resetUnstaged(diffEntry: DiffEntry) =tabState.runOperation { git -> + statusManager.reset(git, diffEntry, staged = false) + + return@runOperation RefreshType.UNCOMMITED_CHANGES + } + + suspend fun loadStatus(git: Git) { + val previousStatus = _stageStatus.value + + try { + _stageStatus.value = StageStatus.Loading + val repositoryState = repositoryManager.getRepositoryState(git) + val currentBranchRef = branchesManager.currentBranchRef(git) + val staged = statusManager.getStaged(git, currentBranchRef, repositoryState) + val unstaged = statusManager.getUnstaged(git, repositoryState) + + _stageStatus.value = StageStatus.Loaded(staged, unstaged) + } catch (ex: Exception) { + _stageStatus.value = previousStatus + throw ex + } + } + + suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) { + _hasUncommitedChanges.value = statusManager.hasUncommitedChanges(git) + } + + fun commit(message: String) = tabState.safeProcessing { git -> + statusManager.commit(git, message) + + return@safeProcessing RefreshType.ALL_DATA + } + + + + suspend fun refresh(git: Git) = withContext(Dispatchers.IO) { + loadStatus(git) + loadHasUncommitedChanges(git) + } + + /** + * Checks if there are uncommited changes and returns if the state has changed ( + */ + suspend fun updateHasUncommitedChanges(git: Git): Boolean { + val hadUncommitedChanges = hasUncommitedChanges.value + + loadStatus(git) + + val hasNowUncommitedChanges = hasUncommitedChanges.value + + // Return true to update the log only if the uncommitedChanges status has changed + return (hasNowUncommitedChanges != hadUncommitedChanges) + } +} + +sealed class StageStatus { + object Loading : StageStatus() + data class Loaded(val staged: List, val unstaged: List) : StageStatus() +} + diff --git a/src/main/kotlin/app/git/GitManager.kt b/src/main/kotlin/app/viewmodels/TabViewModel.kt similarity index 53% rename from src/main/kotlin/app/git/GitManager.kt rename to src/main/kotlin/app/viewmodels/TabViewModel.kt index e2f46d3..9105b54 100644 --- a/src/main/kotlin/app/git/GitManager.kt +++ b/src/main/kotlin/app/viewmodels/TabViewModel.kt @@ -6,6 +6,7 @@ import app.app.newErrorNow import app.credentials.CredentialsState import app.credentials.CredentialsStateManager import app.git.diff.Hunk +import app.viewmodels.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -13,7 +14,6 @@ import kotlinx.coroutines.flow.collect import org.eclipse.jgit.api.Git import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.lib.ObjectId -import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.RepositoryState import org.eclipse.jgit.revwalk.RevCommit @@ -22,15 +22,18 @@ import java.io.File import javax.inject.Inject -class GitManager @Inject constructor( - private val statusManager: StatusManager, - private val logManager: LogManager, +class TabViewModel @Inject constructor( + val logViewModel: LogViewModel, + val branchesViewModel: BranchesViewModel, + val tagsViewModel: TagsViewModel, + val remotesViewModel: RemotesViewModel, + val statusViewModel: StatusViewModel, + val diffViewModel: DiffViewModel, + private val repositoryManager: RepositoryManager, private val remoteOperationsManager: RemoteOperationsManager, - private val branchesManager: BranchesManager, private val stashManager: StashManager, private val diffManager: DiffManager, - private val tagsManager: TagsManager, - private val remotesManager: RemotesManager, + private val tabState: TabState, val errorsManager: ErrorsManager, val appStateManager: AppStateManager, private val fileChangesWatcher: FileChangesWatcher, @@ -50,22 +53,31 @@ class GitManager @Inject constructor( val processing: StateFlow get() = _processing - private val _lastTimeChecked = MutableStateFlow(System.currentTimeMillis()) - val lastTimeChecked: StateFlow - get() = _lastTimeChecked - - val stageStatus: StateFlow = statusManager.stageStatus - val repositoryState: StateFlow = statusManager.repositoryState - val logStatus: StateFlow = logManager.logStatus - val branches: StateFlow> = branchesManager.branches - val tags: StateFlow> = tagsManager.tags - val currentBranch: StateFlow = branchesManager.currentBranch val stashStatus: StateFlow = stashManager.stashStatus val credentialsState: StateFlow = credentialsStateManager.credentialsState val cloneStatus: StateFlow = remoteOperationsManager.cloneStatus - val remotes: StateFlow> = remotesManager.remotes - private var git: Git? = null + private val _repositoryState = MutableStateFlow(RepositoryState.SAFE) + val repositoryState: StateFlow = _repositoryState + + init { + managerScope.launch { + tabState.refreshData.collect { refreshType -> + when (refreshType) { + RefreshType.NONE -> println("Not refreshing...") + RefreshType.ALL_DATA -> refreshRepositoryInfo() + RefreshType.ONLY_LOG -> refreshLog() + RefreshType.UNCOMMITED_CHANGES -> checkUncommitedChanges() + } + } + } + } + + private fun refreshLog() = tabState.runOperation { git -> + logViewModel.refresh(git) + + return@runOperation RefreshType.NONE + } /** * Property that indicates if a git operation is running @@ -74,7 +86,7 @@ class GitManager @Inject constructor( private val safeGit: Git get() { - val git = this.git + val git = this.tabState.git if (git == null) { _repositorySelectionStatus.value = RepositorySelectionStatus.None throw CancellationException() @@ -110,13 +122,15 @@ class GitManager @Inject constructor( try { repository.workTree // test if repository is valid _repositorySelectionStatus.value = RepositorySelectionStatus.Open(repository) - git = Git(repository) + tabState.git = Git(repository) onRepositoryChanged(repository.directory.parent) refreshRepositoryInfo() launch { watchRepositoryChanges() } + + println("AppStateManagerReference $appStateManager") } catch (ex: Exception) { ex.printStackTrace() onRepositoryChanged(null) @@ -125,6 +139,10 @@ class GitManager @Inject constructor( } } + suspend fun loadRepositoryState(git: Git) = withContext(Dispatchers.IO) { + _repositoryState.value = repositoryManager.getRepositoryState(git) + } + private suspend fun watchRepositoryChanges() { val ignored = safeGit.status().call().ignoredNotInIndex.toList() @@ -135,78 +153,35 @@ class GitManager @Inject constructor( if (!operationRunning) { // Only update if there isn't any process running safeProcessing(showError = false) { println("Changes detected, loading status") - val hasUncommitedChanges = statusManager.hasUncommitedChanges.value - statusManager.loadHasUncommitedChanges(safeGit) - statusManager.loadStatus(safeGit) +// val hasUncommitedChanges = statusManager.hasUncommitedChanges.value +// statusManager.loadHasUncommitedChanges(safeGit) +// statusManager.loadStatus(safeGit) - if(!hasUncommitedChanges) { - logManager.loadLog(safeGit) - } + statusViewModel.refresh(safeGit) + checkUncommitedChanges() } } } } - fun loadLog() = managerScope.launch { - coLoadLog() + private suspend fun loadLog() { + logViewModel.loadLog(safeGit) } - private suspend fun coLoadLog() { - logManager.loadLog(safeGit) - } - - suspend fun loadStatus() { - val hadUncommitedChanges = statusManager.hasUncommitedChanges.value - - statusManager.loadStatus(safeGit) - - val hasNowUncommitedChanges = statusManager.hasUncommitedChanges.value + suspend fun checkUncommitedChanges() { + val uncommitedChangesStateChanged = statusViewModel.updateHasUncommitedChanges(safeGit) // Update the log only if the uncommitedChanges status has changed - if (hasNowUncommitedChanges != hadUncommitedChanges) - coLoadLog() + if (uncommitedChangesStateChanged) + loadLog() } - fun stage(diffEntry: DiffEntry) = managerScope.launch { - runOperation { - statusManager.stage(safeGit, diffEntry) - } - } - - fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = managerScope.launch { - runOperation { - statusManager.stageHunk(safeGit, diffEntry, hunk) - } - } - - fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = managerScope.launch { - runOperation { - statusManager.unstageHunk(safeGit, diffEntry, hunk) - } - } - - fun unstage(diffEntry: DiffEntry) = managerScope.launch { - runOperation { - statusManager.unstage(safeGit, diffEntry) - } - } - - fun commit(message: String) = managerScope.launch { - safeProcessing { - statusManager.commit(safeGit, message) - refreshRepositoryInfo() - } - } - - val hasUncommitedChanges: StateFlow - get() = statusManager.hasUncommitedChanges - suspend fun diffFormat(diffEntryType: DiffEntryType): List { try { return diffManager.diffFormat(safeGit, diffEntryType) } catch (ex: Exception) { ex.printStackTrace() - loadStatus() + checkUncommitedChanges() return listOf() } } @@ -214,7 +189,7 @@ class GitManager @Inject constructor( fun pull() = managerScope.launch { safeProcessing { remoteOperationsManager.pull(safeGit) - coLoadLog() + loadLog() } } @@ -223,26 +198,27 @@ class GitManager @Inject constructor( try { remoteOperationsManager.push(safeGit) } finally { - coLoadLog() + loadLog() } } } private suspend fun refreshRepositoryInfo() { - statusManager.loadRepositoryStatus(safeGit) - statusManager.loadHasUncommitedChanges(safeGit) - statusManager.loadStatus(safeGit) - branchesManager.loadBranches(safeGit) - remotesManager.loadRemotes(safeGit, branchesManager.remoteBranches(safeGit)) - tagsManager.loadTags(safeGit) + logViewModel.refresh(safeGit) + branchesViewModel.refresh(safeGit) + remotesViewModel.refresh(safeGit) + tagsViewModel.refresh(safeGit) + statusViewModel.refresh(safeGit) + loadRepositoryState(safeGit) + stashManager.loadStashList(safeGit) - coLoadLog() + loadLog() } fun stash() = managerScope.launch { safeProcessing { stashManager.stash(safeGit) - loadStatus() + checkUncommitedChanges() loadLog() } } @@ -250,42 +226,11 @@ class GitManager @Inject constructor( fun popStash() = managerScope.launch { safeProcessing { stashManager.popStash(safeGit) - loadStatus() + checkUncommitedChanges() loadLog() } } - fun createBranch(branchName: String) = managerScope.launch { - safeProcessing { - branchesManager.createBranch(safeGit, branchName) - coLoadLog() - } - } - - fun deleteBranch(branch: Ref) = managerScope.launch { - safeProcessing { - branchesManager.deleteBranch(safeGit, branch) - refreshRepositoryInfo() - } - } - - fun deleteTag(tag: Ref) = managerScope.launch { - safeProcessing { - tagsManager.deleteTag(safeGit, tag) - refreshRepositoryInfo() - } - } - - fun resetStaged(diffEntry: DiffEntry) = managerScope.launch { - statusManager.reset(safeGit, diffEntry, staged = true) - loadLog() - } - - fun resetUnstaged(diffEntry: DiffEntry) = managerScope.launch { - statusManager.reset(safeGit, diffEntry, staged = false) - loadLog() - } - fun credentialsDenied() { credentialsStateManager.updateState(CredentialsState.CredentialsDenied) } @@ -302,68 +247,8 @@ class GitManager @Inject constructor( return diffManager.commitDiffEntries(safeGit, commit) } - fun unstageAll() = managerScope.launch { - safeProcessing { - statusManager.unstageAll(safeGit) - } - } - - fun stageAll() = managerScope.launch { - safeProcessing { - statusManager.stageAll(safeGit) - } - } - - fun checkoutCommit(revCommit: RevCommit) = managerScope.launch { - safeProcessing { - logManager.checkoutCommit(safeGit, revCommit) - refreshRepositoryInfo() - } - } - - fun revertCommit(revCommit: RevCommit) = managerScope.launch { - safeProcessing { - logManager.revertCommit(safeGit, revCommit) - refreshRepositoryInfo() - } - } - - fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = managerScope.launch { - safeProcessing { - logManager.resetToCommit(safeGit, revCommit, resetType = resetType) - refreshRepositoryInfo() - } - } - - fun createBranchOnCommit(branch: String, revCommit: RevCommit) = managerScope.launch { - safeProcessing { - branchesManager.createBranchOnCommit(safeGit, branch, revCommit) - refreshRepositoryInfo() - } - } - - fun createTagOnCommit(tag: String, revCommit: RevCommit) = managerScope.launch { - safeProcessing { - tagsManager.createTagOnCommit(safeGit, tag, revCommit) - refreshRepositoryInfo() - } - } - var onRepositoryChanged: (path: String?) -> Unit = {} - fun checkoutRef(ref: Ref) = managerScope.launch { - safeProcessing { - logManager.checkoutRef(safeGit, ref) - refreshRepositoryInfo() - } - } - - fun mergeBranch(ref: Ref, fastForward: Boolean) = managerScope.launch { - safeProcessing { - branchesManager.mergeBranch(safeGit, ref, fastForward) - refreshRepositoryInfo() - } - } fun dispose() { managerScope.cancel() @@ -377,7 +262,6 @@ class GitManager @Inject constructor( return safeGit.repository.parseCommit(objectId) } - @Synchronized private suspend fun safeProcessing(showError: Boolean = true, callback: suspend () -> Unit) { _processing.value = true operationRunning = true @@ -395,13 +279,10 @@ class GitManager @Inject constructor( } } - private inline fun runOperation(block: () -> Unit) { - operationRunning = true - try { - block() - } finally { - operationRunning = false - } + fun updatedDiffEntry(diffSelected: DiffEntryType) = tabState.runOperation { git -> + diffViewModel.updateDiff(git , diffSelected) + + return@runOperation RefreshType.NONE } } @@ -410,4 +291,4 @@ sealed class RepositorySelectionStatus { object None : RepositorySelectionStatus() object Loading : RepositorySelectionStatus() data class Open(val repository: Repository) : RepositorySelectionStatus() -} \ No newline at end of file +} diff --git a/src/main/kotlin/app/viewmodels/TagsViewModel.kt b/src/main/kotlin/app/viewmodels/TagsViewModel.kt new file mode 100644 index 0000000..11abb3d --- /dev/null +++ b/src/main/kotlin/app/viewmodels/TagsViewModel.kt @@ -0,0 +1,45 @@ +package app.viewmodels + +import app.git.BranchesManager +import app.git.RefreshType +import app.git.TabState +import app.git.TagsManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import javax.inject.Inject + +class TagsViewModel @Inject constructor( + private val tabState: TabState, + private val branchesManager: BranchesManager, + private val tagsManager: TagsManager, +) { + private val _tags = MutableStateFlow>(listOf()) + val tags: StateFlow> + get() = _tags + + suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) { + val tagsList = tagsManager.getTags(git) + + _tags.value = tagsList + } + + fun checkoutRef(ref: Ref) = tabState.safeProcessing { git -> + branchesManager.checkoutRef(git, ref) + + return@safeProcessing RefreshType.ALL_DATA + } + + fun deleteTag(tag: Ref) = tabState.safeProcessing { git -> + tagsManager.deleteTag(git, tag) + + return@safeProcessing RefreshType.ALL_DATA + } + + suspend fun refresh(git: Git) { + loadTags(git) + } +} \ No newline at end of file