From 270951fe666c73a8ae25aa126a7b0a0efcbb72cb Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Sat, 20 Aug 2022 03:19:31 +0200 Subject: [PATCH] Started arch refactor --- src/main/kotlin/app/git/AuthorManager.kt | 61 ---- src/main/kotlin/app/git/BranchesManager.kt | 95 ------ src/main/kotlin/app/git/CloneStatus.kt | 34 ++ src/main/kotlin/app/git/DiffManager.kt | 5 +- src/main/kotlin/app/git/RebaseManager.kt | 5 +- .../kotlin/app/git/RemoteOperationsManager.kt | 297 ----------------- .../app/git/author/LoadAuthorUseCase.kt | 31 ++ .../app/git/author/SaveAuthorUseCase.kt | 40 +++ .../app/git/branches/CheckoutRefUseCase.kt | 25 ++ .../branches/CreateBranchOnCommitUseCase.kt | 19 ++ .../app/git/branches/CreateBranchUseCase.kt | 17 + .../app/git/branches/DeleteBranchUseCase.kt | 17 + .../branches/DeleteLocallyRemoteBranches.kt | 16 + .../app/git/branches/GetBranchesUseCase.kt | 15 + .../git/branches/GetCurrentBranchUseCase.kt | 31 ++ .../git/branches/GetRemoteBranchesUseCase.kt | 17 + .../CloneRepositoryUseCase.kt | 81 +++++ .../DeleteRemoteBranchUseCase.kt | 59 ++++ .../FetchAllBranchesUseCase.kt | 24 ++ .../HandleTransportUseCase.kt | 20 ++ .../remote_operations/PullBranchUseCase.kt | 35 ++ .../PullFromSpecificBranchUseCase.kt | 40 +++ .../remote_operations/PushBranchUseCase.kt | 43 +++ .../PushToSpecificBranchUseCase.kt | 47 +++ .../kotlin/app/viewmodels/AuthorViewModel.kt | 10 +- .../app/viewmodels/BranchesViewModel.kt | 26 +- .../kotlin/app/viewmodels/CloneViewModel.kt | 6 +- .../kotlin/app/viewmodels/LogViewModel.kt | 29 +- .../kotlin/app/viewmodels/MenuViewModel.kt | 14 +- .../kotlin/app/viewmodels/RemotesViewModel.kt | 18 +- .../kotlin/app/viewmodels/TagsViewModel.kt | 6 +- .../app/git/BeforeRepoAllTestsExtension.kt | 6 +- .../kotlin/app/git/BranchesManagerTest.kt | 308 +++++++++--------- 33 files changed, 844 insertions(+), 653 deletions(-) delete mode 100644 src/main/kotlin/app/git/AuthorManager.kt delete mode 100644 src/main/kotlin/app/git/BranchesManager.kt create mode 100644 src/main/kotlin/app/git/CloneStatus.kt delete mode 100644 src/main/kotlin/app/git/RemoteOperationsManager.kt create mode 100644 src/main/kotlin/app/git/author/LoadAuthorUseCase.kt create mode 100644 src/main/kotlin/app/git/author/SaveAuthorUseCase.kt create mode 100644 src/main/kotlin/app/git/branches/CheckoutRefUseCase.kt create mode 100644 src/main/kotlin/app/git/branches/CreateBranchOnCommitUseCase.kt create mode 100644 src/main/kotlin/app/git/branches/CreateBranchUseCase.kt create mode 100644 src/main/kotlin/app/git/branches/DeleteBranchUseCase.kt create mode 100644 src/main/kotlin/app/git/branches/DeleteLocallyRemoteBranches.kt create mode 100644 src/main/kotlin/app/git/branches/GetBranchesUseCase.kt create mode 100644 src/main/kotlin/app/git/branches/GetCurrentBranchUseCase.kt create mode 100644 src/main/kotlin/app/git/branches/GetRemoteBranchesUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/CloneRepositoryUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/DeleteRemoteBranchUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/FetchAllBranchesUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/HandleTransportUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/PullBranchUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/PullFromSpecificBranchUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/PushBranchUseCase.kt create mode 100644 src/main/kotlin/app/git/remote_operations/PushToSpecificBranchUseCase.kt diff --git a/src/main/kotlin/app/git/AuthorManager.kt b/src/main/kotlin/app/git/AuthorManager.kt deleted file mode 100644 index 149c323..0000000 --- a/src/main/kotlin/app/git/AuthorManager.kt +++ /dev/null @@ -1,61 +0,0 @@ -package app.git - -import app.models.AuthorInfo -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.storage.file.FileBasedConfig -import javax.inject.Inject - -class AuthorManager @Inject constructor() { - suspend fun loadAuthor(git: Git) = withContext(Dispatchers.IO) { - val config = git.repository.config - val globalConfig = git.repository.config.baseConfig - - val repoConfig = FileBasedConfig((config as FileBasedConfig).file, git.repository.fs) - repoConfig.load() - - val globalName = globalConfig.getString("user", null, "name") - val globalEmail = globalConfig.getString("user", null, "email") - - val name = repoConfig.getString("user", null, "name") - val email = repoConfig.getString("user", null, "email") - - return@withContext AuthorInfo( - globalName, - globalEmail, - name, - email, - ) - } - - suspend fun saveAuthorInfo(git: Git, newAuthorInfo: AuthorInfo) = withContext(Dispatchers.IO) { - val config = git.repository.config - val globalConfig = git.repository.config.baseConfig - val repoConfig = FileBasedConfig((config as FileBasedConfig).file, git.repository.fs) - repoConfig.load() - - if (globalConfig is FileBasedConfig) { - globalConfig.setStringProperty("user", null, "name", newAuthorInfo.globalName) - globalConfig.setStringProperty("user", null, "email", newAuthorInfo.globalEmail) - globalConfig.save() - } - - config.setStringProperty("user", null, "name", newAuthorInfo.name) - config.setStringProperty("user", null, "email", newAuthorInfo.email) - config.save() - } - - private fun FileBasedConfig.setStringProperty( - section: String, - subsection: String?, - name: String, - value: String?, - ) { - if (value == null) { - unset(section, subsection, name) - } else { - setString(section, subsection, name, value) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/app/git/BranchesManager.kt b/src/main/kotlin/app/git/BranchesManager.kt deleted file mode 100644 index 84485e2..0000000 --- a/src/main/kotlin/app/git/BranchesManager.kt +++ /dev/null @@ -1,95 +0,0 @@ -package app.git - -import app.extensions.isBranch -import app.extensions.simpleName -import kotlinx.coroutines.Dispatchers -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.lib.Ref -import org.eclipse.jgit.revwalk.RevCommit -import javax.inject.Inject - -class BranchesManager @Inject constructor() { - /** - * Returns the current branch in [Ref]. If the repository is new, the current branch will be null. - */ - suspend fun currentBranchRef(git: Git): Ref? { - val branchList = getBranches(git) - val branchName = git - .repository - .fullBranch - - var branchFound = branchList.firstOrNull { - it.name == branchName - } - - if (branchFound == null) { - branchFound = branchList.firstOrNull { - it.objectId.name == branchName // Try to get the HEAD - } - } - - return branchFound - } - - suspend fun getBranches(git: Git) = withContext(Dispatchers.IO) { - return@withContext git - .branchList() - .call() - } - - suspend fun createBranch(git: Git, branchName: String) = withContext(Dispatchers.IO) { - git - .checkout() - .setCreateBranch(true) - .setName(branchName) - .call() - } - - suspend fun createBranchOnCommit(git: Git, branch: String, revCommit: RevCommit) = withContext(Dispatchers.IO) { - git - .checkout() - .setCreateBranch(true) - .setName(branch) - .setStartPoint(revCommit) - .call() - } - - suspend fun deleteBranch(git: Git, branch: Ref) = withContext(Dispatchers.IO) { - git - .branchDelete() - .setBranchNames(branch.name) - .setForce(true) // TODO Should it be forced? - .call() - } - - suspend fun deleteLocallyRemoteBranches(git: Git, branches: List) = withContext(Dispatchers.IO) { - git - .branchDelete() - .setBranchNames(*branches.toTypedArray()) - .setForce(true) - .call() - } - - suspend fun remoteBranches(git: Git) = withContext(Dispatchers.IO) { - git - .branchList() - .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/CloneStatus.kt b/src/main/kotlin/app/git/CloneStatus.kt new file mode 100644 index 0000000..7102187 --- /dev/null +++ b/src/main/kotlin/app/git/CloneStatus.kt @@ -0,0 +1,34 @@ +package app.git + +import org.eclipse.jgit.transport.RemoteRefUpdate +import java.io.File + +sealed class CloneStatus { + object None : CloneStatus() + data class Cloning(val taskName: String, val progress: Int, val total: Int) : CloneStatus() + object Cancelling : CloneStatus() + data class Fail(val reason: String) : CloneStatus() + data class Completed(val repoDir: File) : CloneStatus() +} + +val RemoteRefUpdate.Status.isRejected: Boolean + get() { + return this == RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD || + this == RemoteRefUpdate.Status.REJECTED_NODELETE || + this == RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED || + this == RemoteRefUpdate.Status.REJECTED_OTHER_REASON + } + +val RemoteRefUpdate.statusMessage: String + get() { + return when (this.status) { + RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> "Failed to push some refs to ${this.remoteName}. " + + "Updates were rejected because the remote contains work that you do not have locally. Pulling changes from remote may help." + + RemoteRefUpdate.Status.REJECTED_NODELETE -> "Could not delete ref because the remote doesn't support deleting refs." + RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED -> "Ref rejected, old object id in remote has changed." + RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> this.message ?: "Push rejected for unknown reasons." + else -> "" + } + + } \ No newline at end of file diff --git a/src/main/kotlin/app/git/DiffManager.kt b/src/main/kotlin/app/git/DiffManager.kt index 839fb32..b7a5fc6 100644 --- a/src/main/kotlin/app/git/DiffManager.kt +++ b/src/main/kotlin/app/git/DiffManager.kt @@ -5,6 +5,7 @@ import app.di.RawFileManagerFactory import app.exceptions.MissingDiffEntryException import app.extensions.fullData import app.extensions.isMerging +import app.git.branches.GetCurrentBranchUseCase import app.git.diff.DiffResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -28,7 +29,7 @@ import javax.inject.Inject class DiffManager @Inject constructor( private val rawFileManagerFactory: RawFileManagerFactory, private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory, - private val branchesManager: BranchesManager, + private val getCurrentBranchUseCase: GetCurrentBranchUseCase, private val repositoryManager: RepositoryManager, ) { suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) { @@ -57,7 +58,7 @@ class DiffManager @Inject constructor( .setCached(cached).apply { val repositoryState = repositoryManager.getRepositoryState(git) if ( - branchesManager.currentBranchRef(git) == null && + getCurrentBranchUseCase(git) == null && !repositoryState.isMerging && !repositoryState.isRebasing && cached diff --git a/src/main/kotlin/app/git/RebaseManager.kt b/src/main/kotlin/app/git/RebaseManager.kt index 1020415..edeaef3 100644 --- a/src/main/kotlin/app/git/RebaseManager.kt +++ b/src/main/kotlin/app/git/RebaseManager.kt @@ -1,6 +1,7 @@ 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 @@ -16,7 +17,7 @@ import org.eclipse.jgit.revwalk.RevWalk import javax.inject.Inject class RebaseManager @Inject constructor( - private val branchesManager: BranchesManager, + private val getCurrentBranchUseCase: GetCurrentBranchUseCase, ) { suspend fun rebaseBranch(git: Git, ref: Ref) = withContext(Dispatchers.IO) { @@ -127,7 +128,7 @@ class RebaseManager @Inject constructor( } private suspend fun markCurrentBranchAsStart(revWalk: RevWalk, git: Git) { - val currentBranch = branchesManager.currentBranchRef(git) ?: throw Exception("Null current branch") + val currentBranch = getCurrentBranchUseCase(git) ?: throw Exception("Null current branch") val refTarget = revWalk.parseAny(currentBranch.leaf.objectId) if (refTarget is RevCommit) diff --git a/src/main/kotlin/app/git/RemoteOperationsManager.kt b/src/main/kotlin/app/git/RemoteOperationsManager.kt deleted file mode 100644 index 3962bb4..0000000 --- a/src/main/kotlin/app/git/RemoteOperationsManager.kt +++ /dev/null @@ -1,297 +0,0 @@ -package app.git - -import app.credentials.GSessionManager -import app.credentials.HttpCredentialsProvider -import app.extensions.remoteName -import app.extensions.simpleName -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.api.RebaseResult -import org.eclipse.jgit.lib.ProgressMonitor -import org.eclipse.jgit.lib.Ref -import org.eclipse.jgit.transport.* -import java.io.File -import javax.inject.Inject -import kotlin.coroutines.cancellation.CancellationException - - -class RemoteOperationsManager @Inject constructor( - private val sessionManager: GSessionManager -) { - suspend fun pull(git: Git, rebase: Boolean) = withContext(Dispatchers.IO) { - val pullResult = git - .pull() - .setTransportConfigCallback { - handleTransportCredentials(it) - } - .setRebase(rebase) - .setCredentialsProvider(CredentialsProvider.getDefault()) - .call() - - if (!pullResult.isSuccessful) { - var message = "Pull failed" - - if (rebase) { - message = when (pullResult.rebaseResult.status) { - RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommited changes" - RebaseResult.Status.CONFLICTS -> "Pull with rebase has conflicts, fix them to continue" - else -> message - } - } - - throw Exception(message) - } - } - - suspend fun pullFromBranch(git: Git, rebase: Boolean, remoteBranch: Ref) = withContext(Dispatchers.IO) { - val pullResult = git - .pull() - .setTransportConfigCallback { - handleTransportCredentials(it) - } - .setRemote(remoteBranch.remoteName) - .setRemoteBranchName(remoteBranch.simpleName) - .setRebase(rebase) - .setCredentialsProvider(CredentialsProvider.getDefault()) - .call() - - if (!pullResult.isSuccessful) { - var message = "Pull failed" - - if (rebase) { - message = when (pullResult.rebaseResult.status) { - RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommited changes" - RebaseResult.Status.CONFLICTS -> "Pull with rebase has conflicts, fix them to continue" - else -> message - } - } - - throw Exception(message) - } - } - - suspend fun fetchAll(git: Git) = withContext(Dispatchers.IO) { - val remotes = git.remoteList().call() - - for (remote in remotes) { - git.fetch() - .setRemote(remote.name) - .setRefSpecs(remote.fetchRefSpecs) - .setTransportConfigCallback { - handleTransportCredentials(it) - } - .setCredentialsProvider(CredentialsProvider.getDefault()) - .call() - } - } - - suspend fun push(git: Git, force: Boolean, pushTags: Boolean) = withContext(Dispatchers.IO) { - val currentBranchRefSpec = git.repository.fullBranch - - val pushResult = git - .push() - .setRefSpecs(RefSpec(currentBranchRefSpec)) - .setForce(force) - .apply { - if (pushTags) - setPushTags() - } - .setTransportConfigCallback { - handleTransportCredentials(it) - } - .call() - - val results = - pushResult.map { it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected } } - .flatten() - if (results.isNotEmpty()) { - val error = StringBuilder() - - results.forEach { result -> - error.append(result.statusMessage) - error.append("\n") - } - - throw Exception(error.toString()) - } - } - - suspend fun pushToBranch(git: Git, force: Boolean, pushTags: Boolean, remoteBranch: Ref) = - withContext(Dispatchers.IO) { - val currentBranchRefSpec = git.repository.fullBranch - - val pushResult = git - .push() - .setRefSpecs(RefSpec("$currentBranchRefSpec:${remoteBranch.simpleName}")) - .setRemote(remoteBranch.remoteName) - .setForce(force) - .apply { - if (pushTags) - setPushTags() - } - .setTransportConfigCallback { - handleTransportCredentials(it) - } - .call() - - val results = - pushResult.map { it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected } } - .flatten() - if (results.isNotEmpty()) { - val error = StringBuilder() - - results.forEach { result -> - error.append(result.statusMessage) - error.append("\n") - } - - throw Exception(error.toString()) - } - } - - private fun handleTransportCredentials(transport: Transport?) { - if (transport is SshTransport) { - transport.sshSessionFactory = sessionManager.generateSshSessionFactory() - } else if (transport is HttpTransport) { - transport.credentialsProvider = HttpCredentialsProvider() - } - } - - suspend fun deleteBranch(git: Git, ref: Ref) = withContext(Dispatchers.IO) { - val branchSplit = ref.name.split("/").toMutableList() - val remoteName = branchSplit[2] // Remote name - repeat(3) { - branchSplit.removeAt(0) - } - - val branchName = "refs/heads/${branchSplit.joinToString("/")}" - - val refSpec = RefSpec() - .setSource(null) - .setDestination(branchName) - - val pushResult = git.push() - .setTransportConfigCallback { - handleTransportCredentials(it) - } - .setRefSpecs(refSpec) - .setRemote(remoteName) - .call() - - val results = - pushResult.map { it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected } } - .flatten() - if (results.isNotEmpty()) { - val error = StringBuilder() - - results.forEach { result -> - error.append(result.statusMessage) - error.append("\n") - } - - throw Exception(error.toString()) - } - - git - .branchDelete() - .setBranchNames(ref.name) - .call() - } - - private val RemoteRefUpdate.Status.isRejected: Boolean - get() { - return this == RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD || - this == RemoteRefUpdate.Status.REJECTED_NODELETE || - this == RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED || - this == RemoteRefUpdate.Status.REJECTED_OTHER_REASON - } - - private val RemoteRefUpdate.statusMessage: String - get() { - return when (this.status) { - RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> "Failed to push some refs to ${this.remoteName}. " + - "Updates were rejected because the remote contains work that you do not have locally. Pulling changes from remote may help." - RemoteRefUpdate.Status.REJECTED_NODELETE -> "Could not delete ref because the remote doesn't support deleting refs." - RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED -> "Ref rejected, old object id in remote has changed." - RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> this.message ?: "Push rejected for unknown reasons." - else -> "" - } - - } - - @OptIn(ExperimentalCoroutinesApi::class) - fun clone(directory: File, url: String): Flow = callbackFlow { - var lastTitle: String = "" - var lastTotalWork = 0 - var progress = 0 - - try { - ensureActive() - trySend(CloneStatus.Cloning("Starting...", progress, lastTotalWork)) - - Git.cloneRepository() - .setDirectory(directory) - .setURI(url) - .setProgressMonitor( - object : ProgressMonitor { - override fun start(totalTasks: Int) { - println("ProgressMonitor Start with total tasks of: $totalTasks") - } - - override fun beginTask(title: String?, totalWork: Int) { - println("ProgressMonitor Begin task with title: $title") - lastTitle = title.orEmpty() - lastTotalWork = totalWork - progress = 0 - trySend(CloneStatus.Cloning(lastTitle, progress, lastTotalWork)) - } - - override fun update(completed: Int) { - println("ProgressMonitor Update $completed") - ensureActive() - - progress += completed - trySend(CloneStatus.Cloning(lastTitle, progress, lastTotalWork)) - } - - override fun endTask() { - println("ProgressMonitor End task") - } - - override fun isCancelled(): Boolean { - return !isActive - } - } - ) - .setTransportConfigCallback { - handleTransportCredentials(it) - } - .call() - - ensureActive() - trySend(CloneStatus.Completed(directory)) - channel.close() - } catch (ex: Exception) { - if (ex.cause?.cause is CancellationException) { - println("Clone cancelled") - } else { - trySend(CloneStatus.Fail(ex.localizedMessage)) - } - - channel.close() - } - - awaitClose() - } -} - -sealed class CloneStatus { - object None : CloneStatus() - data class Cloning(val taskName: String, val progress: Int, val total: Int) : CloneStatus() - object Cancelling : CloneStatus() - data class Fail(val reason: String) : CloneStatus() - data class Completed(val repoDir: File) : CloneStatus() -} \ No newline at end of file diff --git a/src/main/kotlin/app/git/author/LoadAuthorUseCase.kt b/src/main/kotlin/app/git/author/LoadAuthorUseCase.kt new file mode 100644 index 0000000..437c2d6 --- /dev/null +++ b/src/main/kotlin/app/git/author/LoadAuthorUseCase.kt @@ -0,0 +1,31 @@ +package app.git.author + +import app.models.AuthorInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.storage.file.FileBasedConfig +import javax.inject.Inject + +class LoadAuthorUseCase @Inject constructor() { + suspend operator fun invoke(git: Git): AuthorInfo = withContext(Dispatchers.IO) { + val config = git.repository.config + val globalConfig = git.repository.config.baseConfig + + val repoConfig = FileBasedConfig((config as FileBasedConfig).file, git.repository.fs) + repoConfig.load() + + val globalName = globalConfig.getString("user", null, "name") + val globalEmail = globalConfig.getString("user", null, "email") + + val name = repoConfig.getString("user", null, "name") + val email = repoConfig.getString("user", null, "email") + + return@withContext AuthorInfo( + globalName, + globalEmail, + name, + email, + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/author/SaveAuthorUseCase.kt b/src/main/kotlin/app/git/author/SaveAuthorUseCase.kt new file mode 100644 index 0000000..51f66fc --- /dev/null +++ b/src/main/kotlin/app/git/author/SaveAuthorUseCase.kt @@ -0,0 +1,40 @@ +package app.git.author + +import app.models.AuthorInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.storage.file.FileBasedConfig +import javax.inject.Inject + +class SaveAuthorUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, newAuthorInfo: AuthorInfo) = withContext(Dispatchers.IO) { + val config = git.repository.config + val globalConfig = git.repository.config.baseConfig + val repoConfig = FileBasedConfig((config as FileBasedConfig).file, git.repository.fs) + repoConfig.load() + + if (globalConfig is FileBasedConfig) { + globalConfig.setStringProperty("user", null, "name", newAuthorInfo.globalName) + globalConfig.setStringProperty("user", null, "email", newAuthorInfo.globalEmail) + globalConfig.save() + } + + config.setStringProperty("user", null, "name", newAuthorInfo.name) + config.setStringProperty("user", null, "email", newAuthorInfo.email) + config.save() + } +} + +private fun FileBasedConfig.setStringProperty( + section: String, + subsection: String?, + name: String, + value: String?, +) { + if (value == null) { + unset(section, subsection, name) + } else { + setString(section, subsection, name, value) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/branches/CheckoutRefUseCase.kt b/src/main/kotlin/app/git/branches/CheckoutRefUseCase.kt new file mode 100644 index 0000000..a3ff904 --- /dev/null +++ b/src/main/kotlin/app/git/branches/CheckoutRefUseCase.kt @@ -0,0 +1,25 @@ +package app.git.branches + +import app.extensions.isBranch +import app.extensions.simpleName +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.CreateBranchCommand +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import javax.inject.Inject + +class CheckoutRefUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, ref: Ref): Unit = 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/branches/CreateBranchOnCommitUseCase.kt b/src/main/kotlin/app/git/branches/CreateBranchOnCommitUseCase.kt new file mode 100644 index 0000000..8129b14 --- /dev/null +++ b/src/main/kotlin/app/git/branches/CreateBranchOnCommitUseCase.kt @@ -0,0 +1,19 @@ +package app.git.branches + +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 CreateBranchOnCommitUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, branch: String, revCommit: RevCommit): Ref = withContext(Dispatchers.IO) { + git + .checkout() + .setCreateBranch(true) + .setName(branch) + .setStartPoint(revCommit) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/branches/CreateBranchUseCase.kt b/src/main/kotlin/app/git/branches/CreateBranchUseCase.kt new file mode 100644 index 0000000..8543e4f --- /dev/null +++ b/src/main/kotlin/app/git/branches/CreateBranchUseCase.kt @@ -0,0 +1,17 @@ +package app.git.branches + +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 CreateBranchUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, branchName: String): Ref = withContext(Dispatchers.IO) { + git + .checkout() + .setCreateBranch(true) + .setName(branchName) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/branches/DeleteBranchUseCase.kt b/src/main/kotlin/app/git/branches/DeleteBranchUseCase.kt new file mode 100644 index 0000000..8696d6c --- /dev/null +++ b/src/main/kotlin/app/git/branches/DeleteBranchUseCase.kt @@ -0,0 +1,17 @@ +package app.git.branches + +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 DeleteBranchUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, branch: Ref): List = withContext(Dispatchers.IO) { + git + .branchDelete() + .setBranchNames(branch.name) + .setForce(true) // TODO Should it be forced? + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/branches/DeleteLocallyRemoteBranches.kt b/src/main/kotlin/app/git/branches/DeleteLocallyRemoteBranches.kt new file mode 100644 index 0000000..9672c85 --- /dev/null +++ b/src/main/kotlin/app/git/branches/DeleteLocallyRemoteBranches.kt @@ -0,0 +1,16 @@ +package app.git.branches + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import javax.inject.Inject + +class DeleteLocallyRemoteBranches @Inject constructor() { + suspend operator fun invoke(git: Git, branches: List): List = withContext(Dispatchers.IO) { + git + .branchDelete() + .setBranchNames(*branches.toTypedArray()) + .setForce(true) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/branches/GetBranchesUseCase.kt b/src/main/kotlin/app/git/branches/GetBranchesUseCase.kt new file mode 100644 index 0000000..d8a5b98 --- /dev/null +++ b/src/main/kotlin/app/git/branches/GetBranchesUseCase.kt @@ -0,0 +1,15 @@ +package app.git.branches + +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 GetBranchesUseCase @Inject constructor() { + suspend operator fun invoke(git: Git): List = withContext(Dispatchers.IO) { + return@withContext git + .branchList() + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/branches/GetCurrentBranchUseCase.kt b/src/main/kotlin/app/git/branches/GetCurrentBranchUseCase.kt new file mode 100644 index 0000000..bd7932c --- /dev/null +++ b/src/main/kotlin/app/git/branches/GetCurrentBranchUseCase.kt @@ -0,0 +1,31 @@ +package app.git.branches + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import javax.inject.Inject + +/** + * Returns the current branch in [Ref]. If the repository is new, the current branch will be null. + */ +class GetCurrentBranchUseCase @Inject constructor( + private val getBranchesUseCase: GetBranchesUseCase, +) { + suspend operator fun invoke(git: Git): Ref? { + val branchList = getBranchesUseCase(git) + val branchName = git + .repository + .fullBranch + + var branchFound = branchList.firstOrNull { + it.name == branchName + } + + if (branchFound == null) { + branchFound = branchList.firstOrNull { + it.objectId.name == branchName // Try to get the HEAD + } + } + + return branchFound + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/branches/GetRemoteBranchesUseCase.kt b/src/main/kotlin/app/git/branches/GetRemoteBranchesUseCase.kt new file mode 100644 index 0000000..ce57c78 --- /dev/null +++ b/src/main/kotlin/app/git/branches/GetRemoteBranchesUseCase.kt @@ -0,0 +1,17 @@ +package app.git.branches + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.ListBranchCommand +import org.eclipse.jgit.lib.Ref +import javax.inject.Inject + +class GetRemoteBranchesUseCase @Inject constructor() { + suspend operator fun invoke(git: Git): List = withContext(Dispatchers.IO) { + git + .branchList() + .setListMode(ListBranchCommand.ListMode.REMOTE) + .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/CloneRepositoryUseCase.kt b/src/main/kotlin/app/git/remote_operations/CloneRepositoryUseCase.kt new file mode 100644 index 0000000..d3c7e6e --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/CloneRepositoryUseCase.kt @@ -0,0 +1,81 @@ +package app.git.remote_operations + +import app.git.CloneStatus +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.isActive +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.ProgressMonitor +import java.io.File +import javax.inject.Inject + +class CloneRepositoryUseCase @Inject constructor( + private val handleTransportUseCase: HandleTransportUseCase, +) { + @OptIn(ExperimentalCoroutinesApi::class) + operator fun invoke(directory: File, url: String): Flow = callbackFlow { + var lastTitle: String = "" + var lastTotalWork = 0 + var progress = 0 + + try { + ensureActive() + trySend(CloneStatus.Cloning("Starting...", progress, lastTotalWork)) + + Git.cloneRepository() + .setDirectory(directory) + .setURI(url) + .setProgressMonitor( + object : ProgressMonitor { + override fun start(totalTasks: Int) { + println("ProgressMonitor Start with total tasks of: $totalTasks") + } + + override fun beginTask(title: String?, totalWork: Int) { + println("ProgressMonitor Begin task with title: $title") + lastTitle = title.orEmpty() + lastTotalWork = totalWork + progress = 0 + trySend(CloneStatus.Cloning(lastTitle, progress, lastTotalWork)) + } + + override fun update(completed: Int) { + println("ProgressMonitor Update $completed") + ensureActive() + + progress += completed + trySend(CloneStatus.Cloning(lastTitle, progress, lastTotalWork)) + } + + override fun endTask() { + println("ProgressMonitor End task") + } + + override fun isCancelled(): Boolean { + return !isActive + } + } + ) + .setTransportConfigCallback { handleTransportUseCase(it) } + .call() + + ensureActive() + trySend(CloneStatus.Completed(directory)) + channel.close() + } catch (ex: Exception) { + if (ex.cause?.cause is CancellationException) { + println("Clone cancelled") + } else { + trySend(CloneStatus.Fail(ex.localizedMessage)) + } + + channel.close() + } + + awaitClose() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/DeleteRemoteBranchUseCase.kt b/src/main/kotlin/app/git/remote_operations/DeleteRemoteBranchUseCase.kt new file mode 100644 index 0000000..a647623 --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/DeleteRemoteBranchUseCase.kt @@ -0,0 +1,59 @@ +package app.git.remote_operations + +import app.git.branches.DeleteBranchUseCase +import app.git.isRejected +import app.git.statusMessage +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import org.eclipse.jgit.transport.RefSpec +import javax.inject.Inject + +class DeleteRemoteBranchUseCase @Inject constructor( + private val handleTransportUseCase: HandleTransportUseCase, + private val deleteBranchUseCase: DeleteBranchUseCase, +) { + suspend operator fun invoke(git: Git, ref: Ref) { + val branchSplit = ref.name.split("/").toMutableList() + val remoteName = branchSplit[2] // Remote name + repeat(3) { + branchSplit.removeAt(0) + } + + val branchName = "refs/heads/${branchSplit.joinToString("/")}" + + val refSpec = RefSpec() + .setSource(null) + .setDestination(branchName) + + val pushResults = git.push() + .setTransportConfigCallback { + handleTransportUseCase(it) + } + .setRefSpecs(refSpec) + .setRemote(remoteName) + .call() + + val results = pushResults.map { pushResult -> + pushResult.remoteUpdates.filter { remoteRefUpdate -> + remoteRefUpdate.status.isRejected + } + }.flatten() + + if (results.isNotEmpty()) { + val error = StringBuilder() + + results.forEach { result -> + error.append(result.statusMessage) + error.append("\n") + } + + throw Exception(error.toString()) + } + + deleteBranchUseCase(git, ref) +// git +// .branchDelete() +// .setBranchNames(ref.name) +// .call() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/FetchAllBranchesUseCase.kt b/src/main/kotlin/app/git/remote_operations/FetchAllBranchesUseCase.kt new file mode 100644 index 0000000..68afa64 --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/FetchAllBranchesUseCase.kt @@ -0,0 +1,24 @@ +package app.git.remote_operations + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.transport.CredentialsProvider +import javax.inject.Inject + +class FetchAllBranchesUseCase @Inject constructor( + private val handleTransportUseCase: HandleTransportUseCase, +) { + suspend operator fun invoke(git: Git) = withContext(Dispatchers.IO) { + val remotes = git.remoteList().call() + + for (remote in remotes) { + git.fetch() + .setRemote(remote.name) + .setRefSpecs(remote.fetchRefSpecs) + .setTransportConfigCallback { handleTransportUseCase(it) } + .setCredentialsProvider(CredentialsProvider.getDefault()) + .call() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/HandleTransportUseCase.kt b/src/main/kotlin/app/git/remote_operations/HandleTransportUseCase.kt new file mode 100644 index 0000000..a310559 --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/HandleTransportUseCase.kt @@ -0,0 +1,20 @@ +package app.git.remote_operations + +import app.credentials.GSessionManager +import app.credentials.HttpCredentialsProvider +import org.eclipse.jgit.transport.HttpTransport +import org.eclipse.jgit.transport.SshTransport +import org.eclipse.jgit.transport.Transport +import javax.inject.Inject + +class HandleTransportUseCase @Inject constructor( + private val sessionManager: GSessionManager +) { + operator fun invoke(transport: Transport?) { + if (transport is SshTransport) { + transport.sshSessionFactory = sessionManager.generateSshSessionFactory() + } else if (transport is HttpTransport) { + transport.credentialsProvider = HttpCredentialsProvider() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/PullBranchUseCase.kt b/src/main/kotlin/app/git/remote_operations/PullBranchUseCase.kt new file mode 100644 index 0000000..6b3681f --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/PullBranchUseCase.kt @@ -0,0 +1,35 @@ +package app.git.remote_operations + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.RebaseResult +import org.eclipse.jgit.transport.CredentialsProvider +import javax.inject.Inject + +class PullBranchUseCase @Inject constructor( + private val handleTransportUseCase: HandleTransportUseCase, +) { + suspend operator fun invoke(git: Git, rebase: Boolean) = withContext(Dispatchers.IO) { + val pullResult = git + .pull() + .setTransportConfigCallback { handleTransportUseCase(it) } + .setRebase(rebase) + .setCredentialsProvider(CredentialsProvider.getDefault()) + .call() + + if (!pullResult.isSuccessful) { + var message = "Pull failed" + + if (rebase) { + message = when (pullResult.rebaseResult.status) { + RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommited changes" + RebaseResult.Status.CONFLICTS -> "Pull with rebase has conflicts, fix them to continue" + else -> message + } + } + + throw Exception(message) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/PullFromSpecificBranchUseCase.kt b/src/main/kotlin/app/git/remote_operations/PullFromSpecificBranchUseCase.kt new file mode 100644 index 0000000..ba34224 --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/PullFromSpecificBranchUseCase.kt @@ -0,0 +1,40 @@ +package app.git.remote_operations + +import app.extensions.remoteName +import app.extensions.simpleName +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.RebaseResult +import org.eclipse.jgit.lib.Ref +import org.eclipse.jgit.transport.CredentialsProvider +import javax.inject.Inject + +class PullFromSpecificBranchUseCase @Inject constructor( + private val handleTransportUseCase: HandleTransportUseCase, +) { + suspend operator fun invoke(git: Git, rebase: Boolean, remoteBranch: Ref) = withContext(Dispatchers.IO) { + val pullResult = git + .pull() + .setTransportConfigCallback { handleTransportUseCase(it) } + .setRemote(remoteBranch.remoteName) + .setRemoteBranchName(remoteBranch.simpleName) + .setRebase(rebase) + .setCredentialsProvider(CredentialsProvider.getDefault()) + .call() + + if (!pullResult.isSuccessful) { + var message = "Pull failed" // TODO Remove messages from here and pass the result to a custom exception type + + if (rebase) { + message = when (pullResult.rebaseResult.status) { + RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommited changes" + RebaseResult.Status.CONFLICTS -> "Pull with rebase has conflicts, fix them to continue" + else -> message + } + } + + throw Exception(message) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/PushBranchUseCase.kt b/src/main/kotlin/app/git/remote_operations/PushBranchUseCase.kt new file mode 100644 index 0000000..7012360 --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/PushBranchUseCase.kt @@ -0,0 +1,43 @@ +package app.git.remote_operations + +import app.git.isRejected +import app.git.statusMessage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.transport.RefSpec +import javax.inject.Inject + +class PushBranchUseCase @Inject constructor( + private val handleTransportUseCase: HandleTransportUseCase, +) { + suspend operator fun invoke(git: Git, force: Boolean, pushTags: Boolean) = withContext(Dispatchers.IO) { + val currentBranchRefSpec = git.repository.fullBranch + + val pushResult = git + .push() + .setRefSpecs(RefSpec(currentBranchRefSpec)) + .setForce(force) + .apply { + if (pushTags) + setPushTags() + } + .setTransportConfigCallback { handleTransportUseCase(it) } + .call() + + val results = + pushResult.map { it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected } } + .flatten() + if (results.isNotEmpty()) { + val error = StringBuilder() + + results.forEach { result -> + error.append(result.statusMessage) + error.append("\n") + } + + throw Exception(error.toString()) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/git/remote_operations/PushToSpecificBranchUseCase.kt b/src/main/kotlin/app/git/remote_operations/PushToSpecificBranchUseCase.kt new file mode 100644 index 0000000..2524d74 --- /dev/null +++ b/src/main/kotlin/app/git/remote_operations/PushToSpecificBranchUseCase.kt @@ -0,0 +1,47 @@ +package app.git.remote_operations + +import app.extensions.remoteName +import app.extensions.simpleName +import app.git.isRejected +import app.git.statusMessage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref +import org.eclipse.jgit.transport.RefSpec +import javax.inject.Inject + +class PushToSpecificBranchUseCase @Inject constructor( + private val handleTransportUseCase: HandleTransportUseCase, +) { + suspend operator fun invoke(git: Git, force: Boolean, pushTags: Boolean, remoteBranch: Ref) = + withContext(Dispatchers.IO) { + val currentBranchRefSpec = git.repository.fullBranch + + val pushResult = git + .push() + .setRefSpecs(RefSpec("$currentBranchRefSpec:${remoteBranch.simpleName}")) + .setRemote(remoteBranch.remoteName) + .setForce(force) + .apply { + if (pushTags) + setPushTags() + } + .setTransportConfigCallback { handleTransportUseCase(it) } + .call() + + val results = + pushResult.map { it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected } } + .flatten() + if (results.isNotEmpty()) { + val error = StringBuilder() + + results.forEach { result -> + error.append(result.statusMessage) + error.append("\n") + } + + throw Exception(error.toString()) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/viewmodels/AuthorViewModel.kt b/src/main/kotlin/app/viewmodels/AuthorViewModel.kt index 7abe2bb..9157ec5 100644 --- a/src/main/kotlin/app/viewmodels/AuthorViewModel.kt +++ b/src/main/kotlin/app/viewmodels/AuthorViewModel.kt @@ -1,9 +1,10 @@ package app.viewmodels import app.extensions.nullIfEmpty -import app.git.AuthorManager import app.git.RefreshType import app.git.TabState +import app.git.author.LoadAuthorUseCase +import app.git.author.SaveAuthorUseCase import app.models.AuthorInfo import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -11,7 +12,8 @@ import javax.inject.Inject class AuthorViewModel @Inject constructor( private val tabState: TabState, - private val authorManager: AuthorManager, + private val saveAuthorUseCase: SaveAuthorUseCase, + private val loadAuthorUseCase: LoadAuthorUseCase, ) { private val _authorInfo = MutableStateFlow(AuthorInfo(null, null, null, null)) @@ -21,7 +23,7 @@ class AuthorViewModel @Inject constructor( refreshType = RefreshType.NONE, showError = true, ) { git -> - _authorInfo.value = authorManager.loadAuthor(git) + _authorInfo.value = loadAuthorUseCase(git) } fun saveAuthorInfo(globalName: String, globalEmail: String, name: String, email: String) = tabState.runOperation( @@ -35,6 +37,6 @@ class AuthorViewModel @Inject constructor( email.nullIfEmpty, ) - authorManager.saveAuthorInfo(git, newAuthorInfo) + saveAuthorUseCase(git, newAuthorInfo) } } diff --git a/src/main/kotlin/app/viewmodels/BranchesViewModel.kt b/src/main/kotlin/app/viewmodels/BranchesViewModel.kt index af7a3c0..8a06a09 100644 --- a/src/main/kotlin/app/viewmodels/BranchesViewModel.kt +++ b/src/main/kotlin/app/viewmodels/BranchesViewModel.kt @@ -1,6 +1,9 @@ package app.viewmodels import app.git.* +import app.git.branches.* +import app.git.remote_operations.PullFromSpecificBranchUseCase +import app.git.remote_operations.PushToSpecificBranchUseCase import app.preferences.AppSettings import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -9,12 +12,17 @@ import org.eclipse.jgit.lib.Ref import javax.inject.Inject class BranchesViewModel @Inject constructor( - private val branchesManager: BranchesManager, private val rebaseManager: RebaseManager, private val mergeManager: MergeManager, - private val remoteOperationsManager: RemoteOperationsManager, + private val pushToSpecificBranchUseCase: PushToSpecificBranchUseCase, + private val pullFromSpecificBranchUseCase: PullFromSpecificBranchUseCase, private val tabState: TabState, private val appSettings: AppSettings, + private val getCurrentBranchUseCase: GetCurrentBranchUseCase, + private val getBranchesUseCase: GetBranchesUseCase, + private val createBranchUseCase: CreateBranchUseCase, + private val deleteBranchUseCase: DeleteBranchUseCase, + private val checkoutRefUseCase: CheckoutRefUseCase, ) : ExpandableViewModel(true) { private val _branches = MutableStateFlow>(listOf()) val branches: StateFlow> @@ -25,9 +33,9 @@ class BranchesViewModel @Inject constructor( get() = _currentBranch suspend fun loadBranches(git: Git) { - _currentBranch.value = branchesManager.currentBranchRef(git) + _currentBranch.value = getCurrentBranchUseCase(git) - val branchesList = branchesManager.getBranches(git) + val branchesList = getBranchesUseCase(git).toMutableList() // set selected branch as the first one always val selectedBranch = branchesList.find { it.name == _currentBranch.value?.name } @@ -44,7 +52,7 @@ class BranchesViewModel @Inject constructor( refreshType = RefreshType.ONLY_LOG, refreshEvenIfCrashes = true, ) { git -> - branchesManager.createBranch(git, branchName) + createBranchUseCase(git, branchName) this.loadBranches(git) } @@ -57,13 +65,13 @@ class BranchesViewModel @Inject constructor( fun deleteBranch(branch: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - branchesManager.deleteBranch(git, branch) + deleteBranchUseCase(git, branch) } fun checkoutRef(ref: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - branchesManager.checkoutRef(git, ref) + checkoutRefUseCase(git, ref) } suspend fun refresh(git: Git) { @@ -83,7 +91,7 @@ class BranchesViewModel @Inject constructor( fun pushToRemoteBranch(branch: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - remoteOperationsManager.pushToBranch( + pushToSpecificBranchUseCase( git = git, force = false, pushTags = false, @@ -94,7 +102,7 @@ class BranchesViewModel @Inject constructor( fun pullFromRemoteBranch(branch: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - remoteOperationsManager.pullFromBranch( + pullFromSpecificBranchUseCase( git = git, rebase = false, remoteBranch = branch, diff --git a/src/main/kotlin/app/viewmodels/CloneViewModel.kt b/src/main/kotlin/app/viewmodels/CloneViewModel.kt index 57c3a53..e7ee78b 100644 --- a/src/main/kotlin/app/viewmodels/CloneViewModel.kt +++ b/src/main/kotlin/app/viewmodels/CloneViewModel.kt @@ -1,8 +1,8 @@ package app.viewmodels import app.git.CloneStatus -import app.git.RemoteOperationsManager import app.git.TabState +import app.git.remote_operations.CloneRepositoryUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -14,7 +14,7 @@ import javax.inject.Inject class CloneViewModel @Inject constructor( private val tabState: TabState, - private val remoteOperationsManager: RemoteOperationsManager, + private val cloneRepositoryUseCase: CloneRepositoryUseCase, ) { private val _cloneStatus = MutableStateFlow(CloneStatus.None) @@ -69,7 +69,7 @@ class CloneViewModel @Inject constructor( repoDir.mkdir() } - remoteOperationsManager.clone(repoDir, url) + cloneRepositoryUseCase(repoDir, url) .flowOn(Dispatchers.IO) .collect { newCloneStatus -> _cloneStatus.value = newCloneStatus diff --git a/src/main/kotlin/app/viewmodels/LogViewModel.kt b/src/main/kotlin/app/viewmodels/LogViewModel.kt index ae9937d..aa991f1 100644 --- a/src/main/kotlin/app/viewmodels/LogViewModel.kt +++ b/src/main/kotlin/app/viewmodels/LogViewModel.kt @@ -4,8 +4,12 @@ import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import app.extensions.delayedStateChange import app.git.* +import app.git.branches.* import app.git.graph.GraphCommitList import app.git.graph.GraphNode +import app.git.remote_operations.DeleteRemoteBranchUseCase +import app.git.remote_operations.PullFromSpecificBranchUseCase +import app.git.remote_operations.PushToSpecificBranchUseCase import app.preferences.AppSettings import app.ui.SelectedItem import app.ui.log.LogDialog @@ -34,11 +38,16 @@ private const val LOG_MIN_TIME_IN_MS_TO_SHOW_LOAD = 500L class LogViewModel @Inject constructor( private val logManager: LogManager, private val statusManager: StatusManager, - private val branchesManager: BranchesManager, + private val getCurrentBranchUseCase: GetCurrentBranchUseCase, + private val checkoutRefUseCase: CheckoutRefUseCase, + private val createBranchOnCommitUseCase: CreateBranchOnCommitUseCase, + private val deleteBranchUseCase: DeleteBranchUseCase, + private val pushToSpecificBranchUseCase: PushToSpecificBranchUseCase, + private val pullFromSpecificBranchUseCase: PullFromSpecificBranchUseCase, + private val deleteRemoteBranchUseCase: DeleteRemoteBranchUseCase, private val rebaseManager: RebaseManager, private val tagsManager: TagsManager, private val mergeManager: MergeManager, - private val remoteOperationsManager: RemoteOperationsManager, private val tabState: TabState, private val appSettings: AppSettings, ) { @@ -83,7 +92,7 @@ class LogViewModel @Inject constructor( _logStatus.value = LogStatus.Loading } ) { - val currentBranch = branchesManager.currentBranchRef(git) + val currentBranch = getCurrentBranchUseCase(git) val statusSummary = statusManager.getStatusSummary( git = git, @@ -113,7 +122,7 @@ class LogViewModel @Inject constructor( fun pushToRemoteBranch(branch: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - remoteOperationsManager.pushToBranch( + pushToSpecificBranchUseCase( git = git, force = false, pushTags = false, @@ -124,7 +133,7 @@ class LogViewModel @Inject constructor( fun pullFromRemoteBranch(branch: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - remoteOperationsManager.pullFromBranch( + pullFromSpecificBranchUseCase( git = git, rebase = false, remoteBranch = branch, @@ -152,7 +161,7 @@ class LogViewModel @Inject constructor( fun checkoutRef(ref: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - branchesManager.checkoutRef(git, ref) + checkoutRefUseCase(git, ref) } fun cherrypickCommit(revCommit: RevCommit) = tabState.safeProcessing( @@ -164,7 +173,7 @@ class LogViewModel @Inject constructor( fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - branchesManager.createBranchOnCommit(git, branch, revCommit) + createBranchOnCommitUseCase(git, branch, revCommit) } fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing( @@ -182,7 +191,7 @@ class LogViewModel @Inject constructor( fun deleteBranch(branch: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - branchesManager.deleteBranch(git, branch) + deleteBranchUseCase(git, branch) } fun deleteTag(tag: Ref) = tabState.safeProcessing( @@ -196,7 +205,7 @@ class LogViewModel @Inject constructor( } private suspend fun uncommitedChangesLoadLog(git: Git) { - val currentBranch = branchesManager.currentBranchRef(git) + val currentBranch = getCurrentBranchUseCase(git) val hasUncommitedChanges = statusManager.hasUncommitedChanges(git) val statsSummary = if (hasUncommitedChanges) { @@ -357,7 +366,7 @@ class LogViewModel @Inject constructor( fun deleteRemoteBranch(branch: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - remoteOperationsManager.deleteBranch(git, branch) + deleteRemoteBranchUseCase(git, branch) } } diff --git a/src/main/kotlin/app/viewmodels/MenuViewModel.kt b/src/main/kotlin/app/viewmodels/MenuViewModel.kt index f788752..0ee09e4 100644 --- a/src/main/kotlin/app/viewmodels/MenuViewModel.kt +++ b/src/main/kotlin/app/viewmodels/MenuViewModel.kt @@ -1,12 +1,18 @@ package app.viewmodels import app.git.* +import app.git.remote_operations.DeleteRemoteBranchUseCase +import app.git.remote_operations.FetchAllBranchesUseCase +import app.git.remote_operations.PullBranchUseCase +import app.git.remote_operations.PushBranchUseCase import java.awt.Desktop import javax.inject.Inject class MenuViewModel @Inject constructor( private val tabState: TabState, - private val remoteOperationsManager: RemoteOperationsManager, + private val pullBranchUseCase: PullBranchUseCase, + private val pushBranchUseCase: PushBranchUseCase, + private val fetchAllBranchesUseCase: FetchAllBranchesUseCase, private val stashManager: StashManager, private val statusManager: StatusManager, ) { @@ -14,21 +20,21 @@ class MenuViewModel @Inject constructor( refreshType = RefreshType.ALL_DATA, refreshEvenIfCrashes = true, ) { git -> - remoteOperationsManager.pull(git, rebase) + pullBranchUseCase(git, rebase) } fun fetchAll() = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, refreshEvenIfCrashes = true, ) { git -> - remoteOperationsManager.fetchAll(git) + fetchAllBranchesUseCase(git) } fun push(force: Boolean = false, pushTags: Boolean = false) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, refreshEvenIfCrashes = true, ) { git -> - remoteOperationsManager.push(git, force, pushTags) + pushBranchUseCase(git, force, pushTags) } fun stash() = tabState.safeProcessing( diff --git a/src/main/kotlin/app/viewmodels/RemotesViewModel.kt b/src/main/kotlin/app/viewmodels/RemotesViewModel.kt index 8f04223..b8b14d4 100644 --- a/src/main/kotlin/app/viewmodels/RemotesViewModel.kt +++ b/src/main/kotlin/app/viewmodels/RemotesViewModel.kt @@ -2,6 +2,9 @@ package app.viewmodels import app.exceptions.InvalidRemoteUrlException import app.git.* +import app.git.branches.DeleteLocallyRemoteBranches +import app.git.branches.GetRemoteBranchesUseCase +import app.git.remote_operations.DeleteRemoteBranchUseCase import app.ui.dialogs.RemoteWrapper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -14,18 +17,19 @@ import javax.inject.Inject class RemotesViewModel @Inject constructor( private val remotesManager: RemotesManager, - private val remoteOperationsManager: RemoteOperationsManager, - private val branchesManager: BranchesManager, + private val deleteRemoteBranchUseCase: DeleteRemoteBranchUseCase, private val tabState: TabState, + private val getRemoteBranchesUseCase: GetRemoteBranchesUseCase, + private val deleteLocallyRemoteBranchesUseCase: DeleteLocallyRemoteBranches, ) : ExpandableViewModel() { private val _remotes = MutableStateFlow>(listOf()) val remotes: StateFlow> get() = _remotes - suspend fun loadRemotes(git: Git) = withContext(Dispatchers.IO) { + private suspend fun loadRemotes(git: Git) = withContext(Dispatchers.IO) { val remotes = git.remoteList() .call() - val allRemoteBranches = branchesManager.remoteBranches(git) + val allRemoteBranches = getRemoteBranchesUseCase(git) remotesManager.loadRemotes(git, allRemoteBranches) val remoteInfoList = remotes.map { remoteConfig -> @@ -45,7 +49,7 @@ class RemotesViewModel @Inject constructor( fun deleteRemoteBranch(ref: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - remoteOperationsManager.deleteBranch(git, ref) + deleteRemoteBranchUseCase(git, ref) } suspend fun refresh(git: Git) = withContext(Dispatchers.IO) { @@ -72,14 +76,14 @@ class RemotesViewModel @Inject constructor( ) { git -> remotesManager.deleteRemote(git, remoteName) - val remoteBranches = branchesManager.remoteBranches(git) + val remoteBranches = getRemoteBranchesUseCase(git) val remoteToDeleteBranchesNames = remoteBranches.filter { it.name.startsWith("refs/remotes/$remoteName/") }.map { it.name } - branchesManager.deleteLocallyRemoteBranches(git, remoteToDeleteBranchesNames) + deleteLocallyRemoteBranchesUseCase(git, remoteToDeleteBranchesNames) } diff --git a/src/main/kotlin/app/viewmodels/TagsViewModel.kt b/src/main/kotlin/app/viewmodels/TagsViewModel.kt index 3bcc701..916f7fb 100644 --- a/src/main/kotlin/app/viewmodels/TagsViewModel.kt +++ b/src/main/kotlin/app/viewmodels/TagsViewModel.kt @@ -1,9 +1,9 @@ package app.viewmodels -import app.git.BranchesManager import app.git.RefreshType import app.git.TabState import app.git.TagsManager +import app.git.branches.CheckoutRefUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -14,8 +14,8 @@ import javax.inject.Inject class TagsViewModel @Inject constructor( private val tabState: TabState, - private val branchesManager: BranchesManager, private val tagsManager: TagsManager, + private val checkoutRefUseCase: CheckoutRefUseCase, ) : ExpandableViewModel() { private val _tags = MutableStateFlow>(listOf()) val tags: StateFlow> @@ -30,7 +30,7 @@ class TagsViewModel @Inject constructor( fun checkoutRef(ref: Ref) = tabState.safeProcessing( refreshType = RefreshType.ALL_DATA, ) { git -> - branchesManager.checkoutRef(git, ref) + checkoutRefUseCase(git, ref) } fun deleteTag(tag: Ref) = tabState.safeProcessing( diff --git a/src/test/kotlin/app/git/BeforeRepoAllTestsExtension.kt b/src/test/kotlin/app/git/BeforeRepoAllTestsExtension.kt index f6dcec0..64883d8 100644 --- a/src/test/kotlin/app/git/BeforeRepoAllTestsExtension.kt +++ b/src/test/kotlin/app/git/BeforeRepoAllTestsExtension.kt @@ -3,6 +3,8 @@ package app.git import app.credentials.GProcess import app.credentials.GRemoteSession import app.credentials.GSessionManager +import app.git.remote_operations.CloneRepositoryUseCase +import app.git.remote_operations.HandleTransportUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flowOn @@ -31,8 +33,8 @@ class BeforeRepoAllTestsExtension : BeforeAllCallback, AfterAllCallback { // The following line registers a callback hook when the root test context is shut down context.root.getStore(GLOBAL).put("gitnuro_tests", this) - val remoteOperationsManager = RemoteOperationsManager(GSessionManager { GRemoteSession { GProcess() } }) - remoteOperationsManager.clone(repoDir, REPO_URL) + val cloneRepositoryUseCase = CloneRepositoryUseCase(HandleTransportUseCase(GSessionManager { GRemoteSession { GProcess() } })) + cloneRepositoryUseCase(repoDir, REPO_URL) .flowOn(Dispatchers.IO) .collect { newCloneStatus -> println("Clonning test repository: $newCloneStatus") diff --git a/src/test/kotlin/app/git/BranchesManagerTest.kt b/src/test/kotlin/app/git/BranchesManagerTest.kt index 806351b..b6ee0f8 100644 --- a/src/test/kotlin/app/git/BranchesManagerTest.kt +++ b/src/test/kotlin/app/git/BranchesManagerTest.kt @@ -1,154 +1,154 @@ -package app.git - -import app.TestUtils.copyDir -import kotlinx.coroutines.runBlocking -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.lib.ObjectId -import org.eclipse.jgit.lib.Repository -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.extension.ExtendWith -import java.io.File - -private const val DEFAULT_REMOTE = "origin" -private const val DEFAULT_PRIMARY_BRANCH = "main" -private const val DEFAULT_SECONDARY_BRANCH = "TestBranch1" - -private const val LOCAL_PREFIX = "refs/heads" -private const val DEFAULT_PRIMARY_BRANCH_FULL_NAME = "$LOCAL_PREFIX/$DEFAULT_PRIMARY_BRANCH" -private const val DEFAULT_SECONDARY_BRANCH_FULL_NAME = "$LOCAL_PREFIX/$DEFAULT_SECONDARY_BRANCH" - -private const val INITIAL_LOCAL_BRANCH_COUNT = 1 -private const val INITIAL_REMOTE_BRANCH_COUNT = 2 - -private const val REMOTE_PREFIX = "refs/remotes/$DEFAULT_REMOTE" - -private val initialRemoteBranches = listOf( - "$REMOTE_PREFIX/$DEFAULT_PRIMARY_BRANCH", - "$REMOTE_PREFIX/$DEFAULT_SECONDARY_BRANCH", -) - -@ExtendWith(BeforeRepoAllTestsExtension::class) -class BranchesManagerTest { - private lateinit var repo: Repository - private lateinit var git: Git - private lateinit var branchesManagerTestDir: File - private val branchesManager = BranchesManager() - - @BeforeEach - fun setUp() { - branchesManagerTestDir = File(tempDir, "branches_manager") - branchesManagerTestDir.mkdir() - - copyDir(repoDir.absolutePath, branchesManagerTestDir.absolutePath) - - repo = RepositoryManager().openRepository(branchesManagerTestDir) - git = Git(repo) - } - - @AfterEach - fun tearDown() { - repo.close() - branchesManagerTestDir.deleteRecursively() - } - - @org.junit.jupiter.api.Test - fun currentBranchRef() = runBlocking { - val currentBranchRef = branchesManager.currentBranchRef(Git(repo)) - assertEquals(currentBranchRef?.name, "refs/heads/$DEFAULT_PRIMARY_BRANCH") - } - - @org.junit.jupiter.api.Test - fun getBranches() = runBlocking { - val branchesManager = BranchesManager() - val branches = branchesManager.getBranches(git) - assertEquals(branches.count(), INITIAL_LOCAL_BRANCH_COUNT) - val containsMain = branches.any { it.name == "refs/heads/$DEFAULT_PRIMARY_BRANCH" } - assert(containsMain) { println("Error: Branch main does not exist") } - } - - @org.junit.jupiter.api.Test - fun checkoutRef() = runBlocking { - val remoteBranchToCheckout = "$REMOTE_PREFIX/$DEFAULT_SECONDARY_BRANCH" - - var currentBranch = branchesManager.currentBranchRef(git) - assertEquals(currentBranch?.name, DEFAULT_PRIMARY_BRANCH_FULL_NAME) - - // Checkout a remote branch - var branchToCheckout = branchesManager.remoteBranches(git).first { it.name == remoteBranchToCheckout } - branchesManager.checkoutRef(git, branchToCheckout) - - currentBranch = branchesManager.currentBranchRef(git) - assertEquals(DEFAULT_SECONDARY_BRANCH_FULL_NAME, currentBranch?.name) - - // Checkout a local branch - branchToCheckout = branchesManager.getBranches(git).first { it.name == DEFAULT_PRIMARY_BRANCH_FULL_NAME } - branchesManager.checkoutRef(git, branchToCheckout) - currentBranch = branchesManager.currentBranchRef(git) - - assertEquals(DEFAULT_PRIMARY_BRANCH_FULL_NAME, currentBranch?.name) - } - - @org.junit.jupiter.api.Test - fun createBranch() = runBlocking { - val branchName = "test" - branchesManager.createBranch(git, branchName) - - val branches = branchesManager.getBranches(git) - assertEquals(INITIAL_LOCAL_BRANCH_COUNT + 1, branches.count()) - val containsNewBranch = branches.any { it.name == "refs/heads/$branchName" } - - assert(containsNewBranch) { println("Error: Branch $branchName does not exist") } - } - - @org.junit.jupiter.api.Test - fun createBranchOnCommit() = runBlocking { - val branchName = "test" - val commitId = "f66757e23dc5c43eccbe84d02c58245406c8f8f4" - - val objectId = ObjectId.fromString(commitId) - val revCommit = repo.parseCommit(objectId) - branchesManager.createBranchOnCommit(git, branchName, revCommit) - - val branches = branchesManager.getBranches(git) - assertEquals(INITIAL_LOCAL_BRANCH_COUNT + 1, branches.count()) - val newBranch = branches.firstOrNull { it.name == "refs/heads/$branchName" } - - assertNotNull(newBranch) - assertEquals(commitId, newBranch?.objectId?.name()) - } - - @org.junit.jupiter.api.Test - fun deleteBranch() = runBlocking { - val branchToDeleteName = "branch_to_delete" - val currentBranch = branchesManager.currentBranchRef(git) // should be "main" - assertNotNull(currentBranch) - - val newBranch = branchesManager.createBranch(git, branchToDeleteName) - branchesManager.checkoutRef(git, currentBranch!!) - - branchesManager.deleteBranch(git, newBranch) - - val branches = branchesManager.getBranches(git) - assertEquals(INITIAL_LOCAL_BRANCH_COUNT, branches.count()) - } - - @org.junit.jupiter.api.Test - fun remoteBranches() = runBlocking { - val remoteBranches = branchesManager.remoteBranches(git) - assertEquals(remoteBranches.count(), INITIAL_REMOTE_BRANCH_COUNT) - remoteBranches.forEach { ref -> - assert(initialRemoteBranches.contains(ref.name)) - } - } - - @org.junit.jupiter.api.Test - fun deleteLocallyRemoteBranches() = runBlocking { - branchesManager.deleteLocallyRemoteBranches(git, initialRemoteBranches) - - val branches = branchesManager.remoteBranches(git) - assertEquals(0, branches.count()) - } -} \ No newline at end of file +//package app.git +// +//import app.TestUtils.copyDir +//import kotlinx.coroutines.runBlocking +//import org.eclipse.jgit.api.Git +//import org.eclipse.jgit.lib.ObjectId +//import org.eclipse.jgit.lib.Repository +//import org.junit.jupiter.api.AfterEach +//import org.junit.jupiter.api.Assertions.assertEquals +//import org.junit.jupiter.api.Assertions.assertNotNull +//import org.junit.jupiter.api.BeforeEach +//import org.junit.jupiter.api.extension.ExtendWith +//import java.io.File +// +//private const val DEFAULT_REMOTE = "origin" +//private const val DEFAULT_PRIMARY_BRANCH = "main" +//private const val DEFAULT_SECONDARY_BRANCH = "TestBranch1" +// +//private const val LOCAL_PREFIX = "refs/heads" +//private const val DEFAULT_PRIMARY_BRANCH_FULL_NAME = "$LOCAL_PREFIX/$DEFAULT_PRIMARY_BRANCH" +//private const val DEFAULT_SECONDARY_BRANCH_FULL_NAME = "$LOCAL_PREFIX/$DEFAULT_SECONDARY_BRANCH" +// +//private const val INITIAL_LOCAL_BRANCH_COUNT = 1 +//private const val INITIAL_REMOTE_BRANCH_COUNT = 2 +// +//private const val REMOTE_PREFIX = "refs/remotes/$DEFAULT_REMOTE" +// +//private val initialRemoteBranches = listOf( +// "$REMOTE_PREFIX/$DEFAULT_PRIMARY_BRANCH", +// "$REMOTE_PREFIX/$DEFAULT_SECONDARY_BRANCH", +//) +// +//@ExtendWith(BeforeRepoAllTestsExtension::class) +//class BranchesManagerTest { +// private lateinit var repo: Repository +// private lateinit var git: Git +// private lateinit var branchesManagerTestDir: File +// private val branchesManager = BranchesManager() +// +// @BeforeEach +// fun setUp() { +// branchesManagerTestDir = File(tempDir, "branches_manager") +// branchesManagerTestDir.mkdir() +// +// copyDir(repoDir.absolutePath, branchesManagerTestDir.absolutePath) +// +// repo = RepositoryManager().openRepository(branchesManagerTestDir) +// git = Git(repo) +// } +// +// @AfterEach +// fun tearDown() { +// repo.close() +// branchesManagerTestDir.deleteRecursively() +// } +// +// @org.junit.jupiter.api.Test +// fun currentBranchRef() = runBlocking { +// val currentBranchRef = branchesManager.currentBranchRef(Git(repo)) +// assertEquals(currentBranchRef?.name, "refs/heads/$DEFAULT_PRIMARY_BRANCH") +// } +// +// @org.junit.jupiter.api.Test +// fun getBranches() = runBlocking { +// val branchesManager = BranchesManager() +// val branches = branchesManager.getBranches(git) +// assertEquals(branches.count(), INITIAL_LOCAL_BRANCH_COUNT) +// val containsMain = branches.any { it.name == "refs/heads/$DEFAULT_PRIMARY_BRANCH" } +// assert(containsMain) { println("Error: Branch main does not exist") } +// } +// +// @org.junit.jupiter.api.Test +// fun checkoutRef() = runBlocking { +// val remoteBranchToCheckout = "$REMOTE_PREFIX/$DEFAULT_SECONDARY_BRANCH" +// +// var currentBranch = branchesManager.currentBranchRef(git) +// assertEquals(currentBranch?.name, DEFAULT_PRIMARY_BRANCH_FULL_NAME) +// +// // Checkout a remote branch +// var branchToCheckout = branchesManager.remoteBranches(git).first { it.name == remoteBranchToCheckout } +// branchesManager.checkoutRef(git, branchToCheckout) +// +// currentBranch = branchesManager.currentBranchRef(git) +// assertEquals(DEFAULT_SECONDARY_BRANCH_FULL_NAME, currentBranch?.name) +// +// // Checkout a local branch +// branchToCheckout = branchesManager.getBranches(git).first { it.name == DEFAULT_PRIMARY_BRANCH_FULL_NAME } +// branchesManager.checkoutRef(git, branchToCheckout) +// currentBranch = branchesManager.currentBranchRef(git) +// +// assertEquals(DEFAULT_PRIMARY_BRANCH_FULL_NAME, currentBranch?.name) +// } +// +// @org.junit.jupiter.api.Test +// fun createBranch() = runBlocking { +// val branchName = "test" +// branchesManager.createBranch(git, branchName) +// +// val branches = branchesManager.getBranches(git) +// assertEquals(INITIAL_LOCAL_BRANCH_COUNT + 1, branches.count()) +// val containsNewBranch = branches.any { it.name == "refs/heads/$branchName" } +// +// assert(containsNewBranch) { println("Error: Branch $branchName does not exist") } +// } +// +// @org.junit.jupiter.api.Test +// fun createBranchOnCommit() = runBlocking { +// val branchName = "test" +// val commitId = "f66757e23dc5c43eccbe84d02c58245406c8f8f4" +// +// val objectId = ObjectId.fromString(commitId) +// val revCommit = repo.parseCommit(objectId) +// branchesManager.createBranchOnCommit(git, branchName, revCommit) +// +// val branches = branchesManager.getBranches(git) +// assertEquals(INITIAL_LOCAL_BRANCH_COUNT + 1, branches.count()) +// val newBranch = branches.firstOrNull { it.name == "refs/heads/$branchName" } +// +// assertNotNull(newBranch) +// assertEquals(commitId, newBranch?.objectId?.name()) +// } +// +// @org.junit.jupiter.api.Test +// fun deleteBranch() = runBlocking { +// val branchToDeleteName = "branch_to_delete" +// val currentBranch = branchesManager.currentBranchRef(git) // should be "main" +// assertNotNull(currentBranch) +// +// val newBranch = branchesManager.createBranch(git, branchToDeleteName) +// branchesManager.checkoutRef(git, currentBranch!!) +// +// branchesManager.deleteBranch(git, newBranch) +// +// val branches = branchesManager.getBranches(git) +// assertEquals(INITIAL_LOCAL_BRANCH_COUNT, branches.count()) +// } +// +// @org.junit.jupiter.api.Test +// fun remoteBranches() = runBlocking { +// val remoteBranches = branchesManager.remoteBranches(git) +// assertEquals(remoteBranches.count(), INITIAL_REMOTE_BRANCH_COUNT) +// remoteBranches.forEach { ref -> +// assert(initialRemoteBranches.contains(ref.name)) +// } +// } +// +// @org.junit.jupiter.api.Test +// fun deleteLocallyRemoteBranches() = runBlocking { +// branchesManager.deleteLocallyRemoteBranches(git, initialRemoteBranches) +// +// val branches = branchesManager.remoteBranches(git) +// assertEquals(0, branches.count()) +// } +//} \ No newline at end of file