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.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.workspace.StatusEntry
|
||||||
import app.git.StatusType
|
import app.git.workspace.StatusType
|
||||||
import app.theme.addFile
|
import app.theme.addFile
|
||||||
import app.theme.conflictFile
|
import app.theme.conflictFile
|
||||||
import app.theme.modifyFile
|
import app.theme.modifyFile
|
||||||
|
@ -2,6 +2,8 @@ package app.git
|
|||||||
|
|
||||||
import app.extensions.filePath
|
import app.extensions.filePath
|
||||||
import app.extensions.toStatusType
|
import app.extensions.toStatusType
|
||||||
|
import app.git.workspace.StatusEntry
|
||||||
|
import app.git.workspace.StatusType
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
|
||||||
sealed class DiffEntryType {
|
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) {
|
suspend operator fun invoke(git: Git): RepositoryState = withContext(Dispatchers.IO) {
|
||||||
return@withContext git.repository.repositoryState
|
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 androidx.compose.ui.unit.dp
|
||||||
import app.extensions.*
|
import app.extensions.*
|
||||||
import app.git.DiffEntryType
|
import app.git.DiffEntryType
|
||||||
import app.git.StatusEntry
|
import app.git.workspace.StatusEntry
|
||||||
import app.git.StatusType
|
import app.git.workspace.StatusType
|
||||||
import app.keybindings.KeybindingOption
|
import app.keybindings.KeybindingOption
|
||||||
import app.keybindings.matchesBinding
|
import app.keybindings.matchesBinding
|
||||||
import app.theme.*
|
import app.theme.*
|
||||||
|
@ -2,8 +2,8 @@ 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.workspace.StatusEntry
|
||||||
import app.git.StatusType
|
import app.git.workspace.StatusType
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun stagedEntriesContextMenuItems(
|
fun stagedEntriesContextMenuItems(
|
||||||
|
@ -2,8 +2,8 @@ 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.workspace.StatusEntry
|
||||||
import app.git.StatusType
|
import app.git.workspace.StatusType
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun statusEntriesContextMenuItems(
|
fun statusEntriesContextMenuItems(
|
||||||
|
@ -36,6 +36,8 @@ 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
|
||||||
import app.git.diff.LineType
|
import app.git.diff.LineType
|
||||||
|
import app.git.workspace.StatusEntry
|
||||||
|
import app.git.workspace.StatusType
|
||||||
import app.keybindings.KeybindingOption
|
import app.keybindings.KeybindingOption
|
||||||
import app.keybindings.matchesBinding
|
import app.keybindings.matchesBinding
|
||||||
import app.theme.*
|
import app.theme.*
|
||||||
|
@ -26,7 +26,6 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
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.KeyEventType
|
||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.input.key.type
|
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 androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.extensions.*
|
import app.extensions.*
|
||||||
import app.git.StatusSummary
|
import app.git.workspace.StatusSummary
|
||||||
import app.git.graph.GraphCommitList
|
import app.git.graph.GraphCommitList
|
||||||
import app.git.graph.GraphNode
|
import app.git.graph.GraphNode
|
||||||
import app.keybindings.KeybindingOption
|
import app.keybindings.KeybindingOption
|
||||||
@ -58,7 +57,6 @@ import app.ui.dialogs.ResetBranchDialog
|
|||||||
import app.viewmodels.LogSearch
|
import app.viewmodels.LogSearch
|
||||||
import app.viewmodels.LogStatus
|
import app.viewmodels.LogStatus
|
||||||
import app.viewmodels.LogViewModel
|
import app.viewmodels.LogViewModel
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
|
@ -10,6 +10,7 @@ import app.git.diff.FormatDiffUseCase
|
|||||||
import app.git.diff.Hunk
|
import app.git.diff.Hunk
|
||||||
import app.preferences.AppSettings
|
import app.preferences.AppSettings
|
||||||
import app.git.diff.GenerateSplitHunkFromDiffResultUseCase
|
import app.git.diff.GenerateSplitHunkFromDiffResultUseCase
|
||||||
|
import app.git.workspace.*
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
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(
|
class DiffViewModel @Inject constructor(
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val formatDiffUseCase: FormatDiffUseCase,
|
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 settings: AppSettings,
|
||||||
private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase,
|
private val generateSplitHunkFromDiffResultUseCase: GenerateSplitHunkFromDiffResultUseCase,
|
||||||
) {
|
) {
|
||||||
@ -115,32 +120,32 @@ class DiffViewModel @Inject constructor(
|
|||||||
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stageHunk(git, diffEntry, hunk)
|
stageHunkUseCase(git, diffEntry, hunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
fun resetHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
showError = true,
|
showError = true,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.resetHunk(git, diffEntry, hunk)
|
resetHunkUseCase(git, diffEntry, hunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.unstageHunk(git, diffEntry, hunk)
|
unstageHunkUseCase(git, diffEntry, hunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageFile(statusEntry: StatusEntry) = tabState.runOperation(
|
fun stageFile(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stage(git, statusEntry)
|
stageEntryUseCase(git, statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unstageFile(statusEntry: StatusEntry) = tabState.runOperation(
|
fun unstageFile(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.unstage(git, statusEntry)
|
unstageEntryUseCase(git, statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelRunningJobs() {
|
fun cancelRunningJobs() {
|
||||||
|
@ -10,6 +10,9 @@ import app.git.graph.GraphNode
|
|||||||
import app.git.remote_operations.DeleteRemoteBranchUseCase
|
import app.git.remote_operations.DeleteRemoteBranchUseCase
|
||||||
import app.git.remote_operations.PullFromSpecificBranchUseCase
|
import app.git.remote_operations.PullFromSpecificBranchUseCase
|
||||||
import app.git.remote_operations.PushToSpecificBranchUseCase
|
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.preferences.AppSettings
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
import app.ui.log.LogDialog
|
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(
|
class LogViewModel @Inject constructor(
|
||||||
private val logManager: LogManager,
|
private val logManager: LogManager,
|
||||||
private val statusManager: StatusManager,
|
private val getStatusSummaryUseCase: GetStatusSummaryUseCase,
|
||||||
|
private val checkHasUncommitedChangedUseCase: CheckHasUncommitedChangedUseCase,
|
||||||
private val getCurrentBranchUseCase: GetCurrentBranchUseCase,
|
private val getCurrentBranchUseCase: GetCurrentBranchUseCase,
|
||||||
private val checkoutRefUseCase: CheckoutRefUseCase,
|
private val checkoutRefUseCase: CheckoutRefUseCase,
|
||||||
private val createBranchOnCommitUseCase: CreateBranchOnCommitUseCase,
|
private val createBranchOnCommitUseCase: CreateBranchOnCommitUseCase,
|
||||||
@ -94,7 +98,7 @@ class LogViewModel @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
val currentBranch = getCurrentBranchUseCase(git)
|
val currentBranch = getCurrentBranchUseCase(git)
|
||||||
|
|
||||||
val statusSummary = statusManager.getStatusSummary(
|
val statusSummary = getStatusSummaryUseCase(
|
||||||
git = git,
|
git = git,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -206,10 +210,10 @@ class LogViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun uncommitedChangesLoadLog(git: Git) {
|
private suspend fun uncommitedChangesLoadLog(git: Git) {
|
||||||
val currentBranch = getCurrentBranchUseCase(git)
|
val currentBranch = getCurrentBranchUseCase(git)
|
||||||
val hasUncommitedChanges = statusManager.hasUncommitedChanges(git)
|
val hasUncommitedChanges = checkHasUncommitedChangedUseCase(git)
|
||||||
|
|
||||||
val statsSummary = if (hasUncommitedChanges) {
|
val statsSummary = if (hasUncommitedChanges) {
|
||||||
statusManager.getStatusSummary(
|
getStatusSummaryUseCase(
|
||||||
git = git,
|
git = git,
|
||||||
)
|
)
|
||||||
} else
|
} else
|
||||||
|
@ -5,6 +5,7 @@ import app.git.remote_operations.DeleteRemoteBranchUseCase
|
|||||||
import app.git.remote_operations.FetchAllBranchesUseCase
|
import app.git.remote_operations.FetchAllBranchesUseCase
|
||||||
import app.git.remote_operations.PullBranchUseCase
|
import app.git.remote_operations.PullBranchUseCase
|
||||||
import app.git.remote_operations.PushBranchUseCase
|
import app.git.remote_operations.PushBranchUseCase
|
||||||
|
import app.git.workspace.StageUntrackedFileUseCase
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ class MenuViewModel @Inject constructor(
|
|||||||
private val pushBranchUseCase: PushBranchUseCase,
|
private val pushBranchUseCase: PushBranchUseCase,
|
||||||
private val fetchAllBranchesUseCase: FetchAllBranchesUseCase,
|
private val fetchAllBranchesUseCase: FetchAllBranchesUseCase,
|
||||||
private val stashManager: StashManager,
|
private val stashManager: StashManager,
|
||||||
private val statusManager: StatusManager,
|
private val stageUntrackedFileUseCase: StageUntrackedFileUseCase,
|
||||||
) {
|
) {
|
||||||
fun pull(rebase: Boolean = false) = tabState.safeProcessing(
|
fun pull(rebase: Boolean = false) = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
@ -40,14 +41,14 @@ class MenuViewModel @Inject constructor(
|
|||||||
fun stash() = tabState.safeProcessing(
|
fun stash() = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stageUntrackedFiles(git)
|
stageUntrackedFileUseCase(git)
|
||||||
stashManager.stash(git, null)
|
stashManager.stash(git, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stashWithMessage(message: String) = tabState.safeProcessing(
|
fun stashWithMessage(message: String) = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stageUntrackedFiles(git)
|
stageUntrackedFileUseCase(git)
|
||||||
stashManager.stash(git, message)
|
stashManager.stash(git, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import app.extensions.delayedStateChange
|
|||||||
import app.extensions.isMerging
|
import app.extensions.isMerging
|
||||||
import app.extensions.isReverting
|
import app.extensions.isReverting
|
||||||
import app.git.*
|
import app.git.*
|
||||||
|
import app.git.workspace.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@ -20,10 +21,19 @@ private const val MIN_TIME_IN_MS_TO_SHOW_LOAD = 500L
|
|||||||
|
|
||||||
class StatusViewModel @Inject constructor(
|
class StatusViewModel @Inject constructor(
|
||||||
private val tabState: TabState,
|
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 rebaseManager: RebaseManager,
|
||||||
private val mergeManager: MergeManager,
|
private val mergeManager: MergeManager,
|
||||||
private val logManager: LogManager,
|
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))
|
private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf(), false))
|
||||||
val stageStatus: StateFlow<StageStatus> = _stageStatus
|
val stageStatus: StateFlow<StageStatus> = _stageStatus
|
||||||
@ -61,38 +71,38 @@ class StatusViewModel @Inject constructor(
|
|||||||
fun stage(statusEntry: StatusEntry) = tabState.runOperation(
|
fun stage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stage(git, statusEntry)
|
stageEntryUseCase(git, statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unstage(statusEntry: StatusEntry) = tabState.runOperation(
|
fun unstage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.unstage(git, statusEntry)
|
unstageEntryUseCase(git, statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun unstageAll() = tabState.safeProcessing(
|
fun unstageAll() = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.unstageAll(git)
|
unstageAllUseCase(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageAll() = tabState.safeProcessing(
|
fun stageAll() = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stageAll(git)
|
stageAllUseCase(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation(
|
fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.reset(git, statusEntry, staged = true)
|
resetEntryUseCase(git, statusEntry, staged = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetUnstaged(statusEntry: StatusEntry) = tabState.runOperation(
|
fun resetUnstaged(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.reset(git, statusEntry, staged = false)
|
resetEntryUseCase(git, statusEntry, staged = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadStatus(git: Git) {
|
private suspend fun loadStatus(git: Git) {
|
||||||
@ -124,9 +134,9 @@ class StatusViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
val status = statusManager.getStatus(git)
|
val status = getStatusUseCase(git)
|
||||||
val staged = statusManager.getStaged(status).sortedBy { it.filePath }
|
val staged = getStagedUseCase(status).sortedBy { it.filePath }
|
||||||
val unstaged = statusManager.getUnstaged(status).sortedBy { it.filePath }
|
val unstaged = getUnstagedUseCase(status).sortedBy { it.filePath }
|
||||||
|
|
||||||
_stageStatus.value = StageStatus.Loaded(staged, unstaged, isPartiallyReloading = false)
|
_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) {
|
private suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
|
||||||
lastUncommitedChangesState = statusManager.hasUncommitedChanges(git)
|
lastUncommitedChangesState = checkHasUncommitedChangedUseCase(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun commit(message: String, amend: Boolean) = tabState.safeProcessing(
|
fun commit(message: String, amend: Boolean) = tabState.safeProcessing(
|
||||||
@ -163,7 +173,7 @@ class StatusViewModel @Inject constructor(
|
|||||||
} else
|
} else
|
||||||
message
|
message
|
||||||
|
|
||||||
statusManager.commit(git, commitMessage, amend)
|
doCommitUseCase(git, commitMessage, amend)
|
||||||
updateCommitMessage("")
|
updateCommitMessage("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user