Refactored status manager into use cases
This commit is contained in:
parent
d3f2b4a23f
commit
3b1486efb6
@ -6,8 +6,8 @@ import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import app.git.StatusEntry
|
||||
import app.git.StatusType
|
||||
import app.git.workspace.StatusEntry
|
||||
import app.git.workspace.StatusType
|
||||
import app.theme.addFile
|
||||
import app.theme.conflictFile
|
||||
import app.theme.modifyFile
|
||||
|
@ -2,6 +2,8 @@ package app.git
|
||||
|
||||
import app.extensions.filePath
|
||||
import app.extensions.toStatusType
|
||||
import app.git.workspace.StatusEntry
|
||||
import app.git.workspace.StatusType
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
|
||||
sealed class DiffEntryType {
|
||||
|
@ -1,438 +0,0 @@
|
||||
package app.git
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import app.extensions.*
|
||||
import app.git.diff.Hunk
|
||||
import app.git.diff.LineType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.api.Status
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import org.eclipse.jgit.diff.RawText
|
||||
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
import org.eclipse.jgit.lib.FileMode
|
||||
import org.eclipse.jgit.lib.ObjectInserter
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class StatusManager @Inject constructor(
|
||||
private val rawFileManager: RawFileManager,
|
||||
) {
|
||||
suspend fun hasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
|
||||
val status = git
|
||||
.status()
|
||||
.call()
|
||||
|
||||
return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges()
|
||||
}
|
||||
|
||||
suspend fun stage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||
git.add()
|
||||
.addFilepattern(statusEntry.filePath)
|
||||
.setUpdate(statusEntry.statusType == StatusType.REMOVED)
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun stageHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
||||
val repository = git.repository
|
||||
val dirCache = repository.lockDirCache()
|
||||
val dirCacheEditor = dirCache.editor()
|
||||
var completedWithErrors = true
|
||||
|
||||
try {
|
||||
val entryContent = rawFileManager.getRawContent(
|
||||
repository = git.repository,
|
||||
side = DiffEntry.Side.OLD,
|
||||
entry = diffEntry,
|
||||
oldTreeIterator = null,
|
||||
newTreeIterator = null
|
||||
)
|
||||
|
||||
if (entryContent !is EntryContent.Text)
|
||||
return@withContext
|
||||
|
||||
val textLines = getTextLines(entryContent.rawText).toMutableList()
|
||||
|
||||
val hunkLines = hunk.lines.filter { it.lineType != LineType.CONTEXT }
|
||||
|
||||
var linesAdded = 0
|
||||
for (line in hunkLines) {
|
||||
when (line.lineType) {
|
||||
LineType.ADDED -> {
|
||||
textLines.add(line.oldLineNumber + linesAdded, line.text)
|
||||
linesAdded++
|
||||
}
|
||||
|
||||
LineType.REMOVED -> {
|
||||
textLines.removeAt(line.oldLineNumber + linesAdded)
|
||||
linesAdded--
|
||||
}
|
||||
|
||||
else -> throw NotImplementedError("Line type not implemented for stage hunk")
|
||||
}
|
||||
}
|
||||
|
||||
val stagedFileText = textLines.joinToString("")
|
||||
dirCacheEditor.add(HunkEdit(diffEntry.newPath, repository, ByteBuffer.wrap(stagedFileText.toByteArray())))
|
||||
dirCacheEditor.commit()
|
||||
|
||||
completedWithErrors = false
|
||||
} finally {
|
||||
if (completedWithErrors)
|
||||
dirCache.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unstageHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
||||
val repository = git.repository
|
||||
val dirCache = repository.lockDirCache()
|
||||
val dirCacheEditor = dirCache.editor()
|
||||
var completedWithErrors = true
|
||||
|
||||
try {
|
||||
val entryContent = rawFileManager.getRawContent(
|
||||
repository = repository,
|
||||
side = DiffEntry.Side.NEW,
|
||||
entry = diffEntry,
|
||||
oldTreeIterator = null,
|
||||
newTreeIterator = null
|
||||
)
|
||||
|
||||
if (entryContent !is EntryContent.Text)
|
||||
return@withContext
|
||||
|
||||
val textLines = getTextLines(entryContent.rawText).toMutableList()
|
||||
|
||||
val hunkLines = hunk.lines.filter { it.lineType != LineType.CONTEXT }
|
||||
|
||||
val addedLines = hunkLines
|
||||
.filter { it.lineType == LineType.ADDED }
|
||||
.sortedBy { it.newLineNumber }
|
||||
val removedLines = hunkLines
|
||||
.filter { it.lineType == LineType.REMOVED }
|
||||
.sortedBy { it.newLineNumber }
|
||||
|
||||
var linesRemoved = 0
|
||||
|
||||
// Start by removing the added lines to the index
|
||||
for (line in addedLines) {
|
||||
textLines.removeAt(line.newLineNumber + linesRemoved)
|
||||
linesRemoved--
|
||||
}
|
||||
|
||||
var linesAdded = 0
|
||||
|
||||
// Restore previously removed lines to the index
|
||||
for (line in removedLines) {
|
||||
// Check how many lines before this one have been deleted
|
||||
val previouslyRemovedLines = addedLines.count { it.newLineNumber < line.newLineNumber }
|
||||
textLines.add(line.newLineNumber + linesAdded - previouslyRemovedLines, line.text)
|
||||
linesAdded++
|
||||
}
|
||||
|
||||
val stagedFileText = textLines.joinToString("")
|
||||
dirCacheEditor.add(HunkEdit(diffEntry.newPath, repository, ByteBuffer.wrap(stagedFileText.toByteArray())))
|
||||
dirCacheEditor.commit()
|
||||
|
||||
completedWithErrors = false
|
||||
} finally {
|
||||
if (completedWithErrors)
|
||||
dirCache.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTextLines(rawFile: RawText): List<String> {
|
||||
val content = rawFile.rawContent.toString(Charsets.UTF_8)//.removeSuffix(rawFile.lineDelimiter)
|
||||
val lineDelimiter: String? = rawFile.lineDelimiter
|
||||
|
||||
return getTextLines(content, lineDelimiter)
|
||||
}
|
||||
|
||||
private fun getTextLines(content: String, lineDelimiter: String?): List<String> {
|
||||
var splitted: List<String> = if (lineDelimiter != null) {
|
||||
content.split(lineDelimiter).toMutableList().apply {
|
||||
if (this.last() == "")
|
||||
removeLast()
|
||||
}
|
||||
} else {
|
||||
listOf(content)
|
||||
}
|
||||
|
||||
splitted = splitted.mapIndexed { index, line ->
|
||||
val lineWithBreak = line + lineDelimiter.orEmpty()
|
||||
|
||||
if (index == splitted.count() - 1 && !content.endsWith(lineWithBreak)) {
|
||||
line
|
||||
} else
|
||||
lineWithBreak
|
||||
}
|
||||
|
||||
return splitted
|
||||
}
|
||||
|
||||
private class HunkEdit(
|
||||
path: String?,
|
||||
private val repo: Repository,
|
||||
private val content: ByteBuffer,
|
||||
) : PathEdit(path) {
|
||||
override fun apply(ent: DirCacheEntry) {
|
||||
val inserter: ObjectInserter = repo.newObjectInserter()
|
||||
if (ent.rawMode and FileMode.TYPE_MASK != FileMode.TYPE_FILE) {
|
||||
ent.fileMode = FileMode.REGULAR_FILE
|
||||
}
|
||||
ent.length = content.limit()
|
||||
ent.setLastModified(Instant.now())
|
||||
try {
|
||||
val `in` = ByteArrayInputStream(
|
||||
content.array(), 0, content.limit()
|
||||
)
|
||||
ent.setObjectId(
|
||||
inserter.insert(
|
||||
Constants.OBJ_BLOB, content.limit().toLong(),
|
||||
`in`
|
||||
)
|
||||
)
|
||||
inserter.flush()
|
||||
} catch (ex: IOException) {
|
||||
throw RuntimeException(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unstage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||
git.reset()
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun commit(git: Git, message: String, amend: Boolean) = withContext(Dispatchers.IO) {
|
||||
git.commit()
|
||||
.setMessage(message)
|
||||
.setAllowEmpty(false)
|
||||
.setAmend(amend)
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun reset(git: Git, statusEntry: StatusEntry, staged: Boolean) = withContext(Dispatchers.IO) {
|
||||
if (staged || statusEntry.statusType == StatusType.CONFLICTING) {
|
||||
git
|
||||
.reset()
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
|
||||
git
|
||||
.checkout()
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun unstageAll(git: Git) = withContext(Dispatchers.IO) {
|
||||
git
|
||||
.reset()
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun stageAll(git: Git) = withContext(Dispatchers.IO) {
|
||||
git
|
||||
.add()
|
||||
.addFilepattern(".")
|
||||
.setUpdate(true) // Modified and deleted files
|
||||
.call()
|
||||
git
|
||||
.add()
|
||||
.addFilepattern(".")
|
||||
.setUpdate(false) // For newly added files
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun getStatus(git: Git) =
|
||||
withContext(Dispatchers.IO) {
|
||||
git
|
||||
.status()
|
||||
.call()
|
||||
}
|
||||
|
||||
suspend fun getStaged(status: Status) =
|
||||
withContext(Dispatchers.IO) {
|
||||
val added = status.added.map {
|
||||
StatusEntry(it, StatusType.ADDED)
|
||||
}
|
||||
val modified = status.changed.map {
|
||||
StatusEntry(it, StatusType.MODIFIED)
|
||||
}
|
||||
val removed = status.removed.map {
|
||||
StatusEntry(it, StatusType.REMOVED)
|
||||
}
|
||||
|
||||
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): StatusSummary {
|
||||
val status = getStatus(git)
|
||||
val staged = getStaged(status)
|
||||
val allChanges = staged.toMutableList()
|
||||
|
||||
val unstaged = getUnstaged(status)
|
||||
|
||||
allChanges.addAll(unstaged)
|
||||
val groupedChanges = allChanges.groupBy {
|
||||
|
||||
}
|
||||
val changesGrouped = groupedChanges.map {
|
||||
it.value
|
||||
}.flatten()
|
||||
.groupBy {
|
||||
it.statusType
|
||||
}
|
||||
|
||||
val deletedCount = changesGrouped[StatusType.REMOVED].countOrZero()
|
||||
val addedCount = changesGrouped[StatusType.ADDED].countOrZero()
|
||||
|
||||
val modifiedCount = changesGrouped[StatusType.MODIFIED].countOrZero()
|
||||
val conflictingCount = changesGrouped[StatusType.CONFLICTING].countOrZero()
|
||||
|
||||
return StatusSummary(
|
||||
modifiedCount = modifiedCount,
|
||||
deletedCount = deletedCount,
|
||||
addedCount = addedCount,
|
||||
conflictingCount = conflictingCount,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun stageUntrackedFiles(git: Git) = withContext(Dispatchers.IO) {
|
||||
val diffEntries = git
|
||||
.diff()
|
||||
.setShowNameAndStatusOnly(true)
|
||||
.call()
|
||||
|
||||
val addedEntries = diffEntries.filter { it.changeType == DiffEntry.ChangeType.ADD }
|
||||
|
||||
if (addedEntries.isNotEmpty()) {
|
||||
val addCommand = git
|
||||
.add()
|
||||
|
||||
for (entry in addedEntries) {
|
||||
addCommand.addFilepattern(entry.newPath)
|
||||
}
|
||||
|
||||
addCommand.call()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resetHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
||||
val repository = git.repository
|
||||
|
||||
try {
|
||||
val file = File(repository.directory.parent, diffEntry.oldPath)
|
||||
|
||||
val content = file.readText()
|
||||
val textLines = getTextLines(content, content.lineDelimiter).toMutableList()
|
||||
val hunkLines = hunk.lines.filter { it.lineType != LineType.CONTEXT }
|
||||
|
||||
val addedLines = hunkLines
|
||||
.filter { it.lineType == LineType.ADDED }
|
||||
.sortedBy { it.newLineNumber }
|
||||
val removedLines = hunkLines
|
||||
.filter { it.lineType == LineType.REMOVED }
|
||||
.sortedBy { it.newLineNumber }
|
||||
|
||||
var linesRemoved = 0
|
||||
|
||||
// Start by removing the added lines to the index
|
||||
for (line in addedLines) {
|
||||
textLines.removeAt(line.newLineNumber + linesRemoved)
|
||||
linesRemoved--
|
||||
}
|
||||
|
||||
var linesAdded = 0
|
||||
|
||||
// Restore previously removed lines to the index
|
||||
for (line in removedLines) {
|
||||
// Check how many lines before this one have been deleted
|
||||
val previouslyRemovedLines = addedLines.count { it.newLineNumber < line.newLineNumber }
|
||||
textLines.add(line.newLineNumber + linesAdded - previouslyRemovedLines, line.text)
|
||||
linesAdded++
|
||||
}
|
||||
|
||||
val stagedFileText = textLines.joinToString("")
|
||||
|
||||
|
||||
FileWriter(file).use { fw ->
|
||||
fw.write(stagedFileText)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
throw Exception("Discard hunk failed. Check if the file still exists and has the write permissions set", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class StatusEntry(val filePath: String, val statusType: StatusType) {
|
||||
val icon: ImageVector
|
||||
get() = statusType.icon
|
||||
|
||||
val iconColor: Color
|
||||
@Composable
|
||||
get() = statusType.iconColor
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -10,5 +10,4 @@ class GetRepositoryStateUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git): RepositoryState = withContext(Dispatchers.IO) {
|
||||
return@withContext git.repository.repositoryState
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package app.git.workspace
|
||||
|
||||
import app.extensions.hasUntrackedChanges
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import javax.inject.Inject
|
||||
|
||||
class CheckHasUncommitedChangedUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git) = withContext(Dispatchers.IO) {
|
||||
val status = git
|
||||
.status()
|
||||
.call()
|
||||
|
||||
return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges()
|
||||
}
|
||||
}
|
17
src/main/kotlin/app/git/workspace/DoCommitUseCase.kt
Normal file
17
src/main/kotlin/app/git/workspace/DoCommitUseCase.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package app.git.workspace
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import javax.inject.Inject
|
||||
|
||||
class DoCommitUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git, message: String, amend: Boolean): RevCommit = withContext(Dispatchers.IO) {
|
||||
git.commit()
|
||||
.setMessage(message)
|
||||
.setAllowEmpty(false)
|
||||
.setAmend(amend)
|
||||
.call()
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package app.git.workspace
|
||||
|
||||
import org.eclipse.jgit.diff.RawText
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetLinesFromRawTextUseCase @Inject constructor(
|
||||
private val getLinesFromTextUseCase: GetLinesFromTextUseCase,
|
||||
) {
|
||||
operator fun invoke(rawFile: RawText): List<String> {
|
||||
val content = rawFile.rawContent.toString(Charsets.UTF_8)//.removeSuffix(rawFile.lineDelimiter)
|
||||
val lineDelimiter: String? = rawFile.lineDelimiter
|
||||
|
||||
return getLinesFromTextUseCase(content, lineDelimiter)
|
||||
}
|
||||
}
|
27
src/main/kotlin/app/git/workspace/GetLinesFromTextUseCase.kt
Normal file
27
src/main/kotlin/app/git/workspace/GetLinesFromTextUseCase.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package app.git.workspace
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetLinesFromTextUseCase @Inject constructor() {
|
||||
operator fun invoke(content: String, lineDelimiter: String?): List<String> {
|
||||
var splitted: List<String> = if (lineDelimiter != null) {
|
||||
content.split(lineDelimiter).toMutableList().apply {
|
||||
if (this.last() == "")
|
||||
removeLast()
|
||||
}
|
||||
} else {
|
||||
listOf(content)
|
||||
}
|
||||
|
||||
splitted = splitted.mapIndexed { index, line ->
|
||||
val lineWithBreak = line + lineDelimiter.orEmpty()
|
||||
|
||||
if (index == splitted.count() - 1 && !content.endsWith(lineWithBreak)) {
|
||||
line
|
||||
} else
|
||||
lineWithBreak
|
||||
}
|
||||
|
||||
return splitted
|
||||
}
|
||||
}
|
28
src/main/kotlin/app/git/workspace/GetStagedUseCase.kt
Normal file
28
src/main/kotlin/app/git/workspace/GetStagedUseCase.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package app.git.workspace
|
||||
|
||||
import app.extensions.flatListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Status
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetStagedUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(status: Status) =
|
||||
withContext(Dispatchers.IO) {
|
||||
val added = status.added.map {
|
||||
StatusEntry(it, StatusType.ADDED)
|
||||
}
|
||||
val modified = status.changed.map {
|
||||
StatusEntry(it, StatusType.MODIFIED)
|
||||
}
|
||||
val removed = status.removed.map {
|
||||
StatusEntry(it, StatusType.REMOVED)
|
||||
}
|
||||
|
||||
return@withContext flatListOf(
|
||||
added,
|
||||
modified,
|
||||
removed,
|
||||
)
|
||||
}
|
||||
}
|
36
src/main/kotlin/app/git/workspace/GetStatusSummaryUseCase.kt
Normal file
36
src/main/kotlin/app/git/workspace/GetStatusSummaryUseCase.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package app.git.workspace
|
||||
|
||||
import app.extensions.countOrZero
|
||||
import org.eclipse.jgit.api.Git
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetStatusSummaryUseCase @Inject constructor(
|
||||
private val getStagedUseCase: GetStagedUseCase,
|
||||
private val getStatusUseCase: GetStatusUseCase,
|
||||
private val getUnstagedUseCase: GetUnstagedUseCase,
|
||||
) {
|
||||
suspend operator fun invoke(git: Git): StatusSummary {
|
||||
val status = getStatusUseCase(git)
|
||||
val staged = getStagedUseCase(status)
|
||||
|
||||
val unstaged = getUnstagedUseCase(status)
|
||||
val allChanges = staged + unstaged
|
||||
|
||||
val groupedChanges = allChanges.groupBy {
|
||||
it.statusType
|
||||
}
|
||||
|
||||
val deletedCount = groupedChanges[StatusType.REMOVED].countOrZero()
|
||||
val addedCount = groupedChanges[StatusType.ADDED].countOrZero()
|
||||
|
||||
val modifiedCount = groupedChanges[StatusType.MODIFIED].countOrZero()
|
||||
val conflictingCount = groupedChanges[StatusType.CONFLICTING].countOrZero()
|
||||
|
||||
return StatusSummary(
|
||||
modifiedCount = modifiedCount,
|
||||
deletedCount = deletedCount,
|
||||
addedCount = addedCount,
|
||||
conflictingCount = conflictingCount,
|
||||
)
|
||||
}
|
||||
}
|
17
src/main/kotlin/app/git/workspace/GetStatusUseCase.kt
Normal file
17
src/main/kotlin/app/git/workspace/GetStatusUseCase.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package app.git.workspace
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.api.Status
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetStatusUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git): Status =
|
||||
withContext(Dispatchers.IO) {
|
||||
git
|
||||
.status()
|
||||
.call()
|
||||
}
|
||||
|
||||
}
|
34
src/main/kotlin/app/git/workspace/GetUnstagedUseCase.kt
Normal file
34
src/main/kotlin/app/git/workspace/GetUnstagedUseCase.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package app.git.workspace
|
||||
|
||||
import app.extensions.flatListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Status
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetUnstagedUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(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,
|
||||
)
|
||||
}
|
||||
}
|
41
src/main/kotlin/app/git/workspace/HunkEdit.kt
Normal file
41
src/main/kotlin/app/git/workspace/HunkEdit.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package app.git.workspace
|
||||
|
||||
import org.eclipse.jgit.dircache.DirCacheEditor
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
import org.eclipse.jgit.lib.FileMode
|
||||
import org.eclipse.jgit.lib.ObjectInserter
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.time.Instant
|
||||
|
||||
class HunkEdit(
|
||||
path: String?,
|
||||
private val repo: Repository,
|
||||
private val content: ByteBuffer,
|
||||
) : DirCacheEditor.PathEdit(path) {
|
||||
override fun apply(ent: DirCacheEntry) {
|
||||
val inserter: ObjectInserter = repo.newObjectInserter()
|
||||
if (ent.rawMode and FileMode.TYPE_MASK != FileMode.TYPE_FILE) {
|
||||
ent.fileMode = FileMode.REGULAR_FILE
|
||||
}
|
||||
ent.length = content.limit()
|
||||
ent.setLastModified(Instant.now())
|
||||
try {
|
||||
val `in` = ByteArrayInputStream(
|
||||
content.array(), 0, content.limit()
|
||||
)
|
||||
ent.setObjectId(
|
||||
inserter.insert(
|
||||
Constants.OBJ_BLOB, content.limit().toLong(),
|
||||
`in`
|
||||
)
|
||||
)
|
||||
inserter.flush()
|
||||
} catch (ex: IOException) {
|
||||
throw RuntimeException(ex)
|
||||
}
|
||||
}
|
||||
}
|
24
src/main/kotlin/app/git/workspace/ResetEntryUseCase.kt
Normal file
24
src/main/kotlin/app/git/workspace/ResetEntryUseCase.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package app.git.workspace
|
||||
|
||||
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 ResetEntryUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git, statusEntry: StatusEntry, staged: Boolean): Ref =
|
||||
withContext(Dispatchers.IO) {
|
||||
if (staged || statusEntry.statusType == StatusType.CONFLICTING) {
|
||||
git
|
||||
.reset()
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
|
||||
git
|
||||
.checkout()
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
}
|
62
src/main/kotlin/app/git/workspace/ResetHunkUseCase.kt
Normal file
62
src/main/kotlin/app/git/workspace/ResetHunkUseCase.kt
Normal file
@ -0,0 +1,62 @@
|
||||
package app.git.workspace
|
||||
|
||||
import app.extensions.lineDelimiter
|
||||
import app.git.diff.Hunk
|
||||
import app.git.diff.LineType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import javax.inject.Inject
|
||||
|
||||
class ResetHunkUseCase @Inject constructor(
|
||||
private val getLinesFromTextUseCase: GetLinesFromTextUseCase,
|
||||
) {
|
||||
suspend operator fun invoke(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
||||
val repository = git.repository
|
||||
|
||||
try {
|
||||
val file = File(repository.directory.parent, diffEntry.oldPath)
|
||||
|
||||
val content = file.readText()
|
||||
val textLines = getLinesFromTextUseCase(content, content.lineDelimiter).toMutableList()
|
||||
val hunkLines = hunk.lines.filter { it.lineType != LineType.CONTEXT }
|
||||
|
||||
val addedLines = hunkLines
|
||||
.filter { it.lineType == LineType.ADDED }
|
||||
.sortedBy { it.newLineNumber }
|
||||
val removedLines = hunkLines
|
||||
.filter { it.lineType == LineType.REMOVED }
|
||||
.sortedBy { it.newLineNumber }
|
||||
|
||||
var linesRemoved = 0
|
||||
|
||||
// Start by removing the added lines to the index
|
||||
for (line in addedLines) {
|
||||
textLines.removeAt(line.newLineNumber + linesRemoved)
|
||||
linesRemoved--
|
||||
}
|
||||
|
||||
var linesAdded = 0
|
||||
|
||||
// Restore previously removed lines to the index
|
||||
for (line in removedLines) {
|
||||
// Check how many lines before this one have been deleted
|
||||
val previouslyRemovedLines = addedLines.count { it.newLineNumber < line.newLineNumber }
|
||||
textLines.add(line.newLineNumber + linesAdded - previouslyRemovedLines, line.text)
|
||||
linesAdded++
|
||||
}
|
||||
|
||||
val stagedFileText = textLines.joinToString("")
|
||||
|
||||
|
||||
FileWriter(file).use { fw ->
|
||||
fw.write(stagedFileText)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
throw Exception("Discard hunk failed. Check if the file still exists and has the write permissions set", ex)
|
||||
}
|
||||
}
|
||||
}
|
21
src/main/kotlin/app/git/workspace/StageAllUseCase.kt
Normal file
21
src/main/kotlin/app/git/workspace/StageAllUseCase.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package app.git.workspace
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import javax.inject.Inject
|
||||
|
||||
class StageAllUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git): Unit = withContext(Dispatchers.IO) {
|
||||
git
|
||||
.add()
|
||||
.addFilepattern(".")
|
||||
.setUpdate(true) // Modified and deleted files
|
||||
.call()
|
||||
git
|
||||
.add()
|
||||
.addFilepattern(".")
|
||||
.setUpdate(false) // For newly added files
|
||||
.call()
|
||||
}
|
||||
}
|
15
src/main/kotlin/app/git/workspace/StageEntryUseCase.kt
Normal file
15
src/main/kotlin/app/git/workspace/StageEntryUseCase.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package app.git.workspace
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import javax.inject.Inject
|
||||
|
||||
class StageEntryUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||
git.add()
|
||||
.addFilepattern(statusEntry.filePath)
|
||||
.setUpdate(statusEntry.statusType == StatusType.REMOVED)
|
||||
.call()
|
||||
}
|
||||
}
|
74
src/main/kotlin/app/git/workspace/StageHunkUseCase.kt
Normal file
74
src/main/kotlin/app/git/workspace/StageHunkUseCase.kt
Normal file
@ -0,0 +1,74 @@
|
||||
package app.git.workspace
|
||||
|
||||
import app.git.EntryContent
|
||||
import app.git.RawFileManager
|
||||
import app.git.diff.Hunk
|
||||
import app.git.diff.LineType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import java.nio.ByteBuffer
|
||||
import javax.inject.Inject
|
||||
|
||||
class StageHunkUseCase @Inject constructor(
|
||||
private val rawFileManager: RawFileManager,
|
||||
private val getLinesFromRawTextUseCase: GetLinesFromRawTextUseCase,
|
||||
) {
|
||||
suspend operator fun invoke(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
||||
val repository = git.repository
|
||||
val dirCache = repository.lockDirCache()
|
||||
val dirCacheEditor = dirCache.editor()
|
||||
var completedWithErrors = true
|
||||
|
||||
try {
|
||||
val entryContent = rawFileManager.getRawContent(
|
||||
repository = git.repository,
|
||||
side = DiffEntry.Side.OLD,
|
||||
entry = diffEntry,
|
||||
oldTreeIterator = null,
|
||||
newTreeIterator = null
|
||||
)
|
||||
|
||||
if (entryContent !is EntryContent.Text)
|
||||
return@withContext
|
||||
|
||||
val textLines = getLinesFromRawTextUseCase(entryContent.rawText).toMutableList()
|
||||
|
||||
val hunkLines = hunk.lines.filter { it.lineType != LineType.CONTEXT }
|
||||
|
||||
var linesAdded = 0
|
||||
for (line in hunkLines) {
|
||||
when (line.lineType) {
|
||||
LineType.ADDED -> {
|
||||
textLines.add(line.oldLineNumber + linesAdded, line.text)
|
||||
linesAdded++
|
||||
}
|
||||
|
||||
LineType.REMOVED -> {
|
||||
textLines.removeAt(line.oldLineNumber + linesAdded)
|
||||
linesAdded--
|
||||
}
|
||||
|
||||
else -> throw NotImplementedError("Line type not implemented for stage hunk")
|
||||
}
|
||||
}
|
||||
|
||||
val stagedFileText = textLines.joinToString("")
|
||||
dirCacheEditor.add(
|
||||
HunkEdit(
|
||||
diffEntry.newPath,
|
||||
repository,
|
||||
ByteBuffer.wrap(stagedFileText.toByteArray())
|
||||
)
|
||||
)
|
||||
dirCacheEditor.commit()
|
||||
|
||||
completedWithErrors = false
|
||||
} finally {
|
||||
if (completedWithErrors)
|
||||
dirCache.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package app.git.workspace
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import javax.inject.Inject
|
||||
|
||||
class StageUntrackedFileUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git) = withContext(Dispatchers.IO) {
|
||||
val diffEntries = git
|
||||
.diff()
|
||||
.setShowNameAndStatusOnly(true)
|
||||
.call()
|
||||
|
||||
val addedEntries = diffEntries.filter { it.changeType == DiffEntry.ChangeType.ADD }
|
||||
|
||||
if (addedEntries.isNotEmpty()) {
|
||||
val addCommand = git
|
||||
.add()
|
||||
|
||||
for (entry in addedEntries) {
|
||||
addCommand.addFilepattern(entry.newPath)
|
||||
}
|
||||
|
||||
addCommand.call()
|
||||
}
|
||||
}
|
||||
}
|
34
src/main/kotlin/app/git/workspace/Status.kt
Normal file
34
src/main/kotlin/app/git/workspace/Status.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package app.git.workspace
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import app.extensions.*
|
||||
|
||||
data class StatusEntry(val filePath: String, val statusType: StatusType) {
|
||||
val icon: ImageVector
|
||||
get() = statusType.icon
|
||||
|
||||
val iconColor: Color
|
||||
@Composable
|
||||
get() = statusType.iconColor
|
||||
}
|
||||
|
||||
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
|
||||
}
|
14
src/main/kotlin/app/git/workspace/UnstageAllUseCase.kt
Normal file
14
src/main/kotlin/app/git/workspace/UnstageAllUseCase.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package app.git.workspace
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import javax.inject.Inject
|
||||
|
||||
class UnstageAllUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git): Unit = withContext(Dispatchers.IO) {
|
||||
git
|
||||
.reset()
|
||||
.call()
|
||||
}
|
||||
}
|
15
src/main/kotlin/app/git/workspace/UnstageEntryUseCase.kt
Normal file
15
src/main/kotlin/app/git/workspace/UnstageEntryUseCase.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package app.git.workspace
|
||||
|
||||
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 UnstageEntryUseCase @Inject constructor() {
|
||||
suspend operator fun invoke(git: Git, statusEntry: StatusEntry): Ref = withContext(Dispatchers.IO) {
|
||||
git.reset()
|
||||
.addPath(statusEntry.filePath)
|
||||
.call()
|
||||
}
|
||||
}
|
73
src/main/kotlin/app/git/workspace/UnstageHunkUseCase.kt
Normal file
73
src/main/kotlin/app/git/workspace/UnstageHunkUseCase.kt
Normal file
@ -0,0 +1,73 @@
|
||||
package app.git.workspace
|
||||
|
||||
import app.git.EntryContent
|
||||
import app.git.RawFileManager
|
||||
import app.git.diff.Hunk
|
||||
import app.git.diff.LineType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import java.nio.ByteBuffer
|
||||
import javax.inject.Inject
|
||||
|
||||
class UnstageHunkUseCase @Inject constructor(
|
||||
private val rawFileManager: RawFileManager,
|
||||
private val getLinesFromRawTextUseCase: GetLinesFromRawTextUseCase
|
||||
) {
|
||||
suspend operator fun invoke(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
||||
val repository = git.repository
|
||||
val dirCache = repository.lockDirCache()
|
||||
val dirCacheEditor = dirCache.editor()
|
||||
var completedWithErrors = true
|
||||
|
||||
try {
|
||||
val entryContent = rawFileManager.getRawContent(
|
||||
repository = git.repository,
|
||||
side = DiffEntry.Side.OLD,
|
||||
entry = diffEntry,
|
||||
oldTreeIterator = null,
|
||||
newTreeIterator = null
|
||||
)
|
||||
|
||||
if (entryContent !is EntryContent.Text)
|
||||
return@withContext
|
||||
|
||||
val textLines = getLinesFromRawTextUseCase(entryContent.rawText).toMutableList()
|
||||
|
||||
val hunkLines = hunk.lines.filter { it.lineType != LineType.CONTEXT }
|
||||
|
||||
var linesAdded = 0
|
||||
for (line in hunkLines) {
|
||||
when (line.lineType) {
|
||||
LineType.ADDED -> {
|
||||
textLines.add(line.oldLineNumber + linesAdded, line.text)
|
||||
linesAdded++
|
||||
}
|
||||
|
||||
LineType.REMOVED -> {
|
||||
textLines.removeAt(line.oldLineNumber + linesAdded)
|
||||
linesAdded--
|
||||
}
|
||||
|
||||
else -> throw NotImplementedError("Line type not implemented for stage hunk")
|
||||
}
|
||||
}
|
||||
|
||||
val stagedFileText = textLines.joinToString("")
|
||||
dirCacheEditor.add(
|
||||
HunkEdit(
|
||||
diffEntry.newPath,
|
||||
repository,
|
||||
ByteBuffer.wrap(stagedFileText.toByteArray())
|
||||
)
|
||||
)
|
||||
dirCacheEditor.commit()
|
||||
|
||||
completedWithErrors = false
|
||||
} finally {
|
||||
if (completedWithErrors)
|
||||
dirCache.unlock()
|
||||
}
|
||||
}
|
||||
}
|
@ -30,8 +30,8 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.extensions.*
|
||||
import app.git.DiffEntryType
|
||||
import app.git.StatusEntry
|
||||
import app.git.StatusType
|
||||
import app.git.workspace.StatusEntry
|
||||
import app.git.workspace.StatusType
|
||||
import app.keybindings.KeybindingOption
|
||||
import app.keybindings.matchesBinding
|
||||
import app.theme.*
|
||||
|
@ -2,8 +2,8 @@ package app.ui.context_menu
|
||||
|
||||
import androidx.compose.foundation.ContextMenuItem
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import app.git.StatusEntry
|
||||
import app.git.StatusType
|
||||
import app.git.workspace.StatusEntry
|
||||
import app.git.workspace.StatusType
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun stagedEntriesContextMenuItems(
|
||||
|
@ -2,8 +2,8 @@ package app.ui.context_menu
|
||||
|
||||
import androidx.compose.foundation.ContextMenuItem
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import app.git.StatusEntry
|
||||
import app.git.StatusType
|
||||
import app.git.workspace.StatusEntry
|
||||
import app.git.workspace.StatusType
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun statusEntriesContextMenuItems(
|
||||
|
@ -36,6 +36,8 @@ import app.git.diff.DiffResult
|
||||
import app.git.diff.Hunk
|
||||
import app.git.diff.Line
|
||||
import app.git.diff.LineType
|
||||
import app.git.workspace.StatusEntry
|
||||
import app.git.workspace.StatusType
|
||||
import app.keybindings.KeybindingOption
|
||||
import app.keybindings.matchesBinding
|
||||
import app.theme.*
|
||||
|
@ -26,7 +26,6 @@ import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.PathBuilder
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
@ -40,7 +39,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.extensions.*
|
||||
import app.git.StatusSummary
|
||||
import app.git.workspace.StatusSummary
|
||||
import app.git.graph.GraphCommitList
|
||||
import app.git.graph.GraphNode
|
||||
import app.keybindings.KeybindingOption
|
||||
@ -58,7 +57,6 @@ import app.ui.dialogs.ResetBranchDialog
|
||||
import app.viewmodels.LogSearch
|
||||
import app.viewmodels.LogStatus
|
||||
import app.viewmodels.LogViewModel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import org.eclipse.jgit.lib.RepositoryState
|
||||
|
@ -10,6 +10,7 @@ import app.git.diff.FormatDiffUseCase
|
||||
import app.git.diff.Hunk
|
||||
import app.preferences.AppSettings
|
||||
import app.git.diff.GenerateSplitHunkFromDiffResultUseCase
|
||||
import app.git.workspace.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@ -22,7 +23,11 @@ private const val DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD = 200L
|
||||
class DiffViewModel @Inject constructor(
|
||||
private val tabState: TabState,
|
||||
private val formatDiffUseCase: FormatDiffUseCase,
|
||||
private val statusManager: StatusManager,
|
||||
private val stageHunkUseCase: StageHunkUseCase,
|
||||
private val unstageHunkUseCase: UnstageHunkUseCase,
|
||||
private val resetHunkUseCase: ResetHunkUseCase,
|
||||
private val stageEntryUseCase: StageEntryUseCase,
|
||||
private val unstageEntryUseCase: UnstageEntryUseCase,
|
||||
private val settings: AppSettings,
|
||||
private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase,
|
||||
) {
|
||||
@ -115,32 +120,32 @@ class DiffViewModel @Inject constructor(
|
||||
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.stageHunk(git, diffEntry, hunk)
|
||||
stageHunkUseCase(git, diffEntry, hunk)
|
||||
}
|
||||
|
||||
fun resetHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
showError = true,
|
||||
) { git ->
|
||||
statusManager.resetHunk(git, diffEntry, hunk)
|
||||
resetHunkUseCase(git, diffEntry, hunk)
|
||||
}
|
||||
|
||||
fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.unstageHunk(git, diffEntry, hunk)
|
||||
unstageHunkUseCase(git, diffEntry, hunk)
|
||||
}
|
||||
|
||||
fun stageFile(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.stage(git, statusEntry)
|
||||
stageEntryUseCase(git, statusEntry)
|
||||
}
|
||||
|
||||
fun unstageFile(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.unstage(git, statusEntry)
|
||||
unstageEntryUseCase(git, statusEntry)
|
||||
}
|
||||
|
||||
fun cancelRunningJobs() {
|
||||
|
@ -10,6 +10,9 @@ 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.git.workspace.CheckHasUncommitedChangedUseCase
|
||||
import app.git.workspace.GetStatusSummaryUseCase
|
||||
import app.git.workspace.StatusSummary
|
||||
import app.preferences.AppSettings
|
||||
import app.ui.SelectedItem
|
||||
import app.ui.log.LogDialog
|
||||
@ -37,7 +40,8 @@ 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 getStatusSummaryUseCase: GetStatusSummaryUseCase,
|
||||
private val checkHasUncommitedChangedUseCase: CheckHasUncommitedChangedUseCase,
|
||||
private val getCurrentBranchUseCase: GetCurrentBranchUseCase,
|
||||
private val checkoutRefUseCase: CheckoutRefUseCase,
|
||||
private val createBranchOnCommitUseCase: CreateBranchOnCommitUseCase,
|
||||
@ -94,7 +98,7 @@ class LogViewModel @Inject constructor(
|
||||
) {
|
||||
val currentBranch = getCurrentBranchUseCase(git)
|
||||
|
||||
val statusSummary = statusManager.getStatusSummary(
|
||||
val statusSummary = getStatusSummaryUseCase(
|
||||
git = git,
|
||||
)
|
||||
|
||||
@ -206,10 +210,10 @@ class LogViewModel @Inject constructor(
|
||||
|
||||
private suspend fun uncommitedChangesLoadLog(git: Git) {
|
||||
val currentBranch = getCurrentBranchUseCase(git)
|
||||
val hasUncommitedChanges = statusManager.hasUncommitedChanges(git)
|
||||
val hasUncommitedChanges = checkHasUncommitedChangedUseCase(git)
|
||||
|
||||
val statsSummary = if (hasUncommitedChanges) {
|
||||
statusManager.getStatusSummary(
|
||||
getStatusSummaryUseCase(
|
||||
git = git,
|
||||
)
|
||||
} else
|
||||
|
@ -5,6 +5,7 @@ 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 app.git.workspace.StageUntrackedFileUseCase
|
||||
import java.awt.Desktop
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -14,7 +15,7 @@ class MenuViewModel @Inject constructor(
|
||||
private val pushBranchUseCase: PushBranchUseCase,
|
||||
private val fetchAllBranchesUseCase: FetchAllBranchesUseCase,
|
||||
private val stashManager: StashManager,
|
||||
private val statusManager: StatusManager,
|
||||
private val stageUntrackedFileUseCase: StageUntrackedFileUseCase,
|
||||
) {
|
||||
fun pull(rebase: Boolean = false) = tabState.safeProcessing(
|
||||
refreshType = RefreshType.ALL_DATA,
|
||||
@ -40,14 +41,14 @@ class MenuViewModel @Inject constructor(
|
||||
fun stash() = tabState.safeProcessing(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
||||
) { git ->
|
||||
statusManager.stageUntrackedFiles(git)
|
||||
stageUntrackedFileUseCase(git)
|
||||
stashManager.stash(git, null)
|
||||
}
|
||||
|
||||
fun stashWithMessage(message: String) = tabState.safeProcessing(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
||||
) { git ->
|
||||
statusManager.stageUntrackedFiles(git)
|
||||
stageUntrackedFileUseCase(git)
|
||||
stashManager.stash(git, message)
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import app.extensions.delayedStateChange
|
||||
import app.extensions.isMerging
|
||||
import app.extensions.isReverting
|
||||
import app.git.*
|
||||
import app.git.workspace.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@ -20,10 +21,19 @@ private const val MIN_TIME_IN_MS_TO_SHOW_LOAD = 500L
|
||||
|
||||
class StatusViewModel @Inject constructor(
|
||||
private val tabState: TabState,
|
||||
private val statusManager: StatusManager,
|
||||
private val stageEntryUseCase: StageEntryUseCase,
|
||||
private val unstageEntryUseCase: UnstageEntryUseCase,
|
||||
private val resetEntryUseCase: ResetEntryUseCase,
|
||||
private val stageAllUseCase: StageAllUseCase,
|
||||
private val unstageAllUseCase: UnstageAllUseCase,
|
||||
private val rebaseManager: RebaseManager,
|
||||
private val mergeManager: MergeManager,
|
||||
private val logManager: LogManager,
|
||||
private val getStatusUseCase: GetStatusUseCase,
|
||||
private val getStagedUseCase: GetStagedUseCase,
|
||||
private val getUnstagedUseCase: GetUnstagedUseCase,
|
||||
private val checkHasUncommitedChangedUseCase: CheckHasUncommitedChangedUseCase,
|
||||
private val doCommitUseCase: DoCommitUseCase,
|
||||
) {
|
||||
private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf(), false))
|
||||
val stageStatus: StateFlow<StageStatus> = _stageStatus
|
||||
@ -61,38 +71,38 @@ class StatusViewModel @Inject constructor(
|
||||
fun stage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.stage(git, statusEntry)
|
||||
stageEntryUseCase(git, statusEntry)
|
||||
}
|
||||
|
||||
fun unstage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.unstage(git, statusEntry)
|
||||
unstageEntryUseCase(git, statusEntry)
|
||||
}
|
||||
|
||||
|
||||
fun unstageAll() = tabState.safeProcessing(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.unstageAll(git)
|
||||
unstageAllUseCase(git)
|
||||
}
|
||||
|
||||
fun stageAll() = tabState.safeProcessing(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.stageAll(git)
|
||||
stageAllUseCase(git)
|
||||
}
|
||||
|
||||
fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.reset(git, statusEntry, staged = true)
|
||||
resetEntryUseCase(git, statusEntry, staged = true)
|
||||
}
|
||||
|
||||
fun resetUnstaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||
) { git ->
|
||||
statusManager.reset(git, statusEntry, staged = false)
|
||||
resetEntryUseCase(git, statusEntry, staged = false)
|
||||
}
|
||||
|
||||
private suspend fun loadStatus(git: Git) {
|
||||
@ -124,9 +134,9 @@ class StatusViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
) {
|
||||
val status = statusManager.getStatus(git)
|
||||
val staged = statusManager.getStaged(status).sortedBy { it.filePath }
|
||||
val unstaged = statusManager.getUnstaged(status).sortedBy { it.filePath }
|
||||
val status = getStatusUseCase(git)
|
||||
val staged = getStagedUseCase(status).sortedBy { it.filePath }
|
||||
val unstaged = getUnstagedUseCase(status).sortedBy { it.filePath }
|
||||
|
||||
_stageStatus.value = StageStatus.Loaded(staged, unstaged, isPartiallyReloading = false)
|
||||
}
|
||||
@ -152,7 +162,7 @@ class StatusViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
|
||||
lastUncommitedChangesState = statusManager.hasUncommitedChanges(git)
|
||||
lastUncommitedChangesState = checkHasUncommitedChangedUseCase(git)
|
||||
}
|
||||
|
||||
fun commit(message: String, amend: Boolean) = tabState.safeProcessing(
|
||||
@ -163,7 +173,7 @@ class StatusViewModel @Inject constructor(
|
||||
} else
|
||||
message
|
||||
|
||||
statusManager.commit(git, commitMessage, amend)
|
||||
doCommitUseCase(git, commitMessage, amend)
|
||||
updateCommitMessage("")
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user