Added basic version of blame
This commit is contained in:
parent
543545d93d
commit
8e366741ac
@ -33,7 +33,7 @@ class TabState @Inject constructor(
|
|||||||
return git
|
return git
|
||||||
}
|
}
|
||||||
|
|
||||||
val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
private val _refreshData = MutableSharedFlow<RefreshType>()
|
private val _refreshData = MutableSharedFlow<RefreshType>()
|
||||||
val refreshData: Flow<RefreshType> = _refreshData
|
val refreshData: Flow<RefreshType> = _refreshData
|
||||||
|
106
src/main/kotlin/app/ui/Blame.kt
Normal file
106
src/main/kotlin/app/ui/Blame.kt
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
|
package app.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.extensions.lineAt
|
||||||
|
import app.theme.primaryTextColor
|
||||||
|
import app.ui.components.ScrollableLazyColumn
|
||||||
|
import org.eclipse.jgit.blame.BlameResult
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Blame(
|
||||||
|
filePath: String,
|
||||||
|
blameResult: BlameResult,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Header(filePath, onClose = onClose)
|
||||||
|
|
||||||
|
ScrollableLazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
val contents = blameResult.resultContents
|
||||||
|
items(contents.size()) { index ->
|
||||||
|
val line = contents.lineAt(index)
|
||||||
|
val author = blameResult.getSourceAuthor(index)
|
||||||
|
val commit = blameResult.getSourceCommit(index)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().background(MaterialTheme.colors.background)
|
||||||
|
.height(IntrinsicSize.Min),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.width(200.dp).fillMaxHeight().background(MaterialTheme.colors.surface),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = author?.name.orEmpty(),
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = commit.shortMessage,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
|
fontSize = 10.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = line,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Header(
|
||||||
|
filePath: String,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().background(MaterialTheme.colors.surface),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = filePath,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
IconButton(
|
||||||
|
onClick = onClose
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource("close.svg"),
|
||||||
|
contentDescription = "Close diff",
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryTextColor),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,10 @@
|
|||||||
package app.ui
|
package app.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -24,6 +21,7 @@ import app.theme.*
|
|||||||
import app.ui.components.AvatarImage
|
import app.ui.components.AvatarImage
|
||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
import app.ui.components.TooltipText
|
import app.ui.components.TooltipText
|
||||||
|
import app.ui.context_menu.commitedChangesEntriesContextMenuItems
|
||||||
import app.viewmodels.CommitChangesStatus
|
import app.viewmodels.CommitChangesStatus
|
||||||
import app.viewmodels.CommitChangesViewModel
|
import app.viewmodels.CommitChangesViewModel
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
@ -34,7 +32,8 @@ fun CommitChanges(
|
|||||||
commitChangesViewModel: CommitChangesViewModel,
|
commitChangesViewModel: CommitChangesViewModel,
|
||||||
selectedItem: SelectedItem.CommitBasedItem,
|
selectedItem: SelectedItem.CommitBasedItem,
|
||||||
onDiffSelected: (DiffEntry) -> Unit,
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
diffSelected: DiffEntryType?
|
diffSelected: DiffEntryType?,
|
||||||
|
onBlame: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(selectedItem) {
|
LaunchedEffect(selectedItem) {
|
||||||
commitChangesViewModel.loadChanges(selectedItem.revCommit)
|
commitChangesViewModel.loadChanges(selectedItem.revCommit)
|
||||||
@ -52,6 +51,7 @@ fun CommitChanges(
|
|||||||
commit = commitChangesStatus.commit,
|
commit = commitChangesStatus.commit,
|
||||||
changes = commitChangesStatus.changes,
|
changes = commitChangesStatus.changes,
|
||||||
onDiffSelected = onDiffSelected,
|
onDiffSelected = onDiffSelected,
|
||||||
|
onBlame = onBlame
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +62,8 @@ fun CommitChangesView(
|
|||||||
commit: RevCommit,
|
commit: RevCommit,
|
||||||
changes: List<DiffEntry>,
|
changes: List<DiffEntry>,
|
||||||
onDiffSelected: (DiffEntry) -> Unit,
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
diffSelected: DiffEntryType?
|
diffSelected: DiffEntryType?,
|
||||||
|
onBlame: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -118,7 +119,8 @@ fun CommitChangesView(
|
|||||||
CommitLogChanges(
|
CommitLogChanges(
|
||||||
diffSelected = diffSelected,
|
diffSelected = diffSelected,
|
||||||
diffEntries = changes,
|
diffEntries = changes,
|
||||||
onDiffSelected = onDiffSelected
|
onDiffSelected = onDiffSelected,
|
||||||
|
onBlame = onBlame,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,17 +185,27 @@ fun Author(commit: RevCommit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CommitLogChanges(
|
fun CommitLogChanges(
|
||||||
diffEntries: List<DiffEntry>,
|
diffEntries: List<DiffEntry>,
|
||||||
onDiffSelected: (DiffEntry) -> Unit,
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
diffSelected: DiffEntryType?
|
diffSelected: DiffEntryType?,
|
||||||
|
onBlame: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollableLazyColumn(
|
ScrollableLazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
items(items = diffEntries) { diffEntry ->
|
items(items = diffEntries) { diffEntry ->
|
||||||
|
ContextMenuArea(
|
||||||
|
items = {
|
||||||
|
commitedChangesEntriesContextMenuItems(
|
||||||
|
diffEntry,
|
||||||
|
onBlame = { onBlame(diffEntry.filePath) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(40.dp)
|
.height(40.dp)
|
||||||
@ -220,7 +232,7 @@ fun CommitLogChanges(
|
|||||||
tint = diffEntry.iconColor,
|
tint = diffEntry.iconColor,
|
||||||
)
|
)
|
||||||
|
|
||||||
if(diffEntry.parentDirectoryPath.isNotEmpty()) {
|
if (diffEntry.parentDirectoryPath.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = diffEntry.parentDirectoryPath,
|
text = diffEntry.parentDirectoryPath,
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
@ -247,4 +259,5 @@ fun CommitLogChanges(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,6 +14,7 @@ import app.theme.primaryTextColor
|
|||||||
import app.ui.dialogs.NewBranchDialog
|
import app.ui.dialogs.NewBranchDialog
|
||||||
import app.ui.dialogs.RebaseInteractive
|
import app.ui.dialogs.RebaseInteractive
|
||||||
import app.ui.log.Log
|
import app.ui.log.Log
|
||||||
|
import app.viewmodels.BlameState
|
||||||
import app.viewmodels.TabViewModel
|
import app.viewmodels.TabViewModel
|
||||||
import openRepositoryDialog
|
import openRepositoryDialog
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
@ -29,6 +30,7 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
val repositoryState by tabViewModel.repositoryState.collectAsState()
|
val repositoryState by tabViewModel.repositoryState.collectAsState()
|
||||||
val diffSelected by tabViewModel.diffSelected.collectAsState()
|
val diffSelected by tabViewModel.diffSelected.collectAsState()
|
||||||
val selectedItem by tabViewModel.selectedItem.collectAsState()
|
val selectedItem by tabViewModel.selectedItem.collectAsState()
|
||||||
|
val blameState by tabViewModel.blameState.collectAsState()
|
||||||
|
|
||||||
var showNewBranchDialog by remember { mutableStateOf(false) }
|
var showNewBranchDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@ -63,7 +65,7 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
onCreateBranch = { showNewBranchDialog = true }
|
onCreateBranch = { showNewBranchDialog = true }
|
||||||
)
|
)
|
||||||
|
|
||||||
RepoContent(tabViewModel, diffSelected, selectedItem, repositoryState)
|
RepoContent(tabViewModel, diffSelected, selectedItem, repositoryState, blameState)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -75,7 +77,8 @@ fun RepoContent(
|
|||||||
tabViewModel: TabViewModel,
|
tabViewModel: TabViewModel,
|
||||||
diffSelected: DiffEntryType?,
|
diffSelected: DiffEntryType?,
|
||||||
selectedItem: SelectedItem,
|
selectedItem: SelectedItem,
|
||||||
repositoryState: RepositoryState
|
repositoryState: RepositoryState,
|
||||||
|
blameState: BlameState,
|
||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
HorizontalSplitPane {
|
HorizontalSplitPane {
|
||||||
@ -115,6 +118,13 @@ fun RepoContent(
|
|||||||
shape = RoundedCornerShape(4.dp)
|
shape = RoundedCornerShape(4.dp)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
if (blameState is BlameState.Loaded) {
|
||||||
|
Blame(
|
||||||
|
filePath = blameState.filePath,
|
||||||
|
blameResult = blameState.blameResult,
|
||||||
|
onClose = { tabViewModel.resetBlameState() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
when (diffSelected) {
|
when (diffSelected) {
|
||||||
null -> {
|
null -> {
|
||||||
Log(
|
Log(
|
||||||
@ -131,6 +141,7 @@ fun RepoContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
second(minSize = 300.dp) {
|
second(minSize = 300.dp) {
|
||||||
Box(
|
Box(
|
||||||
@ -144,6 +155,11 @@ fun RepoContent(
|
|||||||
selectedEntryType = diffSelected,
|
selectedEntryType = diffSelected,
|
||||||
repositoryState = repositoryState,
|
repositoryState = repositoryState,
|
||||||
onStagedDiffEntrySelected = { diffEntry ->
|
onStagedDiffEntrySelected = { diffEntry ->
|
||||||
|
// TODO: Instead of resetting the state, create a new one where the blame
|
||||||
|
// is "on hold". In this state we can show a bar at the bottom so the user
|
||||||
|
// can click on it and return to the blame
|
||||||
|
tabViewModel.resetBlameState()
|
||||||
|
|
||||||
tabViewModel.newDiffSelected = if (diffEntry != null) {
|
tabViewModel.newDiffSelected = if (diffEntry != null) {
|
||||||
if (repositoryState == RepositoryState.SAFE)
|
if (repositoryState == RepositoryState.SAFE)
|
||||||
DiffEntryType.SafeStagedDiff(diffEntry)
|
DiffEntryType.SafeStagedDiff(diffEntry)
|
||||||
@ -154,11 +170,14 @@ fun RepoContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUnstagedDiffEntrySelected = { diffEntry ->
|
onUnstagedDiffEntrySelected = { diffEntry ->
|
||||||
|
tabViewModel.resetBlameState()
|
||||||
|
|
||||||
if (repositoryState == RepositoryState.SAFE)
|
if (repositoryState == RepositoryState.SAFE)
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
|
||||||
else
|
else
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
|
||||||
}
|
},
|
||||||
|
onBlameFile = { tabViewModel.blameFile(it) }
|
||||||
)
|
)
|
||||||
} else if (safeSelectedItem is SelectedItem.CommitBasedItem) {
|
} else if (safeSelectedItem is SelectedItem.CommitBasedItem) {
|
||||||
CommitChanges(
|
CommitChanges(
|
||||||
@ -166,8 +185,10 @@ fun RepoContent(
|
|||||||
selectedItem = safeSelectedItem,
|
selectedItem = safeSelectedItem,
|
||||||
diffSelected = diffSelected,
|
diffSelected = diffSelected,
|
||||||
onDiffSelected = { diffEntry ->
|
onDiffSelected = { diffEntry ->
|
||||||
|
tabViewModel.resetBlameState()
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
||||||
}
|
},
|
||||||
|
onBlame = { tabViewModel.blameFile(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,14 +40,10 @@ import app.git.StatusEntry
|
|||||||
import app.theme.*
|
import app.theme.*
|
||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
import app.ui.components.SecondaryButton
|
import app.ui.components.SecondaryButton
|
||||||
import app.ui.context_menu.DropDownContent
|
import app.ui.context_menu.*
|
||||||
import app.ui.context_menu.DropDownContentData
|
|
||||||
import app.ui.context_menu.stagedEntriesContextMenuItems
|
|
||||||
import app.ui.context_menu.unstagedEntriesContextMenuItems
|
|
||||||
import app.viewmodels.StageStatus
|
import app.viewmodels.StageStatus
|
||||||
import app.viewmodels.StatusViewModel
|
import app.viewmodels.StatusViewModel
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UncommitedChanges(
|
fun UncommitedChanges(
|
||||||
@ -56,6 +52,7 @@ fun UncommitedChanges(
|
|||||||
repositoryState: RepositoryState,
|
repositoryState: RepositoryState,
|
||||||
onStagedDiffEntrySelected: (StatusEntry?) -> Unit,
|
onStagedDiffEntrySelected: (StatusEntry?) -> Unit,
|
||||||
onUnstagedDiffEntrySelected: (StatusEntry) -> Unit,
|
onUnstagedDiffEntrySelected: (StatusEntry) -> Unit,
|
||||||
|
onBlameFile: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
||||||
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
||||||
@ -106,12 +103,12 @@ fun UncommitedChanges(
|
|||||||
onDiffEntryOptionSelected = {
|
onDiffEntryOptionSelected = {
|
||||||
statusViewModel.unstage(it)
|
statusViewModel.unstage(it)
|
||||||
},
|
},
|
||||||
onGenerateContextMenu = { diffEntry ->
|
onGenerateContextMenu = { statusEntry ->
|
||||||
stagedEntriesContextMenuItems(
|
statusEntriesContextMenuItems(
|
||||||
diffEntry = diffEntry,
|
statusEntry = statusEntry,
|
||||||
onReset = {
|
entryType = EntryType.STAGED,
|
||||||
statusViewModel.resetStaged(diffEntry)
|
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||||
},
|
onReset = { statusViewModel.resetStaged(statusEntry) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onAllAction = {
|
onAllAction = {
|
||||||
@ -134,11 +131,11 @@ fun UncommitedChanges(
|
|||||||
statusViewModel.stage(it)
|
statusViewModel.stage(it)
|
||||||
},
|
},
|
||||||
onGenerateContextMenu = { statusEntry ->
|
onGenerateContextMenu = { statusEntry ->
|
||||||
unstagedEntriesContextMenuItems(
|
statusEntriesContextMenuItems(
|
||||||
statusEntry = statusEntry,
|
statusEntry = statusEntry,
|
||||||
onReset = {
|
entryType = EntryType.UNSTAGED,
|
||||||
statusViewModel.resetUnstaged(statusEntry)
|
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||||
},
|
onReset = { statusViewModel.resetUnstaged(statusEntry) },
|
||||||
onDelete = {
|
onDelete = {
|
||||||
statusViewModel.deleteFile(statusEntry)
|
statusViewModel.deleteFile(statusEntry)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package app.ui.context_menu
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ContextMenuItem
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import app.git.StatusType
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
fun commitedChangesEntriesContextMenuItems(
|
||||||
|
diffEntry: DiffEntry,
|
||||||
|
onBlame: () -> Unit,
|
||||||
|
): List<ContextMenuItem> {
|
||||||
|
return mutableListOf<ContextMenuItem>().apply {
|
||||||
|
if (diffEntry.changeType != DiffEntry.ChangeType.ADD ||
|
||||||
|
diffEntry.changeType != DiffEntry.ChangeType.DELETE) {
|
||||||
|
add(
|
||||||
|
ContextMenuItem(
|
||||||
|
label = "Blame file",
|
||||||
|
onClick = onBlame,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,13 +4,14 @@ import androidx.compose.foundation.ContextMenuItem
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import app.git.StatusEntry
|
import app.git.StatusEntry
|
||||||
import app.git.StatusType
|
import app.git.StatusType
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun unstagedEntriesContextMenuItems(
|
fun statusEntriesContextMenuItems(
|
||||||
statusEntry: StatusEntry,
|
statusEntry: StatusEntry,
|
||||||
|
entryType: EntryType,
|
||||||
onReset: () -> Unit,
|
onReset: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit = {},
|
||||||
|
onBlame: () -> Unit,
|
||||||
): List<ContextMenuItem> {
|
): List<ContextMenuItem> {
|
||||||
return mutableListOf<ContextMenuItem>().apply {
|
return mutableListOf<ContextMenuItem>().apply {
|
||||||
if (statusEntry.statusType != StatusType.ADDED) {
|
if (statusEntry.statusType != StatusType.ADDED) {
|
||||||
@ -20,9 +21,21 @@ fun unstagedEntriesContextMenuItems(
|
|||||||
onClick = onReset,
|
onClick = onReset,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (statusEntry.statusType != StatusType.REMOVED) {
|
if (statusEntry.statusType != StatusType.REMOVED) {
|
||||||
|
add(
|
||||||
|
ContextMenuItem(
|
||||||
|
label = "Blame file",
|
||||||
|
onClick = onBlame,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
entryType == EntryType.UNSTAGED &&
|
||||||
|
statusEntry.statusType != StatusType.REMOVED
|
||||||
|
) {
|
||||||
add(
|
add(
|
||||||
ContextMenuItem(
|
ContextMenuItem(
|
||||||
label = "Delete file",
|
label = "Delete file",
|
||||||
@ -32,3 +45,9 @@ fun unstagedEntriesContextMenuItems(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class EntryType {
|
||||||
|
STAGED,
|
||||||
|
UNSTAGED,
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
import app.AppPreferences
|
|
||||||
import app.AppStateManager
|
import app.AppStateManager
|
||||||
import app.ErrorsManager
|
import app.ErrorsManager
|
||||||
import app.credentials.CredentialsState
|
import app.credentials.CredentialsState
|
||||||
@ -15,6 +14,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.blame.BlameResult
|
||||||
import org.eclipse.jgit.lib.Repository
|
import org.eclipse.jgit.lib.Repository
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -75,6 +75,9 @@ class TabViewModel @Inject constructor(
|
|||||||
private val _repositoryState = MutableStateFlow(RepositoryState.SAFE)
|
private val _repositoryState = MutableStateFlow(RepositoryState.SAFE)
|
||||||
val repositoryState: StateFlow<RepositoryState> = _repositoryState
|
val repositoryState: StateFlow<RepositoryState> = _repositoryState
|
||||||
|
|
||||||
|
private val _blameState = MutableStateFlow<BlameState>(BlameState.None)
|
||||||
|
val blameState: StateFlow<BlameState> = _blameState
|
||||||
|
|
||||||
val showError = MutableStateFlow(false)
|
val showError = MutableStateFlow(false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -315,6 +318,28 @@ class TabViewModel @Inject constructor(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun blameFile(filePath: String) = tabState.safeProcessing(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { git ->
|
||||||
|
_blameState.value = BlameState.Loading(filePath)
|
||||||
|
try {
|
||||||
|
val result = git.blame()
|
||||||
|
.setFilePath(filePath)
|
||||||
|
.setFollowFileRenames(true)
|
||||||
|
.call()
|
||||||
|
|
||||||
|
_blameState.value = BlameState.Loaded(filePath, result)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
resetBlameState()
|
||||||
|
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetBlameState() {
|
||||||
|
_blameState.value = BlameState.None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -323,3 +348,10 @@ sealed class RepositorySelectionStatus {
|
|||||||
data class Opening(val path: String) : RepositorySelectionStatus()
|
data class Opening(val path: String) : RepositorySelectionStatus()
|
||||||
data class Open(val repository: Repository) : RepositorySelectionStatus()
|
data class Open(val repository: Repository) : RepositorySelectionStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sealed interface BlameState {
|
||||||
|
data class Loading(val filePath: String) : BlameState
|
||||||
|
data class Loaded(val filePath: String, val blameResult: BlameResult) : BlameState
|
||||||
|
object None : BlameState
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user