Removed tag & rebase managers in favor of use cases

This commit is contained in:
Abdelilah El Aissaoui 2022-08-30 05:05:57 +02:00
parent 4e387951e1
commit 32b2c1df11
17 changed files with 249 additions and 190 deletions

View File

@ -1,139 +0,0 @@
package app.git
import app.exceptions.UncommitedChangesDetectedException
import app.git.branches.GetCurrentBranchUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
import org.eclipse.jgit.api.RebaseResult
import org.eclipse.jgit.errors.AmbiguousObjectException
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.RebaseTodoLine
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevWalk
import javax.inject.Inject
class RebaseManager @Inject constructor(
private val getCurrentBranchUseCase: GetCurrentBranchUseCase,
) {
suspend fun rebaseBranch(git: Git, ref: Ref) = withContext(Dispatchers.IO) {
val rebaseResult = git.rebase()
.setOperation(RebaseCommand.Operation.BEGIN)
.setUpstream(ref.objectId)
.call()
if (rebaseResult.status == RebaseResult.Status.UNCOMMITTED_CHANGES) {
throw UncommitedChangesDetectedException("Rebase failed, the repository contains uncommited changes.")
}
}
suspend fun continueRebase(git: Git) = withContext(Dispatchers.IO) {
git.rebase()
.setOperation(RebaseCommand.Operation.CONTINUE)
.call()
}
suspend fun abortRebase(git: Git) = withContext(Dispatchers.IO) {
git.rebase()
.setOperation(RebaseCommand.Operation.ABORT)
.call()
}
suspend fun skipRebase(git: Git) = withContext(Dispatchers.IO) {
git.rebase()
.setOperation(RebaseCommand.Operation.SKIP)
.call()
}
suspend fun rebaseInteractive(git: Git, interactiveHandler: InteractiveHandler, commit: RevCommit) =
withContext(Dispatchers.IO) {
val rebaseResult = git.rebase()
.runInteractively(interactiveHandler)
.setOperation(RebaseCommand.Operation.BEGIN)
.setUpstream(commit)
.call()
if (rebaseResult.status == RebaseResult.Status.FAILED) {
throw UncommitedChangesDetectedException("Rebase interactive failed.")
}
}
suspend fun resumeRebase(git: Git, interactiveHandler: InteractiveHandler) = withContext(Dispatchers.IO) {
val rebaseResult = git.rebase()
.runInteractively(interactiveHandler)
.setOperation(RebaseCommand.Operation.PROCESS_STEPS)
.call()
if (rebaseResult.status == RebaseResult.Status.FAILED) {
throw UncommitedChangesDetectedException("Rebase interactive failed.")
}
}
suspend fun rebaseLinesFullMessage(
git: Git,
rebaseTodoLines: List<RebaseTodoLine>,
): Map<String, String> = withContext(Dispatchers.IO) {
return@withContext rebaseTodoLines.map { line ->
val commit = getCommitFromLine(git, line)
val fullMessage = commit?.fullMessage ?: line.shortMessage
line.commit.name() to fullMessage
}.toMap()
}
private fun getCommitFromLine(git: Git, line: RebaseTodoLine): RevCommit? {
val resolvedList: List<ObjectId?> = try {
listOf(git.repository.resolve("${line.commit.name()}^{commit}"))
} catch (ex: AmbiguousObjectException) {
ex.candidates.toList()
}
if (resolvedList.isEmpty()) {
println("Commit search failed for line ${line.commit} - ${line.shortMessage}")
return null
} else if (resolvedList.count() == 1) {
val resolvedId = resolvedList.firstOrNull()
return if (resolvedId == null)
null
else
git.repository.parseCommit(resolvedId)
} else {
println("Multiple matching commits for line ${line.commit} - ${line.shortMessage}")
for (candidateId in resolvedList) {
val candidateCommit = git.repository.parseCommit(candidateId)
if (line.shortMessage == candidateCommit.shortMessage)
return candidateCommit
}
println("None of the matching commits has a matching short message")
return null
}
}
private fun getFullMessage(
rebaseTodoLine: RebaseTodoLine,
commitsList: List<RevCommit>
): String? {
val abbreviatedIdLength = rebaseTodoLine.commit.name().count()
return commitsList.firstOrNull {
it.abbreviate(abbreviatedIdLength).name() == rebaseTodoLine.commit.name()
}?.fullMessage
}
private suspend fun markCurrentBranchAsStart(revWalk: RevWalk, git: Git) {
val currentBranch = getCurrentBranchUseCase(git) ?: throw Exception("Null current branch")
val refTarget = revWalk.parseAny(currentBranch.leaf.objectId)
if (refTarget is RevCommit)
revWalk.markStart(refTarget)
else
throw Exception("Ref target is not a RevCommit")
}
}

