Refactored safeProcessing to be able to refresh data even after a crash

This commit is contained in:
Abdelilah El Aissaoui 2022-02-05 02:06:55 +01:00
parent 9105940e00
commit df48b40ce5
12 changed files with 193 additions and 180 deletions

View File

@ -45,21 +45,49 @@ class TabState @Inject constructor(
*/
@set:Synchronized
var operationRunning = false
get() {
return field || mutex.isLocked
}
private val _processing = MutableStateFlow(false)
val processing: StateFlow<Boolean> = _processing
fun safeProcessing(showError: Boolean = true, callback: suspend (git: Git) -> RefreshType) =
fun safeProcessing(
showError: Boolean = true,
refreshType: RefreshType,
refreshEvenIfCrashes: Boolean = false,
callback: suspend (git: Git) -> Unit
) =
managerScope.launch(Dispatchers.IO) {
mutex.withLock {
var hasProcessFailed = false
_processing.value = true
try {
callback(safeGit)
} catch (ex: Exception) {
hasProcessFailed = true
ex.printStackTrace()
if (showError)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
} finally {
_processing.value = false
if (refreshType != RefreshType.NONE && (!hasProcessFailed || refreshEvenIfCrashes))
_refreshData.emit(refreshType)
}
}
}
fun safeProcessingWihoutGit(showError: Boolean = true, callback: suspend () -> Unit) =
managerScope.launch(Dispatchers.IO) {
mutex.withLock {
_processing.value = true
operationRunning = true
try {
val refreshType = callback(safeGit)
if (refreshType != RefreshType.NONE)
_refreshData.emit(refreshType)
callback()
} catch (ex: Exception) {
ex.printStackTrace()
@ -72,44 +100,30 @@ class TabState @Inject constructor(
}
}
fun safeProcessingWihoutGit(showError: Boolean = true, callback: suspend () -> RefreshType) =
managerScope.launch(Dispatchers.IO) {
mutex.withLock {
_processing.value = true
operationRunning = true
fun runOperation(
showError: Boolean = false,
refreshType: RefreshType,
refreshEvenIfCrashes: Boolean = false,
block: suspend (git: Git) -> Unit
) = managerScope.launch(Dispatchers.IO) {
var hasProcessFailed = false
operationRunning = true
try {
val refreshType = callback()
block(safeGit)
if (refreshType != RefreshType.NONE)
_refreshData.emit(refreshType)
} catch (ex: Exception) {
ex.printStackTrace()
hasProcessFailed = true
if (showError)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
} finally {
_processing.value = false
operationRunning = false
}
}
}
fun runOperation(showError: Boolean = false, block: suspend (git: Git) -> RefreshType) =
managerScope.launch(Dispatchers.IO) {
operationRunning = true
try {
val refreshType = block(safeGit)
if (refreshType != RefreshType.NONE)
if (refreshType != RefreshType.NONE && (!hasProcessFailed || refreshEvenIfCrashes))
_refreshData.emit(refreshType)
} catch (ex: Exception) {
ex.printStackTrace()
if (showError)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
} finally {
operationRunning = false
}
}
}

View File

@ -44,7 +44,7 @@ fun Remotes(
RemoteRow(
remote = remoteInfo,
onBranchClicked = { branch -> onBranchClicked(branch) },
onDeleteBranch = { branch -> remotesViewModel.deleteBranch(branch) },
onDeleteBranch = { branch -> remotesViewModel.deleteRemoteBranch(branch) },
onRemoteClicked = { remotesViewModel.onRemoteClicked(remoteInfo) }
)
}

View File

@ -110,7 +110,7 @@ fun UncommitedChanges(
stagedEntriesContextMenuItems(
diffEntry = diffEntry,
onReset = {
statusViewModel.resetUnstaged(diffEntry)
statusViewModel.resetStaged(diffEntry)
},
)
},

View File

@ -37,38 +37,38 @@ class BranchesViewModel @Inject constructor(
_branches.value = branchesList
}
fun createBranch(branchName: String) = tabState.safeProcessing { git ->
fun createBranch(branchName: String) = tabState.safeProcessing(
refreshType = RefreshType.NONE,
) { git ->
branchesManager.createBranch(git, branchName)
this.loadBranches(git)
return@safeProcessing RefreshType.NONE
}
fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing { git ->
fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
mergeManager.mergeBranch(git, ref, fastForward)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteBranch(branch: Ref) = tabState.safeProcessing { git ->
fun deleteBranch(branch: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
branchesManager.deleteBranch(git, branch)
return@safeProcessing RefreshType.ALL_DATA
}
fun checkoutRef(ref: Ref) = tabState.safeProcessing { git ->
fun checkoutRef(ref: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
branchesManager.checkoutRef(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) {
loadBranches(git)
}
fun rebaseBranch(ref: Ref) = tabState.safeProcessing { git ->
fun rebaseBranch(ref: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.rebaseBranch(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
}

View File

@ -16,14 +16,14 @@ class CommitChangesViewModel @Inject constructor(
private val _commitChangesStatus = MutableStateFlow<CommitChangesStatus>(CommitChangesStatus.Loading)
val commitChangesStatus: StateFlow<CommitChangesStatus> = _commitChangesStatus
fun loadChanges(commit: RevCommit) = tabState.runOperation { git ->
fun loadChanges(commit: RevCommit) = tabState.runOperation(
refreshType = RefreshType.NONE,
) { git ->
_commitChangesStatus.value = CommitChangesStatus.Loading
val changes = diffManager.commitDiffEntries(git, commit)
_commitChangesStatus.value = CommitChangesStatus.Loaded(commit, changes)
return@runOperation RefreshType.NONE
}
}

View File

@ -25,7 +25,9 @@ class DiffViewModel @Inject constructor(
)
)
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation { git ->
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation (
refreshType = RefreshType.NONE,
) { git ->
val oldDiffEntryType = _diffResult.value?.diffEntryType
_diffResult.value = null
@ -50,20 +52,18 @@ class DiffViewModel @Inject constructor(
ex.printStackTrace()
_diffResult.value = ViewDiffResult(diffEntryType, DiffResult.Text(emptyList()))
}
return@runOperation RefreshType.NONE
}
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation { git ->
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation (
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.stageHunk(git, diffEntry, hunk)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation { git ->
fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation (
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.unstageHunk(git, diffEntry, hunk)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
}

View File

@ -32,69 +32,69 @@ class LogViewModel @Inject constructor(
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch)
}
fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing { git ->
fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
logManager.checkoutCommit(git, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing { git ->
fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
logManager.revertCommit(git, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing { git ->
fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
logManager.resetToCommit(git, revCommit, resetType = resetType)
return@safeProcessing RefreshType.ALL_DATA
}
fun checkoutRef(ref: Ref) = tabState.safeProcessing { git ->
fun checkoutRef(ref: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
branchesManager.checkoutRef(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing { git ->
fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
branchesManager.createBranchOnCommit(git, branch, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing { git ->
fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
tagsManager.createTagOnCommit(git, tag, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing { git ->
fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
mergeManager.mergeBranch(git, ref, fastForward)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteBranch(branch: Ref) = tabState.safeProcessing { git ->
fun deleteBranch(branch: Ref) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
branchesManager.deleteBranch(git, branch)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteTag(tag: Ref) = tabState.safeProcessing { git ->
fun deleteTag(tag: Ref) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
tagsManager.deleteTag(git, tag)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) {
loadLog(git)
}
fun rebaseBranch(ref: Ref) = tabState.safeProcessing { git ->
fun rebaseBranch(ref: Ref) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.rebaseBranch(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
}

View File

@ -12,39 +12,43 @@ class MenuViewModel @Inject constructor(
private val remoteOperationsManager: RemoteOperationsManager,
private val stashManager: StashManager,
) {
fun pull(rebase: Boolean = false) = tabState.safeProcessing { git ->
fun pull(rebase: Boolean = false) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
refreshEvenIfCrashes = true,
) { git ->
remoteOperationsManager.pull(git, rebase)
return@safeProcessing RefreshType.ALL_DATA
}
fun fetchAll() = tabState.safeProcessing { git ->
fun fetchAll() = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
refreshEvenIfCrashes = true,
) { git ->
remoteOperationsManager.fetchAll(git)
return@safeProcessing RefreshType.ALL_DATA
}
fun push(force: Boolean = false) = tabState.safeProcessing { git ->
fun push(force: Boolean = false) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
refreshEvenIfCrashes = true,
) { git ->
remoteOperationsManager.push(git, force)
return@safeProcessing RefreshType.ALL_DATA
}
fun stash() = tabState.safeProcessing { git ->
fun stash() = tabState.safeProcessing (
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
stashManager.stash(git)
return@safeProcessing RefreshType.UNCOMMITED_CHANGES
}
fun popStash() = tabState.safeProcessing { git ->
fun popStash() = tabState.safeProcessing (
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
stashManager.popStash(git)
return@safeProcessing RefreshType.UNCOMMITED_CHANGES
}
fun openFolderInFileExplorer() = tabState.runOperation(showError = true) { git ->
fun openFolderInFileExplorer() = tabState.runOperation(
showError = true,
refreshType = RefreshType.NONE,
) { git ->
Desktop.getDesktop().open(git.repository.directory.parentFile)
return@runOperation RefreshType.NONE
}
}

View File

@ -39,10 +39,10 @@ class RemotesViewModel @Inject constructor(
_remotes.value = remoteViewList
}
fun deleteBranch(ref: Ref) = tabState.safeProcessing { git ->
fun deleteRemoteBranch(ref: Ref) = tabState.safeProcessing (
refreshType = RefreshType.ALL_DATA,
) { git ->
remoteOperationsManager.deleteBranch(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {

View File

@ -31,42 +31,41 @@ class StatusViewModel @Inject constructor(
private var lastUncommitedChangesState = false
fun stage(diffEntry: DiffEntry) = tabState.runOperation { git ->
fun stage(diffEntry: DiffEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.stage(git, diffEntry)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
fun unstage(diffEntry: DiffEntry) = tabState.runOperation { git ->
fun unstage(diffEntry: DiffEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.unstage(git, diffEntry)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
fun unstageAll() = tabState.safeProcessing { git ->
fun unstageAll() = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.unstageAll(git)
return@safeProcessing RefreshType.UNCOMMITED_CHANGES
}
fun stageAll() = tabState.safeProcessing { git ->
fun stageAll() = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.stageAll(git)
return@safeProcessing RefreshType.UNCOMMITED_CHANGES
}
fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation { git ->
fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.reset(git, diffEntry, staged = true)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
fun resetUnstaged(diffEntry: DiffEntry) = tabState.runOperation { git ->
fun resetUnstaged(diffEntry: DiffEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
statusManager.reset(git, diffEntry, staged = false)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
private suspend fun loadStatus(git: Git) {
@ -90,10 +89,10 @@ class StatusViewModel @Inject constructor(
lastUncommitedChangesState = statusManager.hasUncommitedChanges(git)
}
fun commit(message: String) = tabState.safeProcessing { git ->
fun commit(message: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
statusManager.commit(git, message)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
@ -116,38 +115,38 @@ class StatusViewModel @Inject constructor(
return (hasNowUncommitedChanges != hadUncommitedChanges)
}
fun continueRebase() = tabState.safeProcessing { git ->
fun continueRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.continueRebase(git)
return@safeProcessing RefreshType.ALL_DATA
}
fun abortRebase() = tabState.safeProcessing { git ->
fun abortRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.abortRebase(git)
return@safeProcessing RefreshType.ALL_DATA
}
fun skipRebase() = tabState.safeProcessing { git ->
fun skipRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
rebaseManager.skipRebase(git)
return@safeProcessing RefreshType.ALL_DATA
}
fun abortMerge() = tabState.safeProcessing { git ->
fun abortMerge() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
mergeManager.abortMerge(git)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteFile(diffEntry: DiffEntry) = tabState.runOperation { git ->
fun deleteFile(diffEntry: DiffEntry) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES,
) { git ->
val path = diffEntry.newPath
val fileToDelete = File(git.repository.directory.parent, path)
fileToDelete.delete()
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
}

View File

@ -81,10 +81,10 @@ class TabViewModel @Inject constructor(
}
}
private fun refreshLog() = tabState.runOperation { git ->
private fun refreshLog() = tabState.runOperation(
refreshType = RefreshType.NONE,
) { git ->
logViewModel.refresh(git)
return@runOperation RefreshType.NONE
}
fun openRepository(directory: String) {
@ -125,8 +125,6 @@ class TabViewModel @Inject constructor(
onRepositoryChanged(null)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
}
return@safeProcessingWihoutGit RefreshType.NONE
}
private suspend fun loadRepositoryState(git: Git) = withContext(Dispatchers.IO) {
@ -149,7 +147,9 @@ class TabViewModel @Inject constructor(
}
}
private suspend fun checkUncommitedChanges(isFsChange: Boolean = false) = tabState.runOperation { git ->
private suspend fun checkUncommitedChanges(isFsChange: Boolean = false) = tabState.runOperation(
refreshType = RefreshType.NONE,
) { git ->
val uncommitedChangesStateChanged = statusViewModel.updateHasUncommitedChanges(git)
// Update the log only if the uncommitedChanges status has changed
@ -161,11 +161,11 @@ class TabViewModel @Inject constructor(
// Stashes list should only be updated if we are doing a stash operation, however it's a small operation
// that we can afford to do when doing other operations
stashesViewModel.refresh(git)
return@runOperation RefreshType.NONE
}
private suspend fun refreshRepositoryInfo() = tabState.safeProcessing { git ->
private suspend fun refreshRepositoryInfo() = tabState.safeProcessing (
refreshType = RefreshType.NONE,
){ git ->
logViewModel.refresh(git)
branchesViewModel.refresh(git)
remotesViewModel.refresh(git)
@ -173,8 +173,6 @@ class TabViewModel @Inject constructor(
statusViewModel.refresh(git)
stashesViewModel.refresh(git)
loadRepositoryState(git)
return@safeProcessing RefreshType.NONE
}
fun credentialsDenied() {
@ -198,8 +196,6 @@ class TabViewModel @Inject constructor(
fun clone(directory: File, url: String) = tabState.safeProcessingWihoutGit {
remoteOperationsManager.clone(directory, url)
return@safeProcessingWihoutGit RefreshType.NONE
}
private fun findCommit(git: Git, objectId: ObjectId): RevCommit {
@ -214,15 +210,15 @@ class TabViewModel @Inject constructor(
}
}
fun newSelectedRef(objectId: ObjectId?) = tabState.runOperation { git ->
fun newSelectedRef(objectId: ObjectId?) = tabState.runOperation(
refreshType = RefreshType.NONE,
) { git ->
if (objectId == null) {
newSelectedItem(SelectedItem.None)
} else {
val commit = findCommit(git, objectId)
newSelectedItem(SelectedItem.Ref(commit))
}
return@runOperation RefreshType.NONE
}
fun newSelectedStash(stash: RevCommit) {

View File

@ -27,16 +27,16 @@ class TagsViewModel @Inject constructor(
_tags.value = tagsList
}
fun checkoutRef(ref: Ref) = tabState.safeProcessing { git ->
fun checkoutRef(ref: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
branchesManager.checkoutRef(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteTag(tag: Ref) = tabState.safeProcessing { git ->
fun deleteTag(tag: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA,
) { git ->
tagsManager.deleteTag(git, tag)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) {