Added change upstream branch option
This commit is contained in:
parent
5fd29fbc39
commit
91094a8771
@ -0,0 +1,8 @@
|
||||
package com.jetpackduba.gitnuro.git.branches
|
||||
|
||||
object BranchesConstants {
|
||||
/**
|
||||
* Prefix added before the upstream branch name in .git/config
|
||||
*/
|
||||
const val UPSTREAM_BRANCH_CONFIG_PREFIX = "refs/heads/"
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.jetpackduba.gitnuro.git.branches
|
||||
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Config
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetTrackingBranchUseCase @Inject constructor() {
|
||||
operator fun invoke(git: Git, ref: Ref): TrackingBranch? {
|
||||
val repository: Repository = git.repository
|
||||
|
||||
val config: Config = repository.config
|
||||
val remote: String? = config.getString("branch", ref.simpleName, "remote")
|
||||
val branch: String? = config.getString("branch", ref.simpleName, "merge")
|
||||
|
||||
if (remote != null && branch != null) {
|
||||
return TrackingBranch(remote, branch.removePrefix(BranchesConstants.UPSTREAM_BRANCH_CONFIG_PREFIX))
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
data class TrackingBranch(val remote: String, val branch: String)
|
@ -0,0 +1,31 @@
|
||||
package com.jetpackduba.gitnuro.git.branches
|
||||
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Config
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
import org.eclipse.jgit.lib.StoredConfig
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetTrackingBranchUseCase @Inject constructor() {
|
||||
operator fun invoke(git: Git, ref: Ref, remoteName: String?, remoteBranch: Ref?) {
|
||||
val repository: Repository = git.repository
|
||||
val config: StoredConfig = repository.config
|
||||
|
||||
if (remoteName == null || remoteBranch == null) {
|
||||
config.unset("branch", ref.simpleName, "remote")
|
||||
config.unset("branch", ref.simpleName, "merge")
|
||||
} else {
|
||||
config.setString("branch", ref.simpleName, "remote", remoteName)
|
||||
config.setString(
|
||||
"branch",
|
||||
ref.simpleName,
|
||||
"merge",
|
||||
BranchesConstants.UPSTREAM_BRANCH_CONFIG_PREFIX + remoteBranch.simpleName
|
||||
)
|
||||
}
|
||||
|
||||
config.save()
|
||||
}
|
||||
}
|
@ -80,19 +80,16 @@ val Colors.isDark: Boolean
|
||||
get() = !this.isLight
|
||||
|
||||
|
||||
enum class Theme(val displayName: String) : DropDownOption {
|
||||
enum class Theme(val displayName: String) {
|
||||
LIGHT("Light"),
|
||||
DARK("Dark"),
|
||||
DARK_GRAY("Dark gray"),
|
||||
CUSTOM("Custom");
|
||||
|
||||
override val optionName: String
|
||||
get() = displayName
|
||||
}
|
||||
|
||||
val themeLists = listOf(
|
||||
Theme.LIGHT,
|
||||
Theme.DARK,
|
||||
Theme.DARK_GRAY,
|
||||
Theme.CUSTOM,
|
||||
DropDownOption(Theme.LIGHT, Theme.LIGHT.displayName),
|
||||
DropDownOption(Theme.DARK, Theme.DARK.displayName),
|
||||
DropDownOption(Theme.DARK_GRAY, Theme.DARK_GRAY.displayName),
|
||||
DropDownOption(Theme.CUSTOM, Theme.CUSTOM.displayName),
|
||||
)
|
@ -18,6 +18,7 @@ import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
import com.jetpackduba.gitnuro.ui.components.*
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.*
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.EditRemotesDialog
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.*
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
@ -42,6 +43,7 @@ fun SidePanel(
|
||||
val submodulesState by submodulesViewModel.submodules.collectAsState()
|
||||
|
||||
var showEditRemotesDialog by remember { mutableStateOf(false) }
|
||||
val (branchToChangeUpstream, setBranchToChangeUpstream) = remember { mutableStateOf<Ref?>(null) }
|
||||
|
||||
Column {
|
||||
FilterTextField(
|
||||
@ -62,6 +64,7 @@ fun SidePanel(
|
||||
localBranches(
|
||||
branchesState = branchesState,
|
||||
branchesViewModel = branchesViewModel,
|
||||
onChangeDefaultUpstreamBranch = { setBranchToChangeUpstream(it) }
|
||||
)
|
||||
|
||||
remotes(
|
||||
@ -95,6 +98,14 @@ fun SidePanel(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (branchToChangeUpstream != null) {
|
||||
SetDefaultUpstreamBranchDialog(
|
||||
viewModel = gitnuroDynamicViewModel(),
|
||||
branch = branchToChangeUpstream,
|
||||
onClose = { setBranchToChangeUpstream(null) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -123,6 +134,7 @@ fun FilterTextField(value: String, onValueChange: (String) -> Unit, modifier: Mo
|
||||
fun LazyListScope.localBranches(
|
||||
branchesState: BranchesState,
|
||||
branchesViewModel: BranchesViewModel,
|
||||
onChangeDefaultUpstreamBranch: (Ref) -> Unit,
|
||||
) {
|
||||
val isExpanded = branchesState.isExpanded
|
||||
val branches = branchesState.branches
|
||||
@ -157,6 +169,7 @@ fun LazyListScope.localBranches(
|
||||
onRebaseBranch = { branchesViewModel.rebaseBranch(branch) },
|
||||
onPushToRemoteBranch = { branchesViewModel.pushToRemoteBranch(branch) },
|
||||
onPullFromRemoteBranch = { branchesViewModel.pullFromRemoteBranch(branch) },
|
||||
onChangeDefaultUpstreamBranch = { onChangeDefaultUpstreamBranch(branch) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -344,6 +357,7 @@ private fun Branch(
|
||||
onDeleteBranch: () -> Unit,
|
||||
onPushToRemoteBranch: () -> Unit,
|
||||
onPullFromRemoteBranch: () -> Unit,
|
||||
onChangeDefaultUpstreamBranch: () -> Unit,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = {
|
||||
@ -358,6 +372,7 @@ private fun Branch(
|
||||
onRebaseBranch = onRebaseBranch,
|
||||
onPushToRemoteBranch = onPushToRemoteBranch,
|
||||
onPullFromRemoteBranch = onPullFromRemoteBranch,
|
||||
onChangeDefaultUpstreamBranch = onChangeDefaultUpstreamBranch
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
@ -0,0 +1,106 @@
|
||||
package com.jetpackduba.gitnuro.ui.components
|
||||
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.AppIcons
|
||||
import com.jetpackduba.gitnuro.extensions.lowercaseContains
|
||||
import com.jetpackduba.gitnuro.ui.dropdowns.DropDownOption
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun FilterDropdownPreview() {
|
||||
val items = listOf(
|
||||
DropDownOption("","Test1"),
|
||||
DropDownOption("","Test2"),
|
||||
DropDownOption("","Test3"),
|
||||
DropDownOption("","Test4"),
|
||||
)
|
||||
|
||||
FilterDropdown(
|
||||
dropdownItems = items,
|
||||
currentOption = items[0],
|
||||
onOptionSelected = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> FilterDropdown(
|
||||
dropdownItems: List<DropDownOption<T>>,
|
||||
currentOption: DropDownOption<T>?,
|
||||
width: Dp = 240.dp,
|
||||
onOptionSelected: (DropDownOption<T>) -> Unit,
|
||||
) {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
var filter by remember { mutableStateOf("") }
|
||||
val filterFocusRequester = remember { FocusRequester() }
|
||||
val filteredDropdownItems = remember(filter, dropdownItems) { dropdownItems.filter { it.optionName.lowercaseContains(filter) } }
|
||||
|
||||
Box {
|
||||
OutlinedButton(
|
||||
onClick = { showDropdown = true },
|
||||
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MaterialTheme.colors.background),
|
||||
modifier = Modifier.width(width)
|
||||
) {
|
||||
Text(
|
||||
text = currentOption?.optionName ?: "",
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
Icon(
|
||||
painter = painterResource(AppIcons.DROPDOWN),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.onBackground,
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = showDropdown,
|
||||
onDismissRequest = { showDropdown = false },
|
||||
modifier = Modifier.width(width),
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
onClick = {},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
AdjustableOutlinedTextField(
|
||||
value = filter,
|
||||
onValueChange = { filter = it },
|
||||
modifier = Modifier.focusable(showDropdown)
|
||||
.focusRequester(filterFocusRequester)
|
||||
)
|
||||
}
|
||||
|
||||
for (dropDownOption in filteredDropdownItems) {
|
||||
DropdownMenuItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
onOptionSelected(dropDownOption)
|
||||
showDropdown = false
|
||||
}
|
||||
) {
|
||||
Text(dropDownOption.optionName, style = MaterialTheme.typography.body2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(showDropdown) {
|
||||
if (showDropdown) {
|
||||
filterFocusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -262,4 +262,10 @@ inline fun <reified T> gitnuroViewModel(): T {
|
||||
return remember(tab) {
|
||||
tab.tabViewModelsHolder.viewModels[T::class] as T
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
inline fun <reified T> gitnuroDynamicViewModel(): T {
|
||||
val tab = LocalTabScope.current
|
||||
|
||||
return tab.tabViewModelsHolder.dynamicViewModel(T::class) as T
|
||||
}
|
@ -18,6 +18,7 @@ fun branchContextMenuItems(
|
||||
onDeleteRemoteBranch: () -> Unit = {},
|
||||
onPushToRemoteBranch: () -> Unit,
|
||||
onPullFromRemoteBranch: () -> Unit,
|
||||
onChangeDefaultUpstreamBranch: () -> Unit,
|
||||
): List<ContextMenuElement> {
|
||||
return mutableListOf<ContextMenuElement>().apply {
|
||||
if (!isCurrentBranch) {
|
||||
@ -68,6 +69,16 @@ fun branchContextMenuItems(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if(isLocal) {
|
||||
add(
|
||||
ContextMenuElement.ContextTextEntry(
|
||||
label = "Change default upstream branch",
|
||||
onClick = onChangeDefaultUpstreamBranch
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (!isLocal) {
|
||||
add(
|
||||
ContextMenuElement.ContextTextEntry(
|
||||
|
@ -0,0 +1,196 @@
|
||||
package com.jetpackduba.gitnuro.ui.dialogs
|
||||
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.AppIcons
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.git.remotes.RemoteInfo
|
||||
import com.jetpackduba.gitnuro.ui.components.FilterDropdown
|
||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||
import com.jetpackduba.gitnuro.ui.dropdowns.DropDownOption
|
||||
import com.jetpackduba.gitnuro.viewmodels.ChangeDefaultUpstreamBranchViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.SetDefaultUpstreamBranchState
|
||||
import org.eclipse.jgit.lib.ObjectIdRef
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SetDefaultUpstreamBranchDialogPreview() {
|
||||
SetDefaultUpstreamBranchDialogView(
|
||||
state = SetDefaultUpstreamBranchState.Loaded(
|
||||
ObjectIdRef.PeeledNonTag(null, "TestBranch", null),
|
||||
null,
|
||||
emptyList(),
|
||||
null,
|
||||
null
|
||||
),
|
||||
onClose = {},
|
||||
setSelectedRemote = {},
|
||||
setSelectedBranch = {},
|
||||
changeDefaultUpstreamBranch = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetDefaultUpstreamBranchDialog(
|
||||
viewModel: ChangeDefaultUpstreamBranchViewModel,
|
||||
branch: Ref,
|
||||
onClose: () -> Unit,
|
||||
) {
|
||||
LaunchedEffect(branch) {
|
||||
viewModel.init(branch)
|
||||
}
|
||||
|
||||
val setDefaultUpstreamBranchState = viewModel.setDefaultUpstreamBranchState.collectAsState().value
|
||||
LaunchedEffect(setDefaultUpstreamBranchState) {
|
||||
if (setDefaultUpstreamBranchState is SetDefaultUpstreamBranchState.UpstreamChanged) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
MaterialDialog(onCloseRequested = onClose) {
|
||||
SetDefaultUpstreamBranchDialogView(
|
||||
state = setDefaultUpstreamBranchState,
|
||||
onClose = onClose,
|
||||
setSelectedRemote = { viewModel.setSelectedRemote(it) },
|
||||
setSelectedBranch = { viewModel.setSelectedBranch(it) },
|
||||
changeDefaultUpstreamBranch = { viewModel.changeDefaultUpstreamBranch() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun SetDefaultUpstreamBranchDialogView(
|
||||
state: SetDefaultUpstreamBranchState,
|
||||
onClose: () -> Unit,
|
||||
setSelectedRemote: (RemoteInfo) -> Unit,
|
||||
setSelectedBranch: (Ref) -> Unit,
|
||||
changeDefaultUpstreamBranch: () -> Unit,
|
||||
) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
painterResource(AppIcons.BRANCH),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.size(64.dp),
|
||||
tint = MaterialTheme.colors.onBackground,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Change upstream branch",
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
style = MaterialTheme.typography.body1,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Set the upstream remote branch",
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
style = MaterialTheme.typography.body2,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
|
||||
if (state is SetDefaultUpstreamBranchState.Loaded) {
|
||||
|
||||
val remotesDropDown =
|
||||
state.remotes.map { DropDownOption(it, it.remoteConfig.name) }
|
||||
|
||||
val selectedRemote = state.selectedRemote
|
||||
val selectedRemoteOption = if (selectedRemote != null) {
|
||||
DropDownOption(selectedRemote, selectedRemote.remoteConfig.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val selectedBranch = state.selectedBranch
|
||||
val selectedBranchOption = if (selectedBranch != null) {
|
||||
DropDownOption(selectedBranch, selectedBranch.simpleName)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val branchesDropDown = remember(selectedRemote) {
|
||||
selectedRemote?.branchesList?.map { ref ->
|
||||
DropDownOption(ref, ref.simpleName)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Remote",
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
style = MaterialTheme.typography.body1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
FilterDropdown(
|
||||
remotesDropDown,
|
||||
selectedRemoteOption,
|
||||
width = 400.dp,
|
||||
onOptionSelected = { setSelectedRemote(it.value) }
|
||||
)
|
||||
|
||||
|
||||
Text(
|
||||
text = "Branch",
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
style = MaterialTheme.typography.body1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
FilterDropdown(
|
||||
branchesDropDown ?: emptyList(),
|
||||
selectedBranchOption,
|
||||
width = 400.dp,
|
||||
onOptionSelected = { setSelectedBranch(it.value) }
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.align(Alignment.End)
|
||||
) {
|
||||
PrimaryButton(
|
||||
text = "Cancel",
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
onClick = onClose,
|
||||
backgroundColor = Color.Transparent,
|
||||
textColor = MaterialTheme.colors.onBackground,
|
||||
)
|
||||
PrimaryButton(
|
||||
onClick = {
|
||||
changeDefaultUpstreamBranch()
|
||||
},
|
||||
text = "Change"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.ErrorDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.MaterialDialog
|
||||
import com.jetpackduba.gitnuro.ui.dropdowns.DropDownOption
|
||||
import com.jetpackduba.gitnuro.ui.dropdowns.ScaleDropDown
|
||||
import com.jetpackduba.gitnuro.viewmodels.SettingsViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@ -168,9 +167,9 @@ fun UiSettings(settingsViewModel: SettingsViewModel) {
|
||||
title = "Theme",
|
||||
subtitle = "Select the UI theme between light and dark mode",
|
||||
dropDownOptions = themeLists,
|
||||
currentOption = currentTheme,
|
||||
onOptionSelected = { theme ->
|
||||
settingsViewModel.theme = theme
|
||||
currentOption = DropDownOption(currentTheme, currentTheme.displayName),
|
||||
onOptionSelected = { themeDropDown ->
|
||||
settingsViewModel.theme = themeDropDown.value
|
||||
}
|
||||
)
|
||||
|
||||
@ -199,12 +198,12 @@ fun UiSettings(settingsViewModel: SettingsViewModel) {
|
||||
var options by remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
ScaleDropDown(1f, "100%"),
|
||||
ScaleDropDown(1.25f, "125%"),
|
||||
ScaleDropDown(1.5f, "150%"),
|
||||
ScaleDropDown(2f, "200%"),
|
||||
ScaleDropDown(2.5f, "250%"),
|
||||
ScaleDropDown(3f, "300%"),
|
||||
DropDownOption(1f, "100%"),
|
||||
DropDownOption(1.25f, "125%"),
|
||||
DropDownOption(1.5f, "150%"),
|
||||
DropDownOption(2f, "200%"),
|
||||
DropDownOption(2.5f, "250%"),
|
||||
DropDownOption(3f, "300%"),
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -221,7 +220,7 @@ fun UiSettings(settingsViewModel: SettingsViewModel) {
|
||||
|
||||
if (matchingOption == null) { // Scale that we haven't taken in consideration
|
||||
// Create a new scale and add it to the options list
|
||||
matchingOption = ScaleDropDown(scaleUi, "${(scaleUi * 100).toInt()}%")
|
||||
matchingOption = DropDownOption(scaleUi, "${(scaleUi * 100).toInt()}%")
|
||||
val newOptions = options.toMutableList()
|
||||
newOptions.add(matchingOption)
|
||||
newOptions.sortBy { it.value }
|
||||
@ -275,12 +274,12 @@ fun Category(
|
||||
|
||||
|
||||
@Composable
|
||||
fun <T : DropDownOption> SettingDropDown(
|
||||
fun <T> SettingDropDown(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
dropDownOptions: List<T>,
|
||||
onOptionSelected: (T) -> Unit,
|
||||
currentOption: T,
|
||||
dropDownOptions: List<DropDownOption<T>>,
|
||||
onOptionSelected: (DropDownOption<T>) -> Unit,
|
||||
currentOption: DropDownOption<T>,
|
||||
) {
|
||||
var showThemeDropdown by remember { mutableStateOf(false) }
|
||||
Row(
|
||||
|
@ -1,5 +1,3 @@
|
||||
package com.jetpackduba.gitnuro.ui.dropdowns
|
||||
|
||||
interface DropDownOption {
|
||||
val optionName: String
|
||||
}
|
||||
data class DropDownOption<T>(val value: T, val optionName: String)
|
@ -1,3 +0,0 @@
|
||||
package com.jetpackduba.gitnuro.ui.dropdowns
|
||||
|
||||
class ScaleDropDown(val value: Float, override val optionName: String) : DropDownOption
|
@ -48,11 +48,13 @@ import com.jetpackduba.gitnuro.theme.*
|
||||
import com.jetpackduba.gitnuro.ui.SelectedItem
|
||||
import com.jetpackduba.gitnuro.ui.components.AvatarImage
|
||||
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.*
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.NewBranchDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.NewTagDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.ResetBranchDialog
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog
|
||||
import com.jetpackduba.gitnuro.viewmodels.LogSearch
|
||||
import com.jetpackduba.gitnuro.viewmodels.LogStatus
|
||||
import com.jetpackduba.gitnuro.viewmodels.LogViewModel
|
||||
@ -436,6 +438,7 @@ fun MessagesList(
|
||||
onRebaseBranch = onRebase,
|
||||
onRebaseInteractive = { logViewModel.rebaseInteractive(graphNode) },
|
||||
onRevCommitSelected = { logViewModel.selectLogLine(graphNode) },
|
||||
onChangeDefaultUpstreamBranch = { onShowLogDialog(LogDialog.ChangeDefaultBranch(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -587,6 +590,14 @@ fun LogDialogs(
|
||||
|
||||
LogDialog.None -> {
|
||||
}
|
||||
|
||||
is LogDialog.ChangeDefaultBranch -> {
|
||||
SetDefaultUpstreamBranchDialog(
|
||||
viewModel = gitnuroDynamicViewModel(),
|
||||
branch = showLogDialog.ref,
|
||||
onClose = { onResetShowLogDialog() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -772,6 +783,7 @@ fun CommitLine(
|
||||
onRebaseBranch: (Ref) -> Unit,
|
||||
onRevCommitSelected: () -> Unit,
|
||||
onRebaseInteractive: () -> Unit,
|
||||
onChangeDefaultUpstreamBranch: (Ref) -> Unit,
|
||||
) {
|
||||
val isLastCommitOfCurrentBranch = currentBranch?.objectId?.name == graphNode.id.name
|
||||
|
||||
@ -828,6 +840,7 @@ fun CommitLine(
|
||||
onRebaseBranch = { ref -> onRebaseBranch(ref) },
|
||||
onPushRemoteBranch = { ref -> logViewModel.pushToRemoteBranch(ref) },
|
||||
onPullRemoteBranch = { ref -> logViewModel.pullFromRemoteBranch(ref) },
|
||||
onChangeDefaultUpstreamBranch = { ref -> onChangeDefaultUpstreamBranch(ref) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -849,6 +862,7 @@ fun CommitMessage(
|
||||
onDeleteTag: (ref: Ref) -> Unit,
|
||||
onPushRemoteBranch: (ref: Ref) -> Unit,
|
||||
onPullRemoteBranch: (ref: Ref) -> Unit,
|
||||
onChangeDefaultUpstreamBranch: (ref: Ref) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@ -884,6 +898,7 @@ fun CommitMessage(
|
||||
onRebaseBranch = { onRebaseBranch(ref) },
|
||||
onPullRemoteBranch = { onPullRemoteBranch(ref) },
|
||||
onPushRemoteBranch = { onPushRemoteBranch(ref) },
|
||||
onChangeDefaultUpstreamBranch = { onChangeDefaultUpstreamBranch(ref) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1092,6 +1107,7 @@ fun BranchChip(
|
||||
onRebaseBranch: () -> Unit,
|
||||
onPushRemoteBranch: () -> Unit,
|
||||
onPullRemoteBranch: () -> Unit,
|
||||
onChangeDefaultUpstreamBranch: () -> Unit,
|
||||
color: Color,
|
||||
) {
|
||||
val contextMenuItemsList = {
|
||||
@ -1107,6 +1123,7 @@ fun BranchChip(
|
||||
onRebaseBranch = onRebaseBranch,
|
||||
onPushToRemoteBranch = onPushRemoteBranch,
|
||||
onPullFromRemoteBranch = onPullRemoteBranch,
|
||||
onChangeDefaultUpstreamBranch = onChangeDefaultUpstreamBranch,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package com.jetpackduba.gitnuro.ui.log
|
||||
|
||||
import com.jetpackduba.gitnuro.git.graph.GraphNode
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
|
||||
sealed class LogDialog {
|
||||
object None : LogDialog()
|
||||
data class NewBranch(val graphNode: GraphNode) : LogDialog()
|
||||
data class NewTag(val graphNode: GraphNode) : LogDialog()
|
||||
data class ResetBranch(val graphNode: GraphNode) : LogDialog()
|
||||
data class ChangeDefaultBranch(val ref: Ref) : LogDialog()
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.git.RefreshType
|
||||
import com.jetpackduba.gitnuro.git.TabState
|
||||
import com.jetpackduba.gitnuro.git.branches.GetRemoteBranchesUseCase
|
||||
import com.jetpackduba.gitnuro.git.branches.GetTrackingBranchUseCase
|
||||
import com.jetpackduba.gitnuro.git.branches.SetTrackingBranchUseCase
|
||||
import com.jetpackduba.gitnuro.git.branches.TrackingBranch
|
||||
import com.jetpackduba.gitnuro.git.remotes.GetRemotesUseCase
|
||||
import com.jetpackduba.gitnuro.git.remotes.RemoteInfo
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import javax.inject.Inject
|
||||
|
||||
class ChangeDefaultUpstreamBranchViewModel @Inject constructor(
|
||||
private val tabState: TabState,
|
||||
private val getRemoteBranchesUseCase: GetRemoteBranchesUseCase,
|
||||
private val getRemotesUseCase: GetRemotesUseCase,
|
||||
private val getTrackingBranchUseCase: GetTrackingBranchUseCase,
|
||||
private val setTrackingBranchUseCase: SetTrackingBranchUseCase,
|
||||
) {
|
||||
private val _setDefaultUpstreamBranchState =
|
||||
MutableStateFlow<SetDefaultUpstreamBranchState>(SetDefaultUpstreamBranchState.Loading)
|
||||
val setDefaultUpstreamBranchState: StateFlow<SetDefaultUpstreamBranchState> =
|
||||
_setDefaultUpstreamBranchState
|
||||
|
||||
fun init(branch: Ref) = tabState.runOperation(
|
||||
refreshType = RefreshType.NONE
|
||||
) { git ->
|
||||
_setDefaultUpstreamBranchState.value = SetDefaultUpstreamBranchState.Loading
|
||||
|
||||
val trackingBranch = getTrackingBranchUseCase(git, branch)
|
||||
val remoteBranches = getRemoteBranchesUseCase(git)
|
||||
val remotes = getRemotesUseCase(git, remoteBranches)
|
||||
|
||||
var remote: RemoteInfo? = null
|
||||
var remoteBranch: Ref? = null
|
||||
|
||||
if (trackingBranch != null) {
|
||||
remote = remotes.firstOrNull { it.remoteConfig.name == trackingBranch.remote }
|
||||
remoteBranch = remote?.branchesList?.firstOrNull { it.simpleName == trackingBranch.branch }
|
||||
}
|
||||
|
||||
_setDefaultUpstreamBranchState.value =
|
||||
SetDefaultUpstreamBranchState.Loaded(
|
||||
branch = branch,
|
||||
trackingBranch = trackingBranch,
|
||||
remotes = remotes,
|
||||
selectedRemote = remote,
|
||||
selectedBranch = remoteBranch
|
||||
)
|
||||
}
|
||||
|
||||
fun changeDefaultUpstreamBranch() = tabState.runOperation(
|
||||
refreshType = RefreshType.NONE,
|
||||
) { git ->
|
||||
val state = _setDefaultUpstreamBranchState.value
|
||||
|
||||
if (state is SetDefaultUpstreamBranchState.Loaded) {
|
||||
setTrackingBranchUseCase(
|
||||
git = git,
|
||||
ref = state.branch,
|
||||
remoteName = state.selectedRemote?.remoteConfig?.name,
|
||||
remoteBranch = state.selectedBranch
|
||||
)
|
||||
|
||||
_setDefaultUpstreamBranchState.value = SetDefaultUpstreamBranchState.UpstreamChanged
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedBranch(branchOption: Ref) {
|
||||
val state = _setDefaultUpstreamBranchState.value
|
||||
|
||||
if (state is SetDefaultUpstreamBranchState.Loaded) {
|
||||
_setDefaultUpstreamBranchState.value = state.copy(selectedBranch = branchOption)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedRemote(remote: RemoteInfo) {
|
||||
val state = setDefaultUpstreamBranchState.value
|
||||
val remoteConfig = remote.remoteConfig
|
||||
|
||||
if (state is SetDefaultUpstreamBranchState.Loaded) {
|
||||
val branch = if (remoteConfig.name == state.trackingBranch?.remote) {
|
||||
remote.branchesList.firstOrNull { it.simpleName == state.trackingBranch?.branch }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
_setDefaultUpstreamBranchState.value = state.copy(
|
||||
selectedRemote = remote,
|
||||
selectedBranch = branch,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface SetDefaultUpstreamBranchState {
|
||||
object Loading : SetDefaultUpstreamBranchState
|
||||
data class Loaded(
|
||||
val branch: Ref,
|
||||
val trackingBranch: TrackingBranch?,
|
||||
val remotes: List<RemoteInfo>,
|
||||
val selectedRemote: RemoteInfo?,
|
||||
val selectedBranch: Ref?,
|
||||
) : SetDefaultUpstreamBranchState
|
||||
|
||||
object UpstreamChanged : SetDefaultUpstreamBranchState
|
||||
}
|
@ -4,6 +4,7 @@ import com.jetpackduba.gitnuro.di.TabScope
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.SidePanelViewModel
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@TabScope
|
||||
class TabViewModelsHolder @Inject constructor(
|
||||
@ -19,7 +20,8 @@ class TabViewModelsHolder @Inject constructor(
|
||||
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
||||
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
||||
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
||||
) {
|
||||
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
||||
) {
|
||||
val viewModels = mapOf(
|
||||
logViewModel::class to logViewModel,
|
||||
sidePanelViewModel::class to sidePanelViewModel,
|
||||
@ -29,4 +31,16 @@ class TabViewModelsHolder @Inject constructor(
|
||||
cloneViewModel::class to cloneViewModel,
|
||||
settingsViewModel::class to settingsViewModel,
|
||||
)
|
||||
|
||||
// TODO Call this when required
|
||||
fun dynamicViewModel(type: KClass<*>): Any {
|
||||
return when(type) {
|
||||
DiffViewModel::class -> diffViewModelProvider.get()
|
||||
RebaseInteractiveViewModel::class -> rebaseInteractiveViewModelProvider.get()
|
||||
HistoryViewModel::class -> historyViewModelProvider.get()
|
||||
AuthorViewModel::class -> authorViewModelProvider.get()
|
||||
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()
|
||||
else -> throw NotImplementedError("View model provider not implemented")
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,6 @@ class BranchesViewModel @AssistedInject constructor(
|
||||
)
|
||||
|
||||
init {
|
||||
|
||||
tabScope.launch {
|
||||
tabState.refreshFlowFiltered(RefreshType.ALL_DATA)
|
||||
{
|
||||
|
@ -0,0 +1,54 @@
|
||||
package com.jetpackduba.gitnuro.git.branches
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
import org.eclipse.jgit.lib.StoredConfig
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class GetTrackingBranchUseCaseTest {
|
||||
private val gitMock: Git = mockk(relaxed = true)
|
||||
private val repositoryMock: Repository = mockk(relaxed = true)
|
||||
private val configMock: StoredConfig = mockk(relaxed = true)
|
||||
private val refMock: Ref = mockk(relaxed = true)
|
||||
|
||||
private val localBranchName = "feature-branch"
|
||||
private val remoteName = "origin"
|
||||
private val remoteBranchFullName = "refs/heads/main"
|
||||
private val remoteBranchShortName = "main"
|
||||
private val objectId = ObjectId.zeroId()
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
every { gitMock.repository } returns repositoryMock
|
||||
every { refMock.name } returns "refs/heads/$localBranchName"
|
||||
every { refMock.objectId } returns objectId
|
||||
every { repositoryMock.config } returns configMock
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns null when remote not found`() {
|
||||
every { configMock.getString("branch", localBranchName, "remote") } returns null
|
||||
every { configMock.getString("branch", localBranchName, "merge") } returns null
|
||||
|
||||
val result = GetTrackingBranchUseCase()(gitMock, refMock)
|
||||
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns tracking branch when local and remote branches exist`() {
|
||||
every { configMock.getString("branch", localBranchName, "remote") } returns remoteName
|
||||
every { configMock.getString("branch", localBranchName, "merge") } returns remoteBranchFullName
|
||||
|
||||
val result = GetTrackingBranchUseCase()(gitMock, refMock)
|
||||
|
||||
assertEquals(TrackingBranch(remoteName, remoteBranchShortName), result)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user