View File

@ -1,30 +0,0 @@
package app.git
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class TagsManager @Inject constructor() {
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) {
git
.tag()
.setAnnotated(true)
.setName(tag)
.setObjectId(revCommit)
.call()
}
suspend fun deleteTag(git: Git, tag: Ref) = withContext(Dispatchers.IO) {
git
.tagDelete()
.setTags(tag.name)
.call()
}
}

View File

@ -0,0 +1,15 @@
package app.git.rebase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import javax.inject.Inject
class AbortRebaseUseCase @Inject constructor() {
suspend operator fun invoke(git: Git): Unit = withContext(Dispatchers.IO) {
git.rebase()
.setOperation(RebaseCommand.Operation.ABORT)
.call()
}
}

View File

@ -0,0 +1,15 @@
package app.git.rebase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import javax.inject.Inject
class ContinueRebaseUseCase @Inject constructor() {
suspend operator fun invoke(git: Git): Unit = withContext(Dispatchers.IO) {
git.rebase()
.setOperation(RebaseCommand.Operation.CONTINUE)
.call()
}
}

View File

@ -0,0 +1,53 @@
package app.git.rebase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.errors.AmbiguousObjectException
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.RebaseTodoLine
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class GetRebaseLinesFullMessageUseCase @Inject constructor() {
suspend operator fun invoke(
git: Git,
rebaseTodoLines: List<RebaseTodoLine>,
): Map<String, String> = withContext(Dispatchers.IO) {
return@withContext rebaseTodoLines.associate { line ->
val commit = getCommitFromLine(git, line)
val fullMessage = commit?.fullMessage ?: line.shortMessage
line.commit.name() to fullMessage
}
}
private fun getCommitFromLine(git: Git, line: RebaseTodoLine): RevCommit? {
val resolvedList: List<ObjectId?> = try {
listOf(git.repository.resolve("${line.commit.name()}^{commit}"))
} catch (ex: AmbiguousObjectException) {
ex.candidates.toList()
}
if (resolvedList.isEmpty()) {
println("Commit search failed for line ${line.commit} - ${line.shortMessage}")
return null
} else if (resolvedList.count() == 1) {
val resolvedId = resolvedList.firstOrNull()
return if (resolvedId == null)
null
else
git.repository.parseCommit(resolvedId)
} else {
println("Multiple matching commits for line ${line.commit} - ${line.shortMessage}")
for (candidateId in resolvedList) {
val candidateCommit = git.repository.parseCommit(candidateId)
if (line.shortMessage == candidateCommit.shortMessage)
return candidateCommit
}
println("None of the matching commits has a matching short message")
return null
}
}
}

View File

