Merge branch 'status_optimizations' into main
This commit is contained in:
commit
716d04df9a
@ -44,8 +44,6 @@ class App {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var appPreferences: AppPreferences
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
appComponent.inject(this)
|
appComponent.inject(this)
|
||||||
}
|
}
|
||||||
@ -92,7 +90,7 @@ class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
appScope.cancel("Closing app")
|
appStateManager.cancelCoroutines()
|
||||||
this.exitApplication()
|
this.exitApplication()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +149,7 @@ class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeTab(key: Int) = appScope.launch(Dispatchers.IO) {
|
private fun removeTab(key: Int) = appStateManager.appStateScope.launch(Dispatchers.IO) {
|
||||||
// Stop any running jobs
|
// Stop any running jobs
|
||||||
val tabs = tabsFlow.value
|
val tabs = tabsFlow.value
|
||||||
val tabToRemove = tabs.firstOrNull { it.key == key } ?: return@launch
|
val tabToRemove = tabs.firstOrNull { it.key == key } ?: return@launch
|
||||||
@ -164,7 +162,7 @@ class App {
|
|||||||
tabsFlow.value = tabsFlow.value.filter { tab -> tab.key != key }
|
tabsFlow.value = tabsFlow.value.filter { tab -> tab.key != key }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTab(tabInformation: TabInformation) = appScope.launch(Dispatchers.IO) {
|
fun addTab(tabInformation: TabInformation) = appStateManager.appStateScope.launch(Dispatchers.IO) {
|
||||||
tabsFlow.value = tabsFlow.value.toMutableList().apply { add(tabInformation) }
|
tabsFlow.value = tabsFlow.value.toMutableList().apply { add(tabInformation) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class AppStateManager @Inject constructor(
|
|||||||
val latestOpenedRepositoriesPaths: List<String>
|
val latestOpenedRepositoriesPaths: List<String>
|
||||||
get() = _latestOpenedRepositoriesPaths
|
get() = _latestOpenedRepositoriesPaths
|
||||||
|
|
||||||
private val appStateScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) // TODO Stop this when closing the app
|
val appStateScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
||||||
val latestOpenedRepositoryPath: String
|
val latestOpenedRepositoryPath: String
|
||||||
get() = _latestOpenedRepositoriesPaths.firstOrNull() ?: ""
|
get() = _latestOpenedRepositoriesPaths.firstOrNull() ?: ""
|
||||||
@ -74,4 +74,8 @@ class AppStateManager @Inject constructor(
|
|||||||
_latestOpenedRepositoriesPaths.addAll(repositories)
|
_latestOpenedRepositoriesPaths.addAll(repositories)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancelCoroutines() {
|
||||||
|
appStateScope.cancel("Closing app")
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
package app.exceptions
|
||||||
|
|
||||||
|
class MissingDiffEntryException(msg: String) : GitnuroException(msg)
|
@ -2,14 +2,14 @@ package app.extensions
|
|||||||
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material.icons.filled.Edit
|
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import app.git.StatusEntry
|
||||||
|
import app.git.StatusType
|
||||||
import app.theme.addFile
|
import app.theme.addFile
|
||||||
|
import app.theme.conflictFile
|
||||||
import app.theme.modifyFile
|
import app.theme.modifyFile
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
|
||||||
@ -31,6 +31,26 @@ val DiffEntry.parentDirectoryPath: String
|
|||||||
"${directoryPath}/"
|
"${directoryPath}/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val StatusEntry.parentDirectoryPath: String
|
||||||
|
get() {
|
||||||
|
val pathSplit = this.filePath.split("/").toMutableList()
|
||||||
|
pathSplit.removeLast()
|
||||||
|
|
||||||
|
val directoryPath = pathSplit.joinToString("/")
|
||||||
|
|
||||||
|
return if (directoryPath.isEmpty())
|
||||||
|
""
|
||||||
|
else
|
||||||
|
"${directoryPath}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
val StatusEntry.fileName: String
|
||||||
|
get() {
|
||||||
|
val pathSplit = filePath.split("/")
|
||||||
|
|
||||||
|
return pathSplit.lastOrNull() ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
val DiffEntry.fileName: String
|
val DiffEntry.fileName: String
|
||||||
get() {
|
get() {
|
||||||
val path = if (this.changeType == DiffEntry.ChangeType.DELETE) {
|
val path = if (this.changeType == DiffEntry.ChangeType.DELETE) {
|
||||||
@ -53,6 +73,16 @@ val DiffEntry.filePath: String
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val StatusType.icon: ImageVector
|
||||||
|
get() {
|
||||||
|
return when (this) {
|
||||||
|
StatusType.ADDED -> Icons.Default.Add
|
||||||
|
StatusType.MODIFIED -> Icons.Default.Edit
|
||||||
|
StatusType.REMOVED -> Icons.Default.Delete
|
||||||
|
StatusType.CONFLICTING -> Icons.Default.Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val DiffEntry.icon: ImageVector
|
val DiffEntry.icon: ImageVector
|
||||||
get() {
|
get() {
|
||||||
return when (this.changeType) {
|
return when (this.changeType) {
|
||||||
@ -65,6 +95,17 @@ val DiffEntry.icon: ImageVector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val StatusType.iconColor: Color
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
return when (this) {
|
||||||
|
StatusType.ADDED -> MaterialTheme.colors.addFile
|
||||||
|
StatusType.MODIFIED -> MaterialTheme.colors.modifyFile
|
||||||
|
StatusType.REMOVED -> MaterialTheme.colors.error
|
||||||
|
StatusType.CONFLICTING -> MaterialTheme.colors.conflictFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val DiffEntry.iconColor: Color
|
val DiffEntry.iconColor: Color
|
||||||
@Composable
|
@Composable
|
||||||
get() {
|
get() {
|
||||||
|
@ -2,4 +2,14 @@ package app.extensions
|
|||||||
|
|
||||||
fun <T> List<T>?.countOrZero(): Int {
|
fun <T> List<T>?.countOrZero(): Int {
|
||||||
return this?.count() ?: 0
|
return this?.count() ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> flatListOf(vararg lists: List<T>): List<T> {
|
||||||
|
val flatList = mutableListOf<T>()
|
||||||
|
|
||||||
|
for(list in lists) {
|
||||||
|
flatList.addAll(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flatList
|
||||||
}
|
}
|
@ -2,22 +2,24 @@ package app.git
|
|||||||
|
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
|
||||||
sealed class DiffEntryType(val diffEntry: DiffEntry) {
|
sealed class DiffEntryType() {
|
||||||
class CommitDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
|
class CommitDiff(val diffEntry: DiffEntry) : DiffEntryType()
|
||||||
|
|
||||||
sealed class UnstagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
|
sealed class UncommitedDiff(val statusEntry: StatusEntry) : DiffEntryType()
|
||||||
sealed class StagedDiff(diffEntry: DiffEntry) : DiffEntryType(diffEntry)
|
|
||||||
|
sealed class UnstagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
||||||
|
sealed class StagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State used to represent staged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
|
* State used to represent staged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
|
||||||
*/
|
*/
|
||||||
class UnsafeStagedDiff(diffEntry: DiffEntry) : StagedDiff(diffEntry)
|
class UnsafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
|
||||||
/**
|
/**
|
||||||
* State used to represent unstaged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
|
* State used to represent unstaged changes when the repository state is not [org.eclipse.jgit.lib.RepositoryState.SAFE]
|
||||||
*/
|
*/
|
||||||
class UnsafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry)
|
class UnsafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
|
||||||
|
|
||||||
class SafeStagedDiff(diffEntry: DiffEntry) : StagedDiff(diffEntry)
|
class SafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
|
||||||
class SafeUnstagedDiff(diffEntry: DiffEntry) : UnstagedDiff(diffEntry)
|
class SafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package app.git
|
|||||||
|
|
||||||
import app.di.HunkDiffGeneratorFactory
|
import app.di.HunkDiffGeneratorFactory
|
||||||
import app.di.RawFileManagerFactory
|
import app.di.RawFileManagerFactory
|
||||||
|
import app.exceptions.MissingDiffEntryException
|
||||||
import app.extensions.fullData
|
import app.extensions.fullData
|
||||||
import app.git.diff.DiffResult
|
import app.git.diff.DiffResult
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -17,6 +18,7 @@ import org.eclipse.jgit.revwalk.RevWalk
|
|||||||
import org.eclipse.jgit.treewalk.AbstractTreeIterator
|
import org.eclipse.jgit.treewalk.AbstractTreeIterator
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
||||||
import org.eclipse.jgit.treewalk.FileTreeIterator
|
import org.eclipse.jgit.treewalk.FileTreeIterator
|
||||||
|
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -26,9 +28,9 @@ class DiffManager @Inject constructor(
|
|||||||
private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory,
|
private val hunkDiffGeneratorFactory: HunkDiffGeneratorFactory,
|
||||||
) {
|
) {
|
||||||
suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) {
|
suspend fun diffFormat(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) {
|
||||||
val diffEntry = diffEntryType.diffEntry
|
|
||||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
val repository = git.repository
|
val repository = git.repository
|
||||||
|
val diffEntry: DiffEntry
|
||||||
|
|
||||||
DiffFormatter(byteArrayOutputStream).use { formatter ->
|
DiffFormatter(byteArrayOutputStream).use { formatter ->
|
||||||
formatter.setRepository(repository)
|
formatter.setRepository(repository)
|
||||||
@ -39,6 +41,24 @@ class DiffManager @Inject constructor(
|
|||||||
if (diffEntryType is DiffEntryType.UnstagedDiff)
|
if (diffEntryType is DiffEntryType.UnstagedDiff)
|
||||||
formatter.scan(oldTree, newTree)
|
formatter.scan(oldTree, newTree)
|
||||||
|
|
||||||
|
diffEntry = when (diffEntryType) {
|
||||||
|
is DiffEntryType.CommitDiff -> {
|
||||||
|
diffEntryType.diffEntry
|
||||||
|
}
|
||||||
|
is DiffEntryType.UncommitedDiff -> {
|
||||||
|
val statusEntry = diffEntryType.statusEntry
|
||||||
|
|
||||||
|
val firstDiffEntry = git.diff()
|
||||||
|
.setPathFilter(PathFilter.create(statusEntry.filePath))
|
||||||
|
.setCached(diffEntryType is DiffEntryType.StagedDiff)
|
||||||
|
.call()
|
||||||
|
.firstOrNull()
|
||||||
|
?: throw MissingDiffEntryException("Diff entry not found")
|
||||||
|
|
||||||
|
firstDiffEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
formatter.format(diffEntry)
|
formatter.format(diffEntry)
|
||||||
|
|
||||||
formatter.flush()
|
formatter.flush()
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package app.git
|
package app.git
|
||||||
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Warning
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -10,17 +7,18 @@ import app.di.RawFileManagerFactory
|
|||||||
import app.extensions.*
|
import app.extensions.*
|
||||||
import app.git.diff.Hunk
|
import app.git.diff.Hunk
|
||||||
import app.git.diff.LineType
|
import app.git.diff.LineType
|
||||||
import app.theme.conflictFile
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.Status
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
import org.eclipse.jgit.diff.RawText
|
import org.eclipse.jgit.diff.RawText
|
||||||
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit
|
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||||
import org.eclipse.jgit.lib.*
|
import org.eclipse.jgit.lib.Constants
|
||||||
import org.eclipse.jgit.submodule.SubmoduleStatusType
|
import org.eclipse.jgit.lib.FileMode
|
||||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator
|
import org.eclipse.jgit.lib.ObjectInserter
|
||||||
|
import org.eclipse.jgit.lib.Repository
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -40,14 +38,14 @@ class StatusManager @Inject constructor(
|
|||||||
return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges()
|
return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun stage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
|
suspend fun stage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||||
if (diffEntry.changeType == DiffEntry.ChangeType.DELETE) {
|
if (statusEntry.statusType == StatusType.REMOVED) {
|
||||||
git.rm()
|
git.rm()
|
||||||
.addFilepattern(diffEntry.filePath)
|
.addFilepattern(statusEntry.filePath)
|
||||||
.call()
|
.call()
|
||||||
} else {
|
} else {
|
||||||
git.add()
|
git.add()
|
||||||
.addFilepattern(diffEntry.filePath)
|
.addFilepattern(statusEntry.filePath)
|
||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +130,7 @@ class StatusManager @Inject constructor(
|
|||||||
// Restore previously removed lines to the index
|
// Restore previously removed lines to the index
|
||||||
for (line in removedLines) {
|
for (line in removedLines) {
|
||||||
// Check how many lines before this one have been deleted
|
// Check how many lines before this one have been deleted
|
||||||
val previouslyRemovedLines = addedLines.count { it.newLineNumber <= line.newLineNumber } - 1
|
val previouslyRemovedLines = addedLines.count { it.newLineNumber < line.newLineNumber }
|
||||||
textLines.add(line.newLineNumber + linesAdded - previouslyRemovedLines, line.text.withoutLineEnding)
|
textLines.add(line.newLineNumber + linesAdded - previouslyRemovedLines, line.text.withoutLineEnding)
|
||||||
linesAdded++
|
linesAdded++
|
||||||
}
|
}
|
||||||
@ -182,9 +180,9 @@ class StatusManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unstage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
|
suspend fun unstage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||||
git.reset()
|
git.reset()
|
||||||
.addPath(diffEntry.filePath)
|
.addPath(statusEntry.filePath)
|
||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,17 +194,17 @@ class StatusManager @Inject constructor(
|
|||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reset(git: Git, diffEntry: DiffEntry, staged: Boolean) = withContext(Dispatchers.IO) {
|
suspend fun reset(git: Git, statusEntry: StatusEntry, staged: Boolean) = withContext(Dispatchers.IO) {
|
||||||
if (staged) {
|
if (staged) {
|
||||||
git
|
git
|
||||||
.reset()
|
.reset()
|
||||||
.addPath(diffEntry.filePath)
|
.addPath(statusEntry.filePath)
|
||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
git
|
git
|
||||||
.checkout()
|
.checkout()
|
||||||
.addPath(diffEntry.filePath)
|
.addPath(statusEntry.filePath)
|
||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,99 +221,86 @@ class StatusManager @Inject constructor(
|
|||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) =
|
suspend fun getStatus(git: Git) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val statusEntries: List<StatusEntry>
|
git
|
||||||
|
.status()
|
||||||
val status = git.diff()
|
|
||||||
.setShowNameAndStatusOnly(true).apply {
|
|
||||||
if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing)
|
|
||||||
setOldTree(EmptyTreeIterator()) // Required if the repository is empty
|
|
||||||
|
|
||||||
setCached(true)
|
|
||||||
}
|
|
||||||
.call()
|
.call()
|
||||||
|
|
||||||
statusEntries = if (repositoryState.isMerging || repositoryState.isRebasing) {
|
|
||||||
status.groupBy {
|
|
||||||
if (it.newPath != "/dev/null")
|
|
||||||
it.newPath
|
|
||||||
else
|
|
||||||
it.oldPath
|
|
||||||
}
|
|
||||||
.map {
|
|
||||||
val entries = it.value
|
|
||||||
|
|
||||||
val hasConflicts =
|
|
||||||
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
|
|
||||||
|
|
||||||
StatusEntry(entries.first(), isConflict = hasConflicts)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
status.map {
|
|
||||||
StatusEntry(it, isConflict = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return@withContext statusEntries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUnstaged(git: Git, repositoryState: RepositoryState) = withContext(Dispatchers.IO) {
|
suspend fun getStaged(status: Status) =
|
||||||
val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git)
|
withContext(Dispatchers.IO) {
|
||||||
|
val added = status.added.map {
|
||||||
return@withContext git
|
StatusEntry(it, StatusType.ADDED)
|
||||||
.diff()
|
|
||||||
.call()
|
|
||||||
.filter {
|
|
||||||
!uninitializedSubmodules.containsKey(it.oldPath) // Filter out uninitialized modules directories
|
|
||||||
}
|
}
|
||||||
.groupBy {
|
val modified = status.changed.map {
|
||||||
if (it.oldPath != "/dev/null")
|
StatusEntry(it, StatusType.MODIFIED)
|
||||||
it.oldPath
|
|
||||||
else
|
|
||||||
it.newPath
|
|
||||||
}
|
}
|
||||||
.map {
|
val removed = status.removed.map {
|
||||||
val entries = it.value
|
StatusEntry(it, StatusType.REMOVED)
|
||||||
|
|
||||||
val hasConflicts =
|
|
||||||
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
|
|
||||||
|
|
||||||
StatusEntry(entries.first(), isConflict = hasConflicts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return@withContext flatListOf(
|
||||||
|
added,
|
||||||
|
modified,
|
||||||
|
removed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUnstaged(status: Status) = withContext(Dispatchers.IO) {
|
||||||
|
// TODO Test uninitialized modules after the refactor
|
||||||
|
// val uninitializedSubmodules = submodulesManager.uninitializedSubmodules(git)
|
||||||
|
|
||||||
|
val added = status.untracked.map {
|
||||||
|
StatusEntry(it, StatusType.ADDED)
|
||||||
|
}
|
||||||
|
val modified = status.modified.map {
|
||||||
|
StatusEntry(it, StatusType.MODIFIED)
|
||||||
|
}
|
||||||
|
val removed = status.missing.map {
|
||||||
|
StatusEntry(it, StatusType.REMOVED)
|
||||||
|
}
|
||||||
|
val conflicting = status.conflicting.map {
|
||||||
|
StatusEntry(it, StatusType.CONFLICTING)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext flatListOf(
|
||||||
|
added,
|
||||||
|
modified,
|
||||||
|
removed,
|
||||||
|
conflicting,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getStatusSummary(git: Git, currentBranch: Ref?, repositoryState: RepositoryState): StatusSummary {
|
suspend fun getStatusSummary(git: Git): StatusSummary {
|
||||||
val staged = getStaged(git, currentBranch, repositoryState)
|
val status = getStatus(git)
|
||||||
|
val staged = getStaged(status)
|
||||||
val allChanges = staged.toMutableList()
|
val allChanges = staged.toMutableList()
|
||||||
|
|
||||||
val unstaged = getUnstaged(git, repositoryState)
|
val unstaged = getUnstaged(status)
|
||||||
|
|
||||||
allChanges.addAll(unstaged)
|
allChanges.addAll(unstaged)
|
||||||
val groupedChanges = allChanges.groupBy {
|
val groupedChanges = allChanges.groupBy {
|
||||||
if (it.diffEntry.newPath != "/dev/null")
|
|
||||||
it.diffEntry.newPath
|
|
||||||
else
|
|
||||||
it.diffEntry.oldPath
|
|
||||||
}
|
}
|
||||||
val changesGrouped = groupedChanges.map {
|
val changesGrouped = groupedChanges.map {
|
||||||
it.value
|
it.value
|
||||||
}.flatten()
|
}.flatten()
|
||||||
.groupBy {
|
.groupBy {
|
||||||
it.diffEntry.changeType
|
it.statusType
|
||||||
}
|
}
|
||||||
|
|
||||||
val deletedCount = changesGrouped[DiffEntry.ChangeType.DELETE].countOrZero()
|
val deletedCount = changesGrouped[StatusType.REMOVED].countOrZero()
|
||||||
val addedCount = changesGrouped[DiffEntry.ChangeType.ADD].countOrZero()
|
val addedCount = changesGrouped[StatusType.ADDED].countOrZero()
|
||||||
|
|
||||||
val modifiedCount = changesGrouped[DiffEntry.ChangeType.MODIFY].countOrZero() +
|
val modifiedCount = changesGrouped[StatusType.MODIFIED].countOrZero()
|
||||||
changesGrouped[DiffEntry.ChangeType.RENAME].countOrZero() +
|
val conflictingCount = changesGrouped[StatusType.CONFLICTING].countOrZero()
|
||||||
changesGrouped[DiffEntry.ChangeType.COPY].countOrZero()
|
|
||||||
|
|
||||||
return StatusSummary(
|
return StatusSummary(
|
||||||
modifiedCount = modifiedCount,
|
modifiedCount = modifiedCount,
|
||||||
deletedCount = deletedCount,
|
deletedCount = deletedCount,
|
||||||
addedCount = addedCount,
|
addedCount = addedCount,
|
||||||
|
conflictingCount = conflictingCount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,22 +326,30 @@ class StatusManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class StatusEntry(val diffEntry: DiffEntry, val isConflict: Boolean) {
|
data class StatusEntry(val filePath: String, val statusType: StatusType) {
|
||||||
val icon: ImageVector
|
val icon: ImageVector
|
||||||
get() {
|
get() = statusType.icon
|
||||||
return if (isConflict)
|
|
||||||
Icons.Default.Warning
|
|
||||||
else
|
|
||||||
diffEntry.icon
|
|
||||||
}
|
|
||||||
val iconColor: Color
|
val iconColor: Color
|
||||||
@Composable
|
@Composable
|
||||||
get() {
|
get() = statusType.iconColor
|
||||||
return if (isConflict)
|
|
||||||
MaterialTheme.colors.conflictFile
|
|
||||||
else
|
|
||||||
diffEntry.iconColor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StatusSummary(val modifiedCount: Int, val deletedCount: Int, val addedCount: Int)
|
enum class StatusType {
|
||||||
|
ADDED,
|
||||||
|
MODIFIED,
|
||||||
|
REMOVED,
|
||||||
|
CONFLICTING,
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StatusSummary(
|
||||||
|
val modifiedCount: Int,
|
||||||
|
val deletedCount: Int,
|
||||||
|
val addedCount: Int,
|
||||||
|
val conflictingCount: Int,
|
||||||
|
) {
|
||||||
|
val total = modifiedCount +
|
||||||
|
deletedCount +
|
||||||
|
addedCount +
|
||||||
|
conflictingCount
|
||||||
|
}
|
@ -50,15 +50,15 @@ class HunkDiffGenerator @AssistedInject constructor(
|
|||||||
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
|
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
|
||||||
throw InvalidObjectException("Invalid object in diff format")
|
throw InvalidObjectException("Invalid object in diff format")
|
||||||
|
|
||||||
var diffResult: DiffResult = DiffResult.Text(emptyList())
|
var diffResult: DiffResult = DiffResult.Text(ent, emptyList())
|
||||||
|
|
||||||
// If we can, generate text diff (if one of the files has never been a binary file)
|
// If we can, generate text diff (if one of the files has never been a binary file)
|
||||||
val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText ->
|
val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText ->
|
||||||
diffResult = DiffResult.Text(format(fileHeader, oldRawText, newRawText))
|
diffResult = DiffResult.Text(ent, format(fileHeader, oldRawText, newRawText))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasGeneratedTextDiff) {
|
if (!hasGeneratedTextDiff) {
|
||||||
diffResult = DiffResult.NonText(rawOld, rawNew)
|
diffResult = DiffResult.NonText(ent, rawOld, rawNew)
|
||||||
}
|
}
|
||||||
|
|
||||||
return diffResult
|
return diffResult
|
||||||
@ -190,10 +190,17 @@ class HunkDiffGenerator @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class DiffResult {
|
sealed class DiffResult(
|
||||||
data class Text(val hunks: List<Hunk>) : DiffResult()
|
val diffEntry: DiffEntry,
|
||||||
data class NonText(
|
) {
|
||||||
|
class Text(
|
||||||
|
diffEntry: DiffEntry,
|
||||||
|
val hunks: List<Hunk>
|
||||||
|
) : DiffResult(diffEntry)
|
||||||
|
|
||||||
|
class NonText(
|
||||||
|
diffEntry: DiffEntry,
|
||||||
val oldBinaryContent: EntryContent,
|
val oldBinaryContent: EntryContent,
|
||||||
val newBinaryContent: EntryContent,
|
val newBinaryContent: EntryContent,
|
||||||
) : DiffResult()
|
) : DiffResult(diffEntry)
|
||||||
}
|
}
|
@ -197,7 +197,7 @@ fun CommitLogChanges(
|
|||||||
val textColor: Color
|
val textColor: Color
|
||||||
val secondaryTextColor: Color
|
val secondaryTextColor: Color
|
||||||
|
|
||||||
if (diffSelected?.diffEntry == diffEntry) {
|
if (diffSelected is DiffEntryType.CommitDiff && diffSelected.diffEntry == diffEntry) {
|
||||||
textColor = MaterialTheme.colors.primary
|
textColor = MaterialTheme.colors.primary
|
||||||
secondaryTextColor = MaterialTheme.colors.halfPrimary
|
secondaryTextColor = MaterialTheme.colors.halfPrimary
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,9 +7,11 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.text.selection.DisableSelection
|
import androidx.compose.foundation.text.selection.DisableSelection
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -23,6 +25,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import app.git.DiffEntryType
|
import app.git.DiffEntryType
|
||||||
import app.git.EntryContent
|
import app.git.EntryContent
|
||||||
|
import app.git.StatusType
|
||||||
import app.git.diff.DiffResult
|
import app.git.diff.DiffResult
|
||||||
import app.git.diff.Hunk
|
import app.git.diff.Hunk
|
||||||
import app.git.diff.Line
|
import app.git.diff.Line
|
||||||
@ -33,6 +36,7 @@ import app.theme.unstageButton
|
|||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
import app.ui.components.SecondaryButton
|
import app.ui.components.SecondaryButton
|
||||||
import app.viewmodels.DiffViewModel
|
import app.viewmodels.DiffViewModel
|
||||||
|
import app.viewmodels.ViewDiffResult
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -47,22 +51,33 @@ fun Diff(
|
|||||||
val diffResultState = diffViewModel.diffResult.collectAsState()
|
val diffResultState = diffViewModel.diffResult.collectAsState()
|
||||||
val viewDiffResult = diffResultState.value ?: return
|
val viewDiffResult = diffResultState.value ?: return
|
||||||
|
|
||||||
val diffEntryType = viewDiffResult.diffEntryType
|
|
||||||
val diffEntry = diffEntryType.diffEntry
|
|
||||||
val diffResult = viewDiffResult.diffResult
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
DiffHeader(diffEntry, onCloseDiffView)
|
when (viewDiffResult) {
|
||||||
if (diffResult is DiffResult.Text) {
|
ViewDiffResult.DiffNotFound -> { onCloseDiffView() }
|
||||||
TextDiff(diffEntryType, diffViewModel, diffResult)
|
is ViewDiffResult.Loaded -> {
|
||||||
} else if (diffResult is DiffResult.NonText) {
|
val diffEntryType = viewDiffResult.diffEntryType
|
||||||
NonTextDiff(diffResult)
|
val diffEntry = viewDiffResult.diffResult.diffEntry
|
||||||
|
val diffResult = viewDiffResult.diffResult
|
||||||
|
|
||||||
|
DiffHeader(diffEntry, onCloseDiffView)
|
||||||
|
|
||||||
|
if (diffResult is DiffResult.Text) {
|
||||||
|
TextDiff(diffEntryType, diffViewModel, diffResult)
|
||||||
|
} else if (diffResult is DiffResult.NonText) {
|
||||||
|
NonTextDiff(diffResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ViewDiffResult.Loading -> {
|
||||||
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,8 +190,9 @@ fun TextDiff(diffEntryType: DiffEntryType, diffViewModel: DiffViewModel, diffRes
|
|||||||
DisableSelection {
|
DisableSelection {
|
||||||
HunkHeader(
|
HunkHeader(
|
||||||
hunk = hunk,
|
hunk = hunk,
|
||||||
diffEntryType = diffEntryType,
|
|
||||||
diffViewModel = diffViewModel,
|
diffViewModel = diffViewModel,
|
||||||
|
diffEntryType = diffEntryType,
|
||||||
|
diffEntry =diffResult.diffEntry,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,6 +216,7 @@ fun HunkHeader(
|
|||||||
hunk: Hunk,
|
hunk: Hunk,
|
||||||
diffEntryType: DiffEntryType,
|
diffEntryType: DiffEntryType,
|
||||||
diffViewModel: DiffViewModel,
|
diffViewModel: DiffViewModel,
|
||||||
|
diffEntry: DiffEntry,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -215,9 +232,12 @@ fun HunkHeader(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
// Hunks options are only visible when repository is a normal state (not during merge/rebase)
|
||||||
if (
|
if (
|
||||||
(diffEntryType is DiffEntryType.SafeStagedDiff || diffEntryType is DiffEntryType.SafeUnstagedDiff) &&
|
(diffEntryType is DiffEntryType.SafeStagedDiff || diffEntryType is DiffEntryType.SafeUnstagedDiff) &&
|
||||||
diffEntryType.diffEntry.changeType == DiffEntry.ChangeType.MODIFY
|
(diffEntryType is DiffEntryType.UncommitedDiff && // Added just to make smartcast work
|
||||||
|
diffEntryType.statusEntry.statusType == StatusType.MODIFIED)
|
||||||
) {
|
) {
|
||||||
val buttonText: String
|
val buttonText: String
|
||||||
val color: Color
|
val color: Color
|
||||||
@ -234,9 +254,9 @@ fun HunkHeader(
|
|||||||
backgroundButton = color,
|
backgroundButton = color,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (diffEntryType is DiffEntryType.StagedDiff) {
|
if (diffEntryType is DiffEntryType.StagedDiff) {
|
||||||
diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk)
|
diffViewModel.unstageHunk(diffEntry, hunk)
|
||||||
} else {
|
} else {
|
||||||
diffViewModel.stageHunk(diffEntryType.diffEntry, hunk)
|
diffViewModel.stageHunk(diffEntry, hunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -45,16 +45,16 @@ import app.ui.context_menu.stagedEntriesContextMenuItems
|
|||||||
import app.ui.context_menu.unstagedEntriesContextMenuItems
|
import app.ui.context_menu.unstagedEntriesContextMenuItems
|
||||||
import app.viewmodels.StageStatus
|
import app.viewmodels.StageStatus
|
||||||
import app.viewmodels.StatusViewModel
|
import app.viewmodels.StatusViewModel
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UncommitedChanges(
|
fun UncommitedChanges(
|
||||||
statusViewModel: StatusViewModel,
|
statusViewModel: StatusViewModel,
|
||||||
selectedEntryType: DiffEntryType?,
|
selectedEntryType: DiffEntryType?,
|
||||||
repositoryState: RepositoryState,
|
repositoryState: RepositoryState,
|
||||||
onStagedDiffEntrySelected: (DiffEntry?) -> Unit,
|
onStagedDiffEntrySelected: (StatusEntry?) -> Unit,
|
||||||
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit,
|
onUnstagedDiffEntrySelected: (StatusEntry) -> Unit,
|
||||||
) {
|
) {
|
||||||
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
||||||
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
||||||
@ -66,18 +66,6 @@ fun UncommitedChanges(
|
|||||||
if (stageStatus is StageStatus.Loaded) {
|
if (stageStatus is StageStatus.Loaded) {
|
||||||
staged = stageStatus.staged
|
staged = stageStatus.staged
|
||||||
unstaged = stageStatus.unstaged
|
unstaged = stageStatus.unstaged
|
||||||
|
|
||||||
LaunchedEffect(staged) {
|
|
||||||
if (selectedEntryType != null) {
|
|
||||||
checkIfSelectedEntryShouldBeUpdated(
|
|
||||||
selectedEntryType = selectedEntryType,
|
|
||||||
staged = staged,
|
|
||||||
unstaged = unstaged,
|
|
||||||
onStagedDiffEntrySelected = onStagedDiffEntrySelected,
|
|
||||||
onUnstagedDiffEntrySelected = onUnstagedDiffEntrySelected,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
staged = listOf()
|
staged = listOf()
|
||||||
unstaged = listOf() // return empty lists if still loading
|
unstaged = listOf() // return empty lists if still loading
|
||||||
@ -110,9 +98,9 @@ fun UncommitedChanges(
|
|||||||
title = "Staged",
|
title = "Staged",
|
||||||
allActionTitle = "Unstage all",
|
allActionTitle = "Unstage all",
|
||||||
actionTitle = "Unstage",
|
actionTitle = "Unstage",
|
||||||
selectedEntryType = selectedEntryType,
|
selectedEntryType = if(selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null,
|
||||||
actionColor = MaterialTheme.colors.unstageButton,
|
actionColor = MaterialTheme.colors.unstageButton,
|
||||||
diffEntries = staged,
|
statusEntries = staged,
|
||||||
onDiffEntrySelected = onStagedDiffEntrySelected,
|
onDiffEntrySelected = onStagedDiffEntrySelected,
|
||||||
onDiffEntryOptionSelected = {
|
onDiffEntryOptionSelected = {
|
||||||
statusViewModel.unstage(it)
|
statusViewModel.unstage(it)
|
||||||
@ -127,7 +115,7 @@ fun UncommitedChanges(
|
|||||||
},
|
},
|
||||||
onAllAction = {
|
onAllAction = {
|
||||||
statusViewModel.unstageAll()
|
statusViewModel.unstageAll()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
EntriesList(
|
EntriesList(
|
||||||
@ -137,20 +125,21 @@ fun UncommitedChanges(
|
|||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
title = "Unstaged",
|
title = "Unstaged",
|
||||||
actionTitle = "Stage",
|
actionTitle = "Stage",
|
||||||
|
selectedEntryType = if(selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null,
|
||||||
actionColor = MaterialTheme.colors.stageButton,
|
actionColor = MaterialTheme.colors.stageButton,
|
||||||
diffEntries = unstaged,
|
statusEntries = unstaged,
|
||||||
onDiffEntrySelected = onUnstagedDiffEntrySelected,
|
onDiffEntrySelected = onUnstagedDiffEntrySelected,
|
||||||
onDiffEntryOptionSelected = {
|
onDiffEntryOptionSelected = {
|
||||||
statusViewModel.stage(it)
|
statusViewModel.stage(it)
|
||||||
},
|
},
|
||||||
onGenerateContextMenu = { diffEntry ->
|
onGenerateContextMenu = { statusEntry ->
|
||||||
unstagedEntriesContextMenuItems(
|
unstagedEntriesContextMenuItems(
|
||||||
diffEntry = diffEntry,
|
statusEntry = statusEntry,
|
||||||
onReset = {
|
onReset = {
|
||||||
statusViewModel.resetUnstaged(diffEntry)
|
statusViewModel.resetUnstaged(statusEntry)
|
||||||
},
|
},
|
||||||
onDelete = {
|
onDelete = {
|
||||||
statusViewModel.deleteFile(diffEntry)
|
statusViewModel.deleteFile(statusEntry)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -158,7 +147,6 @@ fun UncommitedChanges(
|
|||||||
statusViewModel.stageAll()
|
statusViewModel.stageAll()
|
||||||
},
|
},
|
||||||
allActionTitle = "Stage all",
|
allActionTitle = "Stage all",
|
||||||
selectedEntryType = selectedEntryType
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@ -392,46 +380,6 @@ fun ConfirmationButton(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: This logic should be part of the diffViewModel where it gets the latest version of the diffEntry
|
|
||||||
fun checkIfSelectedEntryShouldBeUpdated(
|
|
||||||
selectedEntryType: DiffEntryType,
|
|
||||||
staged: List<StatusEntry>,
|
|
||||||
unstaged: List<StatusEntry>,
|
|
||||||
onStagedDiffEntrySelected: (DiffEntry?) -> Unit,
|
|
||||||
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit,
|
|
||||||
) {
|
|
||||||
val selectedDiffEntry = selectedEntryType.diffEntry
|
|
||||||
val selectedEntryTypeNewId = selectedDiffEntry.newId.name()
|
|
||||||
|
|
||||||
if (selectedEntryType is DiffEntryType.StagedDiff) {
|
|
||||||
val entryType =
|
|
||||||
staged.firstOrNull { stagedEntry -> stagedEntry.diffEntry.newPath == selectedDiffEntry.newPath }?.diffEntry
|
|
||||||
|
|
||||||
if (
|
|
||||||
entryType != null &&
|
|
||||||
selectedEntryTypeNewId != entryType.newId.name()
|
|
||||||
) {
|
|
||||||
onStagedDiffEntrySelected(entryType)
|
|
||||||
|
|
||||||
} else if (entryType == null) {
|
|
||||||
onStagedDiffEntrySelected(null)
|
|
||||||
}
|
|
||||||
} else if (selectedEntryType is DiffEntryType.UnstagedDiff) {
|
|
||||||
val entryType = unstaged.firstOrNull { unstagedEntry ->
|
|
||||||
if (selectedDiffEntry.changeType == DiffEntry.ChangeType.DELETE)
|
|
||||||
unstagedEntry.diffEntry.oldPath == selectedDiffEntry.oldPath
|
|
||||||
else
|
|
||||||
unstagedEntry.diffEntry.newPath == selectedDiffEntry.newPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entryType != null) {
|
|
||||||
onUnstagedDiffEntrySelected(entryType.diffEntry)
|
|
||||||
} else
|
|
||||||
onStagedDiffEntrySelected(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun EntriesList(
|
private fun EntriesList(
|
||||||
@ -439,10 +387,10 @@ private fun EntriesList(
|
|||||||
title: String,
|
title: String,
|
||||||
actionTitle: String,
|
actionTitle: String,
|
||||||
actionColor: Color,
|
actionColor: Color,
|
||||||
diffEntries: List<StatusEntry>,
|
statusEntries: List<StatusEntry>,
|
||||||
onDiffEntrySelected: (DiffEntry) -> Unit,
|
onDiffEntrySelected: (StatusEntry) -> Unit,
|
||||||
onDiffEntryOptionSelected: (DiffEntry) -> Unit,
|
onDiffEntryOptionSelected: (StatusEntry) -> Unit,
|
||||||
onGenerateContextMenu: (DiffEntry) -> List<ContextMenuItem>,
|
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
|
||||||
onAllAction: () -> Unit,
|
onAllAction: () -> Unit,
|
||||||
allActionTitle: String,
|
allActionTitle: String,
|
||||||
selectedEntryType: DiffEntryType?,
|
selectedEntryType: DiffEntryType?,
|
||||||
@ -477,24 +425,25 @@ private fun EntriesList(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
) {
|
) {
|
||||||
itemsIndexed(diffEntries) { index, statusEntry ->
|
itemsIndexed(statusEntries) { index, statusEntry ->
|
||||||
val diffEntry = statusEntry.diffEntry
|
val isEntrySelected = selectedEntryType != null &&
|
||||||
val isEntrySelected = selectedEntryType?.diffEntry == diffEntry
|
selectedEntryType is DiffEntryType.UncommitedDiff && // Added for smartcast
|
||||||
|
selectedEntryType.statusEntry == statusEntry
|
||||||
FileEntry(
|
FileEntry(
|
||||||
statusEntry = statusEntry,
|
statusEntry = statusEntry,
|
||||||
isSelected = isEntrySelected,
|
isSelected = isEntrySelected,
|
||||||
actionTitle = actionTitle,
|
actionTitle = actionTitle,
|
||||||
actionColor = actionColor,
|
actionColor = actionColor,
|
||||||
onClick = {
|
onClick = {
|
||||||
onDiffEntrySelected(diffEntry)
|
onDiffEntrySelected(statusEntry)
|
||||||
},
|
},
|
||||||
onButtonClick = {
|
onButtonClick = {
|
||||||
onDiffEntryOptionSelected(diffEntry)
|
onDiffEntryOptionSelected(statusEntry)
|
||||||
},
|
},
|
||||||
onGenerateContextMenu = onGenerateContextMenu,
|
onGenerateContextMenu = onGenerateContextMenu,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (index < diffEntries.size - 1) {
|
if (index < statusEntries.size - 1) {
|
||||||
Divider(modifier = Modifier.fillMaxWidth())
|
Divider(modifier = Modifier.fillMaxWidth())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -514,10 +463,9 @@ private fun FileEntry(
|
|||||||
actionColor: Color,
|
actionColor: Color,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onButtonClick: () -> Unit,
|
onButtonClick: () -> Unit,
|
||||||
onGenerateContextMenu: (DiffEntry) -> List<ContextMenuItem>,
|
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
|
||||||
) {
|
) {
|
||||||
var active by remember { mutableStateOf(false) }
|
var active by remember { mutableStateOf(false) }
|
||||||
val diffEntry = statusEntry.diffEntry
|
|
||||||
|
|
||||||
val textColor: Color
|
val textColor: Color
|
||||||
val secondaryTextColor: Color
|
val secondaryTextColor: Color
|
||||||
@ -547,7 +495,7 @@ private fun FileEntry(
|
|||||||
) {
|
) {
|
||||||
ContextMenuArea(
|
ContextMenuArea(
|
||||||
items = {
|
items = {
|
||||||
onGenerateContextMenu(diffEntry)
|
onGenerateContextMenu(statusEntry)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@ -566,9 +514,9 @@ private fun FileEntry(
|
|||||||
tint = statusEntry.iconColor,
|
tint = statusEntry.iconColor,
|
||||||
)
|
)
|
||||||
|
|
||||||
if(diffEntry.parentDirectoryPath.isNotEmpty()) {
|
if(statusEntry.parentDirectoryPath.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = diffEntry.parentDirectoryPath,
|
text = statusEntry.parentDirectoryPath,
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
@ -578,7 +526,7 @@ private fun FileEntry(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = diffEntry.fileName,
|
text = statusEntry.fileName,
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
|
@ -2,15 +2,17 @@ package app.ui.context_menu
|
|||||||
|
|
||||||
import androidx.compose.foundation.ContextMenuItem
|
import androidx.compose.foundation.ContextMenuItem
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import app.git.StatusEntry
|
||||||
|
import app.git.StatusType
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun stagedEntriesContextMenuItems(
|
fun stagedEntriesContextMenuItems(
|
||||||
diffEntry: DiffEntry,
|
diffEntry: StatusEntry,
|
||||||
onReset: () -> Unit,
|
onReset: () -> Unit,
|
||||||
): List<ContextMenuItem> {
|
): List<ContextMenuItem> {
|
||||||
return mutableListOf<ContextMenuItem>().apply {
|
return mutableListOf<ContextMenuItem>().apply {
|
||||||
if (diffEntry.changeType != DiffEntry.ChangeType.ADD) {
|
if (diffEntry.statusType != StatusType.ADDED) {
|
||||||
add(
|
add(
|
||||||
ContextMenuItem(
|
ContextMenuItem(
|
||||||
label = "Reset",
|
label = "Reset",
|
||||||
|
@ -2,16 +2,18 @@ package app.ui.context_menu
|
|||||||
|
|
||||||
import androidx.compose.foundation.ContextMenuItem
|
import androidx.compose.foundation.ContextMenuItem
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import app.git.StatusEntry
|
||||||
|
import app.git.StatusType
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun unstagedEntriesContextMenuItems(
|
fun unstagedEntriesContextMenuItems(
|
||||||
diffEntry: DiffEntry,
|
statusEntry: StatusEntry,
|
||||||
onReset: () -> Unit,
|
onReset: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
): List<ContextMenuItem> {
|
): List<ContextMenuItem> {
|
||||||
return mutableListOf<ContextMenuItem>().apply {
|
return mutableListOf<ContextMenuItem>().apply {
|
||||||
if (diffEntry.changeType != DiffEntry.ChangeType.ADD) {
|
if (statusEntry.statusType != StatusType.ADDED) {
|
||||||
add(
|
add(
|
||||||
ContextMenuItem(
|
ContextMenuItem(
|
||||||
label = "Reset",
|
label = "Reset",
|
||||||
@ -20,7 +22,7 @@ fun unstagedEntriesContextMenuItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diffEntry.changeType != DiffEntry.ChangeType.DELETE) {
|
if (statusEntry.statusType != StatusType.REMOVED) {
|
||||||
add(
|
add(
|
||||||
ContextMenuItem(
|
ContextMenuItem(
|
||||||
label = "Delete file",
|
label = "Delete file",
|
||||||
|
@ -590,6 +590,14 @@ fun LogStatusSummary(statusSummary: StatusSummary, modifier: Modifier) {
|
|||||||
color = MaterialTheme.colors.deleteFile,
|
color = MaterialTheme.colors.deleteFile,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (statusSummary.conflictingCount > 0) {
|
||||||
|
SummaryEntry(
|
||||||
|
count = statusSummary.conflictingCount,
|
||||||
|
icon = Icons.Default.Warning,
|
||||||
|
color = MaterialTheme.colors.conflictFile,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import app.exceptions.MissingDiffEntryException
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import app.git.diff.DiffResult
|
import app.git.diff.DiffResult
|
||||||
import app.git.diff.Hunk
|
import app.git.diff.Hunk
|
||||||
@ -14,8 +15,7 @@ class DiffViewModel @Inject constructor(
|
|||||||
private val diffManager: DiffManager,
|
private val diffManager: DiffManager,
|
||||||
private val statusManager: StatusManager,
|
private val statusManager: StatusManager,
|
||||||
) {
|
) {
|
||||||
// TODO Maybe use a sealed class instead of a null to represent that a diff is not selected?
|
private val _diffResult = MutableStateFlow<ViewDiffResult>(ViewDiffResult.Loading)
|
||||||
private val _diffResult = MutableStateFlow<ViewDiffResult?>(null)
|
|
||||||
val diffResult: StateFlow<ViewDiffResult?> = _diffResult
|
val diffResult: StateFlow<ViewDiffResult?> = _diffResult
|
||||||
|
|
||||||
val lazyListState = MutableStateFlow(
|
val lazyListState = MutableStateFlow(
|
||||||
@ -25,18 +25,23 @@ class DiffViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO Cancel job if the user closed the diff view while loading
|
||||||
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation(
|
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation(
|
||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) { git ->
|
) { git ->
|
||||||
val oldDiffEntryType = _diffResult.value?.diffEntryType
|
var oldDiffEntryType: DiffEntryType? = null
|
||||||
|
val oldDiffResult = _diffResult.value
|
||||||
|
|
||||||
_diffResult.value = null
|
if(oldDiffResult is ViewDiffResult.Loaded) {
|
||||||
|
oldDiffEntryType = oldDiffResult.diffEntryType
|
||||||
|
}
|
||||||
|
|
||||||
|
_diffResult.value = ViewDiffResult.Loading
|
||||||
|
|
||||||
// If it's a different file or different state (index or workdir), reset the scroll state
|
// If it's a different file or different state (index or workdir), reset the scroll state
|
||||||
if (oldDiffEntryType != null &&
|
if (oldDiffEntryType != null &&
|
||||||
(oldDiffEntryType.diffEntry.oldPath != diffEntryType.diffEntry.oldPath ||
|
oldDiffEntryType is DiffEntryType.UncommitedDiff && diffEntryType is DiffEntryType.UncommitedDiff &&
|
||||||
oldDiffEntryType.diffEntry.newPath != diffEntryType.diffEntry.newPath ||
|
oldDiffEntryType.statusEntry.filePath != diffEntryType.statusEntry.filePath
|
||||||
oldDiffEntryType::class != diffEntryType::class)
|
|
||||||
) {
|
) {
|
||||||
lazyListState.value = LazyListState(
|
lazyListState.value = LazyListState(
|
||||||
0,
|
0,
|
||||||
@ -44,13 +49,14 @@ class DiffViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Just a workaround when trying to diff binary files
|
|
||||||
try {
|
try {
|
||||||
val hunks = diffManager.diffFormat(git, diffEntryType)
|
val diffFormat = diffManager.diffFormat(git, diffEntryType)
|
||||||
_diffResult.value = ViewDiffResult(diffEntryType, hunks)
|
_diffResult.value = ViewDiffResult.Loaded(diffEntryType, diffFormat)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
ex.printStackTrace()
|
if(ex is MissingDiffEntryException) {
|
||||||
_diffResult.value = ViewDiffResult(diffEntryType, DiffResult.Text(emptyList()))
|
_diffResult.value = ViewDiffResult.DiffNotFound
|
||||||
|
} else
|
||||||
|
ex.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,4 +73,9 @@ class DiffViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ViewDiffResult(val diffEntryType: DiffEntryType, val diffResult: DiffResult)
|
|
||||||
|
sealed interface ViewDiffResult {
|
||||||
|
object Loading: ViewDiffResult
|
||||||
|
object DiffNotFound: ViewDiffResult
|
||||||
|
data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult): ViewDiffResult
|
||||||
|
}
|
||||||
|
@ -56,18 +56,14 @@ class LogViewModel @Inject constructor(
|
|||||||
|
|
||||||
val statusSummary = statusManager.getStatusSummary(
|
val statusSummary = statusManager.getStatusSummary(
|
||||||
git = git,
|
git = git,
|
||||||
currentBranch = currentBranch,
|
|
||||||
repositoryState = repositoryManager.getRepositoryState(git),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val hasUncommitedChanges =
|
val hasUncommitedChanges = statusSummary.total > 0
|
||||||
statusSummary.addedCount + statusSummary.deletedCount + statusSummary.modifiedCount > 0
|
|
||||||
val log = logManager.loadLog(git, currentBranch, hasUncommitedChanges)
|
val log = logManager.loadLog(git, currentBranch, hasUncommitedChanges)
|
||||||
|
|
||||||
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary)
|
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary)
|
||||||
|
|
||||||
// Remove search filter if the log has been updated
|
// Remove search filter if the log has been updated
|
||||||
// TODO: Should we just update the search instead of closing it?
|
|
||||||
_logSearchFilterResults.value = LogSearch.NotSearching
|
_logSearchFilterResults.value = LogSearch.NotSearching
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,11 +159,9 @@ class LogViewModel @Inject constructor(
|
|||||||
val statsSummary = if (hasUncommitedChanges) {
|
val statsSummary = if (hasUncommitedChanges) {
|
||||||
statusManager.getStatusSummary(
|
statusManager.getStatusSummary(
|
||||||
git = git,
|
git = git,
|
||||||
currentBranch = currentBranch,
|
|
||||||
repositoryState = repositoryManager.getRepositoryState(git),
|
|
||||||
)
|
)
|
||||||
} else
|
} else
|
||||||
StatusSummary(0, 0, 0)
|
StatusSummary(0, 0, 0, 0)
|
||||||
|
|
||||||
val previousLogStatusValue = _logStatus.value
|
val previousLogStatusValue = _logStatus.value
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -27,16 +26,16 @@ class StatusViewModel @Inject constructor(
|
|||||||
|
|
||||||
private var lastUncommitedChangesState = false
|
private var lastUncommitedChangesState = false
|
||||||
|
|
||||||
fun stage(diffEntry: DiffEntry) = tabState.runOperation(
|
fun stage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stage(git, diffEntry)
|
statusManager.stage(git, statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unstage(diffEntry: DiffEntry) = tabState.runOperation(
|
fun unstage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.unstage(git, diffEntry)
|
statusManager.unstage(git, statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -52,16 +51,16 @@ class StatusViewModel @Inject constructor(
|
|||||||
statusManager.stageAll(git)
|
statusManager.stageAll(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation(
|
fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.reset(git, diffEntry, staged = true)
|
statusManager.reset(git, statusEntry, staged = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetUnstaged(diffEntry: DiffEntry) = tabState.runOperation(
|
fun resetUnstaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.reset(git, diffEntry, staged = false)
|
statusManager.reset(git, statusEntry, staged = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadStatus(git: Git) {
|
private suspend fun loadStatus(git: Git) {
|
||||||
@ -69,10 +68,9 @@ class StatusViewModel @Inject constructor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
_stageStatus.value = StageStatus.Loading
|
_stageStatus.value = StageStatus.Loading
|
||||||
val repositoryState = repositoryManager.getRepositoryState(git)
|
val status = statusManager.getStatus(git)
|
||||||
val currentBranchRef = branchesManager.currentBranchRef(git)
|
val staged = statusManager.getStaged(status)
|
||||||
val staged = statusManager.getStaged(git, currentBranchRef, repositoryState)
|
val unstaged = statusManager.getUnstaged(status)
|
||||||
val unstaged = statusManager.getUnstaged(git, repositoryState)
|
|
||||||
|
|
||||||
_stageStatus.value = StageStatus.Loaded(staged, unstaged)
|
_stageStatus.value = StageStatus.Loaded(staged, unstaged)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
@ -141,10 +139,10 @@ class StatusViewModel @Inject constructor(
|
|||||||
mergeManager.abortMerge(git)
|
mergeManager.abortMerge(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteFile(diffEntry: DiffEntry) = tabState.runOperation(
|
fun deleteFile(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
val path = diffEntry.newPath
|
val path = statusEntry.filePath
|
||||||
|
|
||||||
val fileToDelete = File(git.repository.directory.parent, path)
|
val fileToDelete = File(git.repository.directory.parent, path)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user