Started arch refactor

This commit is contained in:
Abdelilah El Aissaoui 2022-08-20 03:19:31 +02:00
parent cbcb13d730
commit 270951fe66
33 changed files with 844 additions and 653 deletions

View File

@ -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)
}
}
}

View File

@ -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<String>) = 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()
}
}
}

View File

@ -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 -> ""
}
}

View File

@ -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

View File

@ -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)

View File

@ -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<CloneStatus> = 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()
}

View File

@ -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,
)
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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<String> = withContext(Dispatchers.IO) {
git
.branchDelete()
.setBranchNames(branch.name)
.setForce(true) // TODO Should it be forced?
.call()
}
}

View File

@ -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<String>): List<String> = withContext(Dispatchers.IO) {
git
.branchDelete()
.setBranchNames(*branches.toTypedArray())
.setForce(true)
.call()
}
}

View File

@ -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<Ref> = withContext(Dispatchers.IO) {
return@withContext git
.branchList()
.call()
}
}

View File

@ -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
}
}

View File

@ -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<Ref> = withContext(Dispatchers.IO) {
git
.branchList()
.setListMode(ListBranchCommand.ListMode.REMOTE)
.call()
}
}

View File

@ -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<CloneStatus> = 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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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())
}
}
}

View File

@ -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())
}
}
}

View File

@ -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)
}
}

View File

@ -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<List<Ref>>(listOf())
val branches: StateFlow<List<Ref>>
@ -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,

View File

@ -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>(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

View File

@ -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)
}
}

View File

@ -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(

View File

@ -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<List<RemoteView>>(listOf())
val remotes: StateFlow<List<RemoteView>>
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)
}

View File

@ -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<List<Ref>>(listOf())
val tags: StateFlow<List<Ref>>
@ -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(

View File

@ -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")

View File

@ -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())
}
}
//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())
// }
//}