Gitnuro/src/main/kotlin/app/git/GitManager.kt
2021-10-27 03:23:06 +02:00

282 lines
8.5 KiB
Kotlin

package app.git
import app.credentials.CredentialsState
import app.credentials.CredentialsStateManager
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import app.AppStateManager
import app.app.Error
import app.app.ErrorsManager
import app.app.newErrorNow
import org.eclipse.jgit.lib.ObjectId
import java.io.File
import javax.inject.Inject
class GitManager @Inject constructor(
private val statusManager: StatusManager,
private val logManager: LogManager,
private val remoteOperationsManager: RemoteOperationsManager,
private val branchesManager: BranchesManager,
private val stashManager: StashManager,
private val diffManager: DiffManager,
val errorsManager: ErrorsManager,
val appStateManager: AppStateManager,
) {
val repositoryName: String
get() = safeGit.repository.directory.parentFile.name
private val credentialsStateManager = CredentialsStateManager
private val managerScope = CoroutineScope(SupervisorJob())
private val _repositorySelectionStatus = MutableStateFlow<RepositorySelectionStatus>(RepositorySelectionStatus.None)
val repositorySelectionStatus: StateFlow<RepositorySelectionStatus>
get() = _repositorySelectionStatus
private val _processing = MutableStateFlow(false)
val processing: StateFlow<Boolean>
get() = _processing
private val _lastTimeChecked = MutableStateFlow(System.currentTimeMillis())
val lastTimeChecked: StateFlow<Long>
get() = _lastTimeChecked
val stageStatus: StateFlow<StageStatus>
get() = statusManager.stageStatus
val logStatus: StateFlow<LogStatus>
get() = logManager.logStatus
val branches: StateFlow<List<Ref>>
get() = branchesManager.branches
val currentBranch: StateFlow<String>
get() = branchesManager.currentBranch
val stashStatus: StateFlow<StashStatus>
get() = stashManager.stashStatus
val credentialsState: StateFlow<CredentialsState>
get() = credentialsStateManager.credentialsState
private var git: Git? = null
val safeGit: Git
get() {
val git = this.git
if (git == null) {
_repositorySelectionStatus.value = RepositorySelectionStatus.None
throw CancellationException()
} else
return git
}
fun openRepository(directory: String) {
openRepository(File(directory))
}
fun openRepository(directory: File) = managerScope.launch(Dispatchers.IO) {
safeProcessing {
println("Trying to open repository ${directory.absoluteFile}")
val gitDirectory = if (directory.name == ".git") {
directory
} else {
val gitDir = File(directory, ".git")
if (gitDir.exists() && gitDir.isDirectory) {
gitDir
} else
directory
}
val builder = FileRepositoryBuilder()
val repository: Repository = builder.setGitDir(gitDirectory)
.readEnvironment() // scan environment GIT_* variables
.findGitDir() // scan up the file system tree
.build()
try {
repository.workTree // test if repository is valid
_repositorySelectionStatus.value = RepositorySelectionStatus.Open(repository)
git = Git(repository)
onRepositoryChanged(repository.directory.parent)
refreshRepositoryInfo()
} catch (ex: Exception) {
ex.printStackTrace()
onRepositoryChanged(null)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
}
}
}
fun loadLog() = managerScope.launch {
coLoadLog()
}
private suspend fun coLoadLog() {
logManager.loadLog(safeGit)
}
suspend fun loadStatus() {
val hadUncommitedChanges = statusManager.hasUncommitedChanges.value
statusManager.loadStatus(safeGit)
val hasNowUncommitedChanges = statusManager.hasUncommitedChanges.value
// Update the log only if the uncommitedChanges status has changed
if (hasNowUncommitedChanges != hadUncommitedChanges)
coLoadLog()
}
fun stage(diffEntry: DiffEntry) = managerScope.launch {
statusManager.stage(safeGit, diffEntry)
}
fun unstage(diffEntry: DiffEntry) = managerScope.launch {
statusManager.unstage(safeGit, diffEntry)
}
fun commit(message: String) = managerScope.launch {
statusManager.commit(safeGit, message)
logManager.loadLog(safeGit)
}
val hasUncommitedChanges: StateFlow<Boolean>
get() = statusManager.hasUncommitedChanges
suspend fun diffFormat(diffEntryType: DiffEntryType): List<String> {
return diffManager.diffFormat(safeGit, diffEntryType)
}
fun pull() = managerScope.launch {
safeProcessing {
remoteOperationsManager.pull(safeGit)
coLoadLog()
}
}
fun push() = managerScope.launch {
safeProcessing {
remoteOperationsManager.push(safeGit)
coLoadLog()
}
}
private suspend fun refreshRepositoryInfo() {
statusManager.loadHasUncommitedChanges(safeGit)
branchesManager.loadBranches(safeGit)
stashManager.loadStashList(safeGit)
coLoadLog()
}
fun stash() = managerScope.launch {
safeProcessing {
stashManager.stash(safeGit)
loadStatus()
loadLog()
}
}
fun popStash() = managerScope.launch {
safeProcessing {
stashManager.popStash(safeGit)
loadStatus()
loadLog()
}
}
fun createBranch(branchName: String) = managerScope.launch {
safeProcessing {
branchesManager.createBranch(safeGit, branchName)
coLoadLog()
}
}
fun deleteBranch(branch: Ref) = managerScope.launch {
branchesManager.deleteBranch(safeGit, branch)
}
fun resetStaged(diffEntry: DiffEntry) = managerScope.launch {
statusManager.reset(safeGit, diffEntry, staged = true)
}
fun resetUnstaged(diffEntry: DiffEntry) = managerScope.launch {
statusManager.reset(safeGit, diffEntry, staged = false)
}
fun statusShouldBeUpdated() {
_lastTimeChecked.value = System.currentTimeMillis()
}
fun credentialsDenied() {
credentialsStateManager.updateState(CredentialsState.CredentialsDenied)
}
fun credentialsAccepted(user: String, password: String) {
credentialsStateManager.updateState(CredentialsState.CredentialsAccepted(user, password))
}
suspend fun diffListFromCommit(commit: RevCommit): List<DiffEntry> {
return diffManager.commitDiffEntries(safeGit, commit)
}
fun unstageAll() = managerScope.launch {
statusManager.unstageAll(safeGit)
}
fun stageAll() = managerScope.launch {
statusManager.stageAll(safeGit)
}
fun checkoutCommit(revCommit: RevCommit) = managerScope.launch {
safeProcessing {
logManager.checkoutCommit(safeGit, revCommit)
coLoadLog()
}
}
fun createBranchOnCommit(branch: String, revCommit: RevCommit) = managerScope.launch {
safeProcessing {
logManager.createBranchOnCommit(safeGit, branch, revCommit)
coLoadLog()
}
}
fun createTagOnCommit(tag: String, revCommit: RevCommit) = managerScope.launch {
safeProcessing {
logManager.createTagOnCommit(safeGit, tag, revCommit)
coLoadLog()
}
}
var onRepositoryChanged: (path: String?) -> Unit = {}
private suspend fun safeProcessing(callback: suspend () -> Unit) {
_processing.value = true
try {
callback()
} catch (ex: Exception) {
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
} finally {
_processing.value = false
}
}
}
sealed class RepositorySelectionStatus {
object None : RepositorySelectionStatus()
object Loading : RepositorySelectionStatus()
data class Open(val repository: Repository) : RepositorySelectionStatus()
}