diff --git a/src/main/kotlin/app/git/LogManager.kt b/src/main/kotlin/app/git/LogManager.kt deleted file mode 100644 index 5e70c02..0000000 --- a/src/main/kotlin/app/git/LogManager.kt +++ /dev/null @@ -1,118 +0,0 @@ -package app.git - -import app.git.graph.GraphCommitList -import app.git.graph.GraphWalk -import app.logging.printLog -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.withContext -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.api.ResetCommand -import org.eclipse.jgit.lib.Constants -import org.eclipse.jgit.lib.Ref -import org.eclipse.jgit.revwalk.RevCommit -import javax.inject.Inject - -private const val TAG = "LogManager" - -class LogManager @Inject constructor() { - suspend fun loadLog(git: Git, currentBranch: Ref?, hasUncommitedChanges: Boolean, commitsLimit: Int) = - withContext(Dispatchers.IO) { - val commitList = GraphCommitList() - val repositoryState = git.repository.repositoryState - - printLog(TAG, "Repository state ${repositoryState.description}") - - if (currentBranch != null || repositoryState.isRebasing) { // Current branch is null when there is no log (new repo) or rebasing - val logList = git.log().setMaxCount(1).call().toList() - - val walk = GraphWalk(git.repository) - - walk.use { - // Without this, during rebase conflicts the graph won't show the HEAD commits (new commits created - // by the rebase) - walk.markStart(walk.lookupCommit(logList.first())) - - walk.markStartAllRefs(Constants.R_HEADS) - walk.markStartAllRefs(Constants.R_REMOTES) - walk.markStartAllRefs(Constants.R_TAGS) - - if (hasUncommitedChanges) - commitList.addUncommitedChangesGraphCommit(logList.first()) - - commitList.source(walk) - commitList.fillTo(commitsLimit) - } - - ensureActive() - - } - - commitList.calcMaxLine() - - return@withContext commitList - } - - suspend fun checkoutCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) { - git - .checkout() - .setName(revCommit.name) - .call() - } - - suspend fun revertCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) { - git - .revert() - .include(revCommit) - .call() - } - - suspend fun resetToCommit(git: Git, revCommit: RevCommit, resetType: ResetType) = withContext(Dispatchers.IO) { - val reset = when (resetType) { - ResetType.SOFT -> ResetCommand.ResetType.SOFT - ResetType.MIXED -> ResetCommand.ResetType.MIXED - ResetType.HARD -> ResetCommand.ResetType.HARD - } - git - .reset() - .setMode(reset) - .setRef(revCommit.name) - .call() - } - - suspend fun latestMessage(git: Git): String = withContext(Dispatchers.IO) { - try { - val log = git.log().setMaxCount(1).call() - val latestCommitNode = log.firstOrNull() - - return@withContext if (latestCommitNode == null) - "" - else - latestCommitNode.fullMessage - - } catch (ex: Exception) { - ex.printStackTrace() - return@withContext "" - } - - } - - suspend fun hasPreviousCommits(git: Git): Boolean = withContext(Dispatchers.IO) { - try { - val log = git.log().setMaxCount(1).call() - val latestCommitNode = log.firstOrNull() - - return@withContext latestCommitNode != null - - } catch (ex: Exception) { - ex.printStackTrace() - return@withContext false - } - } -} - -enum class ResetType { - SOFT, - MIXED, - HARD, -} \ No newline at end of file diff --git a/src/main/kotlin/app/git/MergeManager.kt b/src/main/kotlin/app/git/branches/MergeBranchUseCase.kt similarity index 55% rename from src/main/kotlin/app/git/MergeManager.kt rename to src/main/kotlin/app/git/branches/MergeBranchUseCase.kt index 7203898..43f804c 100644 --- a/src/main/kotlin/app/git/MergeManager.kt +++ b/src/main/kotlin/app/git/branches/MergeBranchUseCase.kt @@ -1,4 +1,4 @@ -package app.git +package app.git.branches import app.exceptions.UncommitedChangesDetectedException import kotlinx.coroutines.Dispatchers @@ -6,13 +6,11 @@ import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.MergeCommand import org.eclipse.jgit.api.MergeResult -import org.eclipse.jgit.api.ResetCommand import org.eclipse.jgit.lib.Ref -import org.eclipse.jgit.revwalk.RevCommit import javax.inject.Inject -class MergeManager @Inject constructor() { - suspend fun mergeBranch(git: Git, branch: Ref, fastForward: Boolean) = withContext(Dispatchers.IO) { +class MergeBranchUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, branch: Ref, fastForward: Boolean) = withContext(Dispatchers.IO) { val fastForwardMode = if (fastForward) MergeCommand.FastForwardMode.FF else @@ -28,17 +26,4 @@ class MergeManager @Inject constructor() { throw UncommitedChangesDetectedException("Merge failed, makes sure you repository doesn't contain uncommited changes.") } } - - suspend fun resetRepoState(git: Git) = withContext(Dispatchers.IO) { - git.repository.writeMergeCommitMsg(null) - git.repository.writeMergeHeads(null) - - git.reset().setMode(ResetCommand.ResetType.HARD).call() - } - - suspend fun cherryPickCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) { - git.cherryPick() - .include(revCommit) - .call() - } } \ No newline at end of file diff --git a/src/main/kotlin/app/git/log/CheckHasPreviousCommitsUseCase.kt b/src/main/kotlin/app/git/log/CheckHasPreviousCommitsUseCase.kt new file mode 100644 index 0000000..4a27c24 --- /dev/null +++ b/src/main/kotlin/app/git/log/CheckHasPreviousCommitsUseCase.kt @@ -0,0 +1,21 @@ +package app.git.log + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import javax.inject.Inject + +class CheckHasPreviousCommitsUseCase @Inject constructor() { + suspend operator fun invoke(git: Git): Boolean = withContext(Dispatchers.IO) { + try { + val log = git.log().setMaxCount(1).call() + val latestCommitNode = log.firstOrNull() + + return@withContext latestCommitNode != null + + } catch (ex: Exception) { + ex.printStackTrace() + return@withContext false + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/log/CheckoutCommitUseCase.kt b/src/main/kotlin/app/git/log/CheckoutCommitUseCase.kt new file mode 100644 index 0000000..0586ce8 --- /dev/null +++ b/src/main/kotlin/app/git/log/CheckoutCommitUseCase.kt @@ -0,0 +1,16 @@ +package app.git.log + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.revwalk.RevCommit +import javax.inject.Inject + +class CheckoutCommitUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, revCommit: RevCommit): Unit = withContext(Dispatchers.IO) { + git + .checkout() + .setName(revCommit.name) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/log/CherryPickCommitUseCase.kt b/src/main/kotlin/app/git/log/CherryPickCommitUseCase.kt new file mode 100644 index 0000000..61b5e65 --- /dev/null +++ b/src/main/kotlin/app/git/log/CherryPickCommitUseCase.kt @@ -0,0 +1,16 @@ +package app.git.log + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.CherryPickResult +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.revwalk.RevCommit +import javax.inject.Inject + +class CherryPickCommitUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, revCommit: RevCommit): CherryPickResult = withContext(Dispatchers.IO) { + git.cherryPick() + .include(revCommit) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/log/GetLastCommitMessageUseCase.kt b/src/main/kotlin/app/git/log/GetLastCommitMessageUseCase.kt new file mode 100644 index 0000000..d3fe839 --- /dev/null +++ b/src/main/kotlin/app/git/log/GetLastCommitMessageUseCase.kt @@ -0,0 +1,24 @@ +package app.git.log + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import javax.inject.Inject + +class GetLastCommitMessageUseCase @Inject constructor() { + suspend operator fun invoke(git: Git): String = withContext(Dispatchers.IO) { + try { + val log = git.log().setMaxCount(1).call() + val latestCommitNode = log.firstOrNull() + + return@withContext if (latestCommitNode == null) + "" + else + latestCommitNode.fullMessage + + } catch (ex: Exception) { + ex.printStackTrace() + return@withContext "" + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/log/GetLogUseCase.kt b/src/main/kotlin/app/git/log/GetLogUseCase.kt new file mode 100644 index 0000000..69a3b83 --- /dev/null +++ b/src/main/kotlin/app/git/log/GetLogUseCase.kt @@ -0,0 +1,49 @@ +package app.git.log + + +import app.git.graph.GraphCommitList +import app.git.graph.GraphWalk +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Constants +import org.eclipse.jgit.lib.Ref +import javax.inject.Inject + +class GetLogUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, currentBranch: Ref?, hasUncommitedChanges: Boolean, commitsLimit: Int) = + withContext(Dispatchers.IO) { + val commitList = GraphCommitList() + val repositoryState = git.repository.repositoryState + + if (currentBranch != null || repositoryState.isRebasing) { // Current branch is null when there is no log (new repo) or rebasing + val logList = git.log().setMaxCount(1).call().toList() + + val walk = GraphWalk(git.repository) + + walk.use { + // Without this, during rebase conflicts the graph won't show the HEAD commits (new commits created + // by the rebase) + walk.markStart(walk.lookupCommit(logList.first())) + + walk.markStartAllRefs(Constants.R_HEADS) + walk.markStartAllRefs(Constants.R_REMOTES) + walk.markStartAllRefs(Constants.R_TAGS) + + if (hasUncommitedChanges) + commitList.addUncommitedChangesGraphCommit(logList.first()) + + commitList.source(walk) + commitList.fillTo(commitsLimit) + } + + ensureActive() + + } + + commitList.calcMaxLine() + + return@withContext commitList + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/log/ResetToCommitUseCase.kt b/src/main/kotlin/app/git/log/ResetToCommitUseCase.kt new file mode 100644 index 0000000..76ba9bf --- /dev/null +++ b/src/main/kotlin/app/git/log/ResetToCommitUseCase.kt @@ -0,0 +1,30 @@ +package app.git.log + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.ResetCommand +import org.eclipse.jgit.revwalk.RevCommit +import javax.inject.Inject + +class ResetToCommitUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, revCommit: RevCommit, resetType: ResetType): Unit = + withContext(Dispatchers.IO) { + val reset = when (resetType) { + ResetType.SOFT -> ResetCommand.ResetType.SOFT + ResetType.MIXED -> ResetCommand.ResetType.MIXED + ResetType.HARD -> ResetCommand.ResetType.HARD + } + git + .reset() + .setMode(reset) + .setRef(revCommit.name) + .call() + } +} + +enum class ResetType { + SOFT, + MIXED, + HARD, +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/log/RevertCommitUseCase.kt b/src/main/kotlin/app/git/log/RevertCommitUseCase.kt new file mode 100644 index 0000000..f06a2fc --- /dev/null +++ b/src/main/kotlin/app/git/log/RevertCommitUseCase.kt @@ -0,0 +1,16 @@ +package app.git.log + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.revwalk.RevCommit +import javax.inject.Inject + +class RevertCommitUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, revCommit: RevCommit): Unit = withContext(Dispatchers.IO) { + git + .revert() + .include(revCommit) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/repository/ResetRepositoryStateUseCase.kt b/src/main/kotlin/app/git/repository/ResetRepositoryStateUseCase.kt new file mode 100644 index 0000000..ea5ca17 --- /dev/null +++ b/src/main/kotlin/app/git/repository/ResetRepositoryStateUseCase.kt @@ -0,0 +1,20 @@ +package app.git.repository + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.ResetCommand +import javax.inject.Inject + +class ResetRepositoryStateUseCase @Inject constructor() { + suspend operator fun invoke(git: Git): Unit = withContext(Dispatchers.IO) { + git.repository.apply { + writeMergeCommitMsg(null) + writeMergeHeads(null) + } + + git.reset() + .setMode(ResetCommand.ResetType.HARD) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/tags/GetTagsUseCase.kt b/src/main/kotlin/app/git/tags/GetTagsUseCase.kt new file mode 100644 index 0000000..414d2f4 --- /dev/null +++ b/src/main/kotlin/app/git/tags/GetTagsUseCase.kt @@ -0,0 +1,6 @@ +package app.git.tags + +import javax.inject.Inject + +class GetTagsUseCase @Inject constructor() { +} \ No newline at end of file diff --git a/src/main/kotlin/app/ui/dialogs/ResetDialog.kt b/src/main/kotlin/app/ui/dialogs/ResetDialog.kt index 6fc6715..afc6e3d 100644 --- a/src/main/kotlin/app/ui/dialogs/ResetDialog.kt +++ b/src/main/kotlin/app/ui/dialogs/ResetDialog.kt @@ -13,7 +13,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import app.git.ResetType +import app.git.log.ResetType import app.theme.primaryTextColor import app.theme.textButtonColors import app.ui.components.PrimaryButton diff --git a/src/main/kotlin/app/ui/diff/Diff.kt b/src/main/kotlin/app/ui/diff/Diff.kt index 2175dbf..5273fe6 100644 --- a/src/main/kotlin/app/ui/diff/Diff.kt +++ b/src/main/kotlin/app/ui/diff/Diff.kt @@ -728,7 +728,7 @@ fun SplitDiffLine( Row( modifier = Modifier .background(backgroundColor) - .fillMaxHeight() + .fillMaxHeight(), ) { DisableSelection { LineNumber( diff --git a/src/main/kotlin/app/viewmodels/BranchesViewModel.kt b/src/main/kotlin/app/viewmodels/BranchesViewModel.kt index 8a06a09..532517a 100644 --- a/src/main/kotlin/app/viewmodels/BranchesViewModel.kt +++ b/src/main/kotlin/app/viewmodels/BranchesViewModel.kt @@ -13,12 +13,12 @@ import javax.inject.Inject class BranchesViewModel @Inject constructor( private val rebaseManager: RebaseManager, - private val mergeManager: MergeManager, - private val pushToSpecificBranchUseCase: PushToSpecificBranchUseCase, - private val pullFromSpecificBranchUseCase: PullFromSpecificBranchUseCase, private val tabState: TabState, private val appSettings: AppSettings, + private val pushToSpecificBranchUseCase: PushToSpecificBranchUseCase, + private val pullFromSpecificBranchUseCase: PullFromSpecificBranchUseCase, private val getCurrentBranchUseCase: GetCurrentBranchUseCase, + private val mergeBranchUseCase: MergeBranchUseCase, private val getBranchesUseCase: GetBranchesUseCase, private val createBranchUseCase: CreateBranchUseCase, private val deleteBranchUseCase: DeleteBranchUseCase, @@ -32,7 +32,7 @@ class BranchesViewModel @Inject constructor( val currentBranch: StateFlow get() = _currentBranch - suspend fun loadBranches(git: Git) { + private suspend fun loadBranches(git: Git) { _currentBranch.value = getCurrentBranchUseCase(git) val branchesList = getBranchesUseCase(git).toMutableList() @@ -59,7 +59,7 @@ class BranchesViewModel @Inject constructor( fun mergeBranch(ref: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - mergeManager.mergeBranch(git, ref, appSettings.ffMerge) + mergeBranchUseCase(git, ref, appSettings.ffMerge) } fun deleteBranch(branch: Ref) = tabState.safeProcessing( diff --git a/src/main/kotlin/app/viewmodels/LogViewModel.kt b/src/main/kotlin/app/viewmodels/LogViewModel.kt index fd138f3..aa76b35 100644 --- a/src/main/kotlin/app/viewmodels/LogViewModel.kt +++ b/src/main/kotlin/app/viewmodels/LogViewModel.kt @@ -7,6 +7,7 @@ import app.git.* import app.git.branches.* import app.git.graph.GraphCommitList import app.git.graph.GraphNode +import app.git.log.* import app.git.remote_operations.DeleteRemoteBranchUseCase import app.git.remote_operations.PullFromSpecificBranchUseCase import app.git.remote_operations.PushToSpecificBranchUseCase @@ -39,7 +40,7 @@ private const val FIRST_INDEX = 1 private const val LOG_MIN_TIME_IN_MS_TO_SHOW_LOAD = 500L class LogViewModel @Inject constructor( - private val logManager: LogManager, + private val getLogUseCase: GetLogUseCase, private val getStatusSummaryUseCase: GetStatusSummaryUseCase, private val checkHasUncommitedChangedUseCase: CheckHasUncommitedChangedUseCase, private val getCurrentBranchUseCase: GetCurrentBranchUseCase, @@ -49,9 +50,13 @@ class LogViewModel @Inject constructor( private val pushToSpecificBranchUseCase: PushToSpecificBranchUseCase, private val pullFromSpecificBranchUseCase: PullFromSpecificBranchUseCase, private val deleteRemoteBranchUseCase: DeleteRemoteBranchUseCase, + private val checkoutCommitUseCase: CheckoutCommitUseCase, + private val revertCommitUseCase: RevertCommitUseCase, + private val resetToCommitUseCase: ResetToCommitUseCase, + private val cherryPickCommitUseCase: CherryPickCommitUseCase, + private val mergeBranchUseCase: MergeBranchUseCase, private val rebaseManager: RebaseManager, private val tagsManager: TagsManager, - private val mergeManager: MergeManager, private val tabState: TabState, private val appSettings: AppSettings, ) { @@ -113,7 +118,7 @@ class LogViewModel @Inject constructor( } else -1 - val log = logManager.loadLog(git, currentBranch, hasUncommitedChanges, commitsLimit) + val log = getLogUseCase(git, currentBranch, hasUncommitedChanges, commitsLimit) _logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary, commitsLimitDisplayed) @@ -147,19 +152,19 @@ class LogViewModel @Inject constructor( fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - logManager.checkoutCommit(git, revCommit) + checkoutCommitUseCase(git, revCommit) } fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - logManager.revertCommit(git, revCommit) + revertCommitUseCase(git, revCommit) } fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - logManager.resetToCommit(git, revCommit, resetType = resetType) + resetToCommitUseCase(git, revCommit, resetType = resetType) } fun checkoutRef(ref: Ref) = tabState.safeProcessing( @@ -171,7 +176,7 @@ class LogViewModel @Inject constructor( fun cherrypickCommit(revCommit: RevCommit) = tabState.safeProcessing( refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG, ) { git -> - mergeManager.cherryPickCommit(git, revCommit) + cherryPickCommitUseCase(git, revCommit) } fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing( @@ -189,7 +194,7 @@ class LogViewModel @Inject constructor( fun mergeBranch(ref: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - mergeManager.mergeBranch(git, ref, appSettings.ffMerge) + mergeBranchUseCase(git, ref, appSettings.ffMerge) } fun deleteBranch(branch: Ref) = tabState.safeProcessing( diff --git a/src/main/kotlin/app/viewmodels/StatusViewModel.kt b/src/main/kotlin/app/viewmodels/StatusViewModel.kt index d6fc0a9..4f91e70 100644 --- a/src/main/kotlin/app/viewmodels/StatusViewModel.kt +++ b/src/main/kotlin/app/viewmodels/StatusViewModel.kt @@ -5,6 +5,9 @@ import app.extensions.delayedStateChange import app.extensions.isMerging import app.extensions.isReverting import app.git.* +import app.git.log.CheckHasPreviousCommitsUseCase +import app.git.log.GetLastCommitMessageUseCase +import app.git.repository.ResetRepositoryStateUseCase import app.git.workspace.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -26,9 +29,10 @@ class StatusViewModel @Inject constructor( private val resetEntryUseCase: ResetEntryUseCase, private val stageAllUseCase: StageAllUseCase, private val unstageAllUseCase: UnstageAllUseCase, + private val checkHasPreviousCommitsUseCase: CheckHasPreviousCommitsUseCase, + private val getLastCommitMessageUseCase: GetLastCommitMessageUseCase, + private val resetRepositoryStateUseCase: ResetRepositoryStateUseCase, private val rebaseManager: RebaseManager, - private val mergeManager: MergeManager, - private val logManager: LogManager, private val getStatusUseCase: GetStatusUseCase, private val getStagedUseCase: GetStagedUseCase, private val getUnstagedUseCase: GetUnstagedUseCase, @@ -169,7 +173,7 @@ class StatusViewModel @Inject constructor( refreshType = RefreshType.ALL_DATA, ) { git -> val commitMessage = if (amend && message.isBlank()) { - logManager.latestMessage(git) + getLastCommitMessageUseCase(git) } else message @@ -192,7 +196,7 @@ class StatusViewModel @Inject constructor( loadHasUncommitedChanges(git) val hasNowUncommitedChanges = this.lastUncommitedChangesState - hasPreviousCommits = logManager.hasPreviousCommits(git) + hasPreviousCommits = checkHasPreviousCommitsUseCase(git) // Return true to update the log only if the uncommitedChanges status has changed return (hasNowUncommitedChanges != hadUncommitedChanges) @@ -219,7 +223,7 @@ class StatusViewModel @Inject constructor( fun resetRepoState() = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - mergeManager.resetRepoState(git) + resetRepositoryStateUseCase(git) } fun deleteFile(statusEntry: StatusEntry) = tabState.runOperation(