@ -0,0 +1,23 @@
package app.git.rebase
import app.exceptions.UncommitedChangesDetectedException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.RebaseResult
import org.eclipse.jgit.lib.Ref
import javax.inject.Inject
class RebaseBranchUseCase @Inject constructor() {
suspend operator fun invoke(git: Git, ref: Ref) = withContext(Dispatchers.IO) {
val rebaseResult = git.rebase()
.setOperation(RebaseCommand.Operation.BEGIN)
.setUpstream(ref.objectId)
.call()
if (rebaseResult.status == RebaseResult.Status.UNCOMMITTED_CHANGES) {
throw UncommitedChangesDetectedException("Rebase failed, the repository contains uncommited changes.")
}
}
}

View File

@ -0,0 +1,23 @@
package app.git.rebase
import app.exceptions.UncommitedChangesDetectedException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.RebaseResult
import javax.inject.Inject
class ResumeRebaseInteractiveUseCase @Inject constructor() {
suspend operator fun invoke(git: Git, interactiveHandler: RebaseCommand.InteractiveHandler) =
withContext(Dispatchers.IO) {
val rebaseResult = git.rebase()
.runInteractively(interactiveHandler)
.setOperation(RebaseCommand.Operation.PROCESS_STEPS)
.call()
if (rebaseResult.status == RebaseResult.Status.FAILED) {
throw UncommitedChangesDetectedException("Rebase interactive failed.")
}
}
}

View File

@ -0,0 +1,15 @@
package app.git.rebase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import javax.inject.Inject
class SkipRebaseUseCase @Inject constructor() {
suspend operator fun invoke(git: Git): Unit = withContext(Dispatchers.IO) {
git.rebase()
.setOperation(RebaseCommand.Operation.SKIP)
.call()
}
}

View File

@ -0,0 +1,25 @@
package app.git.rebase
import app.exceptions.UncommitedChangesDetectedException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.RebaseResult
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class StartRebaseInteractiveUseCase @Inject constructor() {
suspend operator fun invoke(git: Git, interactiveHandler: RebaseCommand.InteractiveHandler, commit: RevCommit) =
withContext(Dispatchers.IO) {
val rebaseResult = git.rebase()
.runInteractively(interactiveHandler)
.setOperation(RebaseCommand.Operation.BEGIN)
.setUpstream(commit)
.call()
if (rebaseResult.status == RebaseResult.Status.FAILED) {
throw UncommitedChangesDetectedException("Rebase interactive failed.")
}
}
}

View File

@ -0,0 +1,18 @@
package app.git.tags
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 CreateTagOnCommitUseCase @Inject constructor() {
suspend operator fun invoke(git: Git, tag: String, revCommit: RevCommit): Unit = withContext(Dispatchers.IO) {
git
.tag()
.setAnnotated(true)
.setName(tag)
.setObjectId(revCommit)
.call()
}
}

View File

@ -0,0 +1,16 @@
package app.git.tags
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Ref
import javax.inject.Inject
class DeleteTagUseCase @Inject constructor() {
suspend operator fun invoke(git: Git, tag: Ref): Unit = withContext(Dispatchers.IO) {
git
.tagDelete()
.setTags(tag.name)
.call()
}
}

View File

@ -1,6 +1,13 @@
package app.git.tags
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Ref
import javax.inject.Inject
class GetTagsUseCase @Inject constructor() {
suspend operator fun invoke(git: Git): List<Ref> = withContext(Dispatchers.IO) {
return@withContext git.tagList().call()
}
}

View File

@ -2,6 +2,7 @@ package app.viewmodels
import app.git.*
import app.git.branches.*
import app.git.rebase.RebaseBranchUseCase
import app.git.remote_operations.PullFromSpecificBranchUseCase
import app.git.remote_operations.PushToSpecificBranchUseCase
import app.preferences.AppSettings
@ -12,7 +13,7 @@ import org.eclipse.jgit.lib.Ref
import javax.inject.Inject
class BranchesViewModel @Inject constructor(
private val rebaseManager: RebaseManager,
private val rebaseBranchUseCase: RebaseBranchUseCase,
private val tabState: TabState,
private val appSettings: AppSettings,
private val pushToSpecificBranchUseCase: PushToSpecificBranchUseCase,
@ -81,7 +82,7 @@ class BranchesViewModel @Inject constructor(
fun rebaseBranch(ref: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.rebaseBranch(git, ref)
rebaseBranchUseCase(git, ref)
}
fun selectBranch(ref: Ref) {

View File

@ -8,9 +8,12 @@ import app.git.branches.*
import app.git.graph.GraphCommitList
import app.git.graph.GraphNode
import app.git.log.*
import app.git.rebase.RebaseBranchUseCase
import app.git.remote_operations.DeleteRemoteBranchUseCase
import app.git.remote_operations.PullFromSpecificBranchUseCase
import app.git.remote_operations.PushToSpecificBranchUseCase
import app.git.tags.CreateTagOnCommitUseCase
import app.git.tags.DeleteTagUseCase
import app.git.workspace.CheckHasUncommitedChangedUseCase
import app.git.workspace.GetStatusSummaryUseCase
import app.git.workspace.StatusSummary
@ -55,8 +58,9 @@ class LogViewModel @Inject constructor(
private val resetToCommitUseCase: ResetToCommitUseCase,
private val cherryPickCommitUseCase: CherryPickCommitUseCase,
private val mergeBranchUseCase: MergeBranchUseCase,
private val rebaseManager: RebaseManager,
private val tagsManager: TagsManager,
private val createTagOnCommitUseCase: CreateTagOnCommitUseCase,
private val deleteTagUseCase: DeleteTagUseCase,
private val rebaseBranchUseCase: RebaseBranchUseCase,
private val tabState: TabState,
private val appSettings: AppSettings,
) {
@ -188,7 +192,7 @@ class LogViewModel @Inject constructor(
fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
tagsManager.createTagOnCommit(git, tag, revCommit)
createTagOnCommitUseCase(git, tag, revCommit)
}
fun mergeBranch(ref: Ref) = tabState.safeProcessing(
@ -206,7 +210,7 @@ class LogViewModel @Inject constructor(
fun deleteTag(tag: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
tagsManager.deleteTag(git, tag)
deleteTagUseCase(git, tag)
}
suspend fun refreshUncommitedChanges(git: Git) {
@ -246,7 +250,7 @@ class LogViewModel @Inject constructor(
fun rebaseBranch(ref: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.rebaseBranch(git, ref)
rebaseBranchUseCase(git, ref)
}
fun selectUncommitedChanges() {

View File

@ -2,9 +2,12 @@ package app.viewmodels
import app.exceptions.InvalidMessageException
import app.exceptions.RebaseCancelledException
import app.git.RebaseManager
import app.git.RefreshType
import app.git.TabState
import app.git.rebase.AbortRebaseUseCase
import app.git.rebase.GetRebaseLinesFullMessageUseCase
import app.git.rebase.ResumeRebaseInteractiveUseCase
import app.git.rebase.StartRebaseInteractiveUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
@ -18,7 +21,10 @@ import javax.inject.Inject
class RebaseInteractiveViewModel @Inject constructor(
private val tabState: TabState,
private val rebaseManager: RebaseManager,
private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase,
private val startRebaseInteractiveUseCase: StartRebaseInteractiveUseCase,
private val abortRebaseUseCase: AbortRebaseUseCase,
private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
) {
private val rebaseInteractiveMutex = Mutex(true)
private val _rebaseState = MutableStateFlow<RebaseInteractiveState>(RebaseInteractiveState.Loading)
@ -34,7 +40,7 @@ class RebaseInteractiveViewModel @Inject constructor(
tabState.refreshData(RefreshType.REPO_STATE)
tabState.coRunOperation(refreshType = RefreshType.NONE) { git ->
val messages = rebaseManager.rebaseLinesFullMessage(git, steps)
val messages = getRebaseLinesFullMessageUseCase(git, steps)
_rebaseState.value = RebaseInteractiveState.Loaded(steps, messages)
}
@ -78,7 +84,7 @@ class RebaseInteractiveViewModel @Inject constructor(
showError = true
) { git ->
try {
rebaseManager.rebaseInteractive(git, interactiveHandler, revCommit)
startRebaseInteractiveUseCase(git, interactiveHandler, revCommit)
completed = true
} catch (ex: Exception) {
if (ex is RebaseCancelledException) {
@ -134,7 +140,7 @@ class RebaseInteractiveViewModel @Inject constructor(
refreshType = RefreshType.REPO_STATE
) { git ->
if (!cancelled && !completed) {
rebaseManager.abortRebase(git)
abortRebaseUseCase(git)
cancelled = true
@ -147,7 +153,7 @@ class RebaseInteractiveViewModel @Inject constructor(
refreshType = RefreshType.NONE,
) { git ->
try {
rebaseManager.resumeRebase(git, interactiveHandler)
resumeRebaseInteractiveUseCase(git, interactiveHandler)
completed = true
} catch (ex: Exception) {
if (ex is RebaseCancelledException) {

View File

@ -7,6 +7,9 @@ import app.extensions.isReverting
import app.git.*
import app.git.log.CheckHasPreviousCommitsUseCase
import app.git.log.GetLastCommitMessageUseCase
import app.git.rebase.AbortRebaseUseCase
import app.git.rebase.ContinueRebaseUseCase
import app.git.rebase.SkipRebaseUseCase
import app.git.repository.ResetRepositoryStateUseCase
import app.git.workspace.*
import kotlinx.coroutines.Dispatchers
@ -32,7 +35,9 @@ class StatusViewModel @Inject constructor(
private val checkHasPreviousCommitsUseCase: CheckHasPreviousCommitsUseCase,
private val getLastCommitMessageUseCase: GetLastCommitMessageUseCase,
private val resetRepositoryStateUseCase: ResetRepositoryStateUseCase,
private val rebaseManager: RebaseManager,
private val continueRebaseUseCase: ContinueRebaseUseCase,
private val abortRebaseUseCase: AbortRebaseUseCase,
private val skipRebaseUseCase: SkipRebaseUseCase,
private val getStatusUseCase: GetStatusUseCase,
private val getStagedUseCase: GetStagedUseCase,
private val getUnstagedUseCase: GetUnstagedUseCase,
@ -205,19 +210,19 @@ class StatusViewModel @Inject constructor(
fun continueRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.continueRebase(git)
continueRebaseUseCase(git)
}
fun abortRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.abortRebase(git)
abortRebaseUseCase(git)
}
fun skipRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.skipRebase(git)
skipRebaseUseCase(git)
}
fun resetRepoState() = tabState.safeProcessing(

View File

@ -2,8 +2,9 @@ package app.viewmodels
import app.git.RefreshType
import app.git.TabState
import app.git.TagsManager
import app.git.branches.CheckoutRefUseCase
import app.git.tags.DeleteTagUseCase
import app.git.tags.GetTagsUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -14,7 +15,8 @@ import javax.inject.Inject
class TagsViewModel @Inject constructor(
private val tabState: TabState,
private val tagsManager: TagsManager,
private val getTagsUseCase: GetTagsUseCase,
private val deleteTagUseCase: DeleteTagUseCase,
private val checkoutRefUseCase: CheckoutRefUseCase,
) : ExpandableViewModel() {
private val _tags = MutableStateFlow<List<Ref>>(listOf())
@ -22,7 +24,7 @@ class TagsViewModel @Inject constructor(
get() = _tags
private suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
val tagsList = tagsManager.getTags(git)
val tagsList = getTagsUseCase(git)
_tags.value = tagsList
}
@ -36,7 +38,7 @@ class TagsViewModel @Inject constructor(
fun deleteTag(tag: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
tagsManager.deleteTag(git, tag)
deleteTagUseCase(git, tag)
}
fun selectTag(tag: Ref) {