Added option to pull/push from specific branch
This commit is contained in:
parent
b8e76aa7a8
commit
250ca295b9
@ -6,7 +6,7 @@ The main goal of Gitnuro is to provide a multiplatform open source Git client wi
|
|||||||
can use it nor relying on web technologies.
|
can use it nor relying on web technologies.
|
||||||
|
|
||||||
The project it is still in alpha and many features are lacking or missing, but can be good for basic usage.
|
The project it is still in alpha and many features are lacking or missing, but can be good for basic usage.
|
||||||
|
q
|
||||||
Right now you CAN:
|
Right now you CAN:
|
||||||
|
|
||||||
- View diffs for text based files.
|
- View diffs for text based files.
|
||||||
|
@ -41,6 +41,20 @@ val Ref.simpleLogName: String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val Ref.remoteName: String
|
||||||
|
get() {
|
||||||
|
if(this.isLocal) {
|
||||||
|
throw Exception("Trying to get remote name from a local branch")
|
||||||
|
}
|
||||||
|
val remoteWithoutPrefix = name.replace("refs/remotes/", "")
|
||||||
|
val remoteName = remoteWithoutPrefix.split("/").firstOrNull()
|
||||||
|
|
||||||
|
if(remoteName == null)
|
||||||
|
throw Exception("Invalid remote name")
|
||||||
|
else
|
||||||
|
return remoteName
|
||||||
|
}
|
||||||
|
|
||||||
val Ref.isBranch: Boolean
|
val Ref.isBranch: Boolean
|
||||||
get() {
|
get() {
|
||||||
return this is ObjectIdRef.PeeledNonTag
|
return this is ObjectIdRef.PeeledNonTag
|
||||||
|
@ -2,6 +2,8 @@ package app.git
|
|||||||
|
|
||||||
import app.credentials.GSessionManager
|
import app.credentials.GSessionManager
|
||||||
import app.credentials.HttpCredentialsProvider
|
import app.credentials.HttpCredentialsProvider
|
||||||
|
import app.extensions.remoteName
|
||||||
|
import app.extensions.simpleName
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@ -48,6 +50,33 @@ class RemoteOperationsManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun pullFromBranch(git: Git, rebase: Boolean, remoteBranch: Ref) = withContext(Dispatchers.IO) {
|
||||||
|
val pullResult = git
|
||||||
|
.pull()
|
||||||
|
.setTransportConfigCallback {
|
||||||
|
handleTransportCredentials(it)
|
||||||
|
}
|
||||||
|
.setRemote(remoteBranch.remoteName)
|
||||||
|
.setRemoteBranchName(remoteBranch.simpleName)
|
||||||
|
.setRebase(rebase)
|
||||||
|
.setCredentialsProvider(CredentialsProvider.getDefault())
|
||||||
|
.call()
|
||||||
|
|
||||||
|
if (!pullResult.isSuccessful) {
|
||||||
|
var message = "Pull failed"
|
||||||
|
|
||||||
|
if (rebase) {
|
||||||
|
message = when (pullResult.rebaseResult.status) {
|
||||||
|
RebaseResult.Status.UNCOMMITTED_CHANGES -> "The pull with rebase has failed because you have got uncommited changes"
|
||||||
|
RebaseResult.Status.CONFLICTS -> "Pull with rebase has conflicts, fix them to continue"
|
||||||
|
else -> message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun fetchAll(git: Git) = withContext(Dispatchers.IO) {
|
suspend fun fetchAll(git: Git) = withContext(Dispatchers.IO) {
|
||||||
val remotes = git.remoteList().call()
|
val remotes = git.remoteList().call()
|
||||||
|
|
||||||
@ -71,7 +100,7 @@ class RemoteOperationsManager @Inject constructor(
|
|||||||
.setRefSpecs(RefSpec(currentBranchRefSpec))
|
.setRefSpecs(RefSpec(currentBranchRefSpec))
|
||||||
.setForce(force)
|
.setForce(force)
|
||||||
.apply {
|
.apply {
|
||||||
if(pushTags)
|
if (pushTags)
|
||||||
setPushTags()
|
setPushTags()
|
||||||
}
|
}
|
||||||
.setTransportConfigCallback {
|
.setTransportConfigCallback {
|
||||||
@ -94,6 +123,39 @@ class RemoteOperationsManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun pushToBranch(git: Git, force: Boolean, pushTags: Boolean, remoteBranch: Ref) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val currentBranchRefSpec = git.repository.fullBranch
|
||||||
|
|
||||||
|
val pushResult = git
|
||||||
|
.push()
|
||||||
|
.setRefSpecs(RefSpec("$currentBranchRefSpec:${remoteBranch.simpleName}"))
|
||||||
|
.setRemote(remoteBranch.remoteName)
|
||||||
|
.setForce(force)
|
||||||
|
.apply {
|
||||||
|
if (pushTags)
|
||||||
|
setPushTags()
|
||||||
|
}
|
||||||
|
.setTransportConfigCallback {
|
||||||
|
handleTransportCredentials(it)
|
||||||
|
}
|
||||||
|
.call()
|
||||||
|
|
||||||
|
val results =
|
||||||
|
pushResult.map { it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected } }
|
||||||
|
.flatten()
|
||||||
|
if (results.isNotEmpty()) {
|
||||||
|
val error = StringBuilder()
|
||||||
|
|
||||||
|
results.forEach { result ->
|
||||||
|
error.append(result.statusMessage)
|
||||||
|
error.append("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception(error.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleTransportCredentials(transport: Transport?) {
|
private fun handleTransportCredentials(transport: Transport?) {
|
||||||
if (transport is SshTransport) {
|
if (transport is SshTransport) {
|
||||||
transport.sshSessionFactory = sessionManager.generateSshSessionFactory()
|
transport.sshSessionFactory = sessionManager.generateSshSessionFactory()
|
||||||
|
@ -36,12 +36,15 @@ fun Branches(
|
|||||||
itemContent = { branch ->
|
itemContent = { branch ->
|
||||||
BranchLineEntry(
|
BranchLineEntry(
|
||||||
branch = branch,
|
branch = branch,
|
||||||
|
currentBranchName = currentBranch,
|
||||||
isCurrentBranch = currentBranch == branch.name,
|
isCurrentBranch = currentBranch == branch.name,
|
||||||
onBranchClicked = { branchesViewModel.selectBranch(branch) },
|
onBranchClicked = { branchesViewModel.selectBranch(branch) },
|
||||||
onCheckoutBranch = { branchesViewModel.checkoutRef(branch) },
|
onCheckoutBranch = { branchesViewModel.checkoutRef(branch) },
|
||||||
onMergeBranch = { setMergeBranch(branch) },
|
onMergeBranch = { setMergeBranch(branch) },
|
||||||
onDeleteBranch = { branchesViewModel.deleteBranch(branch) },
|
onDeleteBranch = { branchesViewModel.deleteBranch(branch) },
|
||||||
onRebaseBranch = { setRebaseBranch(branch) },
|
onRebaseBranch = { setRebaseBranch(branch) },
|
||||||
|
onPushToRemoteBranch = { branchesViewModel.pushToRemoteBranch(branch) },
|
||||||
|
onPullFromRemoteBranch = { branchesViewModel.pullFromRemoteBranch(branch) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -69,22 +72,29 @@ fun Branches(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun BranchLineEntry(
|
private fun BranchLineEntry(
|
||||||
branch: Ref,
|
branch: Ref,
|
||||||
|
currentBranchName: String,
|
||||||
isCurrentBranch: Boolean,
|
isCurrentBranch: Boolean,
|
||||||
onBranchClicked: () -> Unit,
|
onBranchClicked: () -> Unit,
|
||||||
onCheckoutBranch: () -> Unit,
|
onCheckoutBranch: () -> Unit,
|
||||||
onMergeBranch: () -> Unit,
|
onMergeBranch: () -> Unit,
|
||||||
onRebaseBranch: () -> Unit,
|
onRebaseBranch: () -> Unit,
|
||||||
onDeleteBranch: () -> Unit,
|
onDeleteBranch: () -> Unit,
|
||||||
|
onPushToRemoteBranch: () -> Unit,
|
||||||
|
onPullFromRemoteBranch: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ContextMenuArea(
|
ContextMenuArea(
|
||||||
items = {
|
items = {
|
||||||
branchContextMenuItems(
|
branchContextMenuItems(
|
||||||
|
branch = branch,
|
||||||
|
currentBranchName = currentBranchName,
|
||||||
isCurrentBranch = isCurrentBranch,
|
isCurrentBranch = isCurrentBranch,
|
||||||
isLocal = branch.isLocal,
|
isLocal = branch.isLocal,
|
||||||
onCheckoutBranch = onCheckoutBranch,
|
onCheckoutBranch = onCheckoutBranch,
|
||||||
onMergeBranch = onMergeBranch,
|
onMergeBranch = onMergeBranch,
|
||||||
onDeleteBranch = onDeleteBranch,
|
onDeleteBranch = onDeleteBranch,
|
||||||
onRebaseBranch = onRebaseBranch,
|
onRebaseBranch = onRebaseBranch,
|
||||||
|
onPushToRemoteBranch = onPushToRemoteBranch,
|
||||||
|
onPullFromRemoteBranch = onPullFromRemoteBranch,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -2,15 +2,21 @@ 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.extensions.simpleLogName
|
||||||
|
import org.eclipse.jgit.lib.Ref
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun branchContextMenuItems(
|
fun branchContextMenuItems(
|
||||||
|
branch: Ref,
|
||||||
isCurrentBranch: Boolean,
|
isCurrentBranch: Boolean,
|
||||||
|
currentBranchName: String,
|
||||||
isLocal: Boolean,
|
isLocal: Boolean,
|
||||||
onCheckoutBranch: () -> Unit,
|
onCheckoutBranch: () -> Unit,
|
||||||
onMergeBranch: () -> Unit,
|
onMergeBranch: () -> Unit,
|
||||||
onRebaseBranch: () -> Unit,
|
onRebaseBranch: () -> Unit,
|
||||||
onDeleteBranch: () -> Unit,
|
onDeleteBranch: () -> Unit,
|
||||||
|
onPushToRemoteBranch: () -> Unit,
|
||||||
|
onPullFromRemoteBranch: () -> Unit,
|
||||||
): List<ContextMenuItem> {
|
): List<ContextMenuItem> {
|
||||||
return mutableListOf<ContextMenuItem>().apply {
|
return mutableListOf<ContextMenuItem>().apply {
|
||||||
if (!isCurrentBranch) {
|
if (!isCurrentBranch) {
|
||||||
@ -41,5 +47,19 @@ fun branchContextMenuItems(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (!isLocal) {
|
||||||
|
add(
|
||||||
|
ContextMenuItem(
|
||||||
|
label = "Push $currentBranchName to ${branch.simpleLogName}",
|
||||||
|
onClick = onPushToRemoteBranch
|
||||||
|
)
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
ContextMenuItem(
|
||||||
|
label = "Pull ${branch.simpleLogName} to $currentBranchName",
|
||||||
|
onClick = onPullFromRemoteBranch
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -573,6 +573,8 @@ fun CommitLine(
|
|||||||
onDeleteBranch = { ref -> logViewModel.deleteBranch(ref) },
|
onDeleteBranch = { ref -> logViewModel.deleteBranch(ref) },
|
||||||
onDeleteTag = { ref -> logViewModel.deleteTag(ref) },
|
onDeleteTag = { ref -> logViewModel.deleteTag(ref) },
|
||||||
onRebaseBranch = { ref -> onRebaseBranch(ref) },
|
onRebaseBranch = { ref -> onRebaseBranch(ref) },
|
||||||
|
onPushRemoteBranch = { ref -> logViewModel.pushToRemoteBranch(ref) },
|
||||||
|
onPullRemoteBranch = { ref -> logViewModel.pullFromRemoteBranch(ref) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,6 +594,8 @@ fun CommitMessage(
|
|||||||
onDeleteBranch: (ref: Ref) -> Unit,
|
onDeleteBranch: (ref: Ref) -> Unit,
|
||||||
onRebaseBranch: (ref: Ref) -> Unit,
|
onRebaseBranch: (ref: Ref) -> Unit,
|
||||||
onDeleteTag: (ref: Ref) -> Unit,
|
onDeleteTag: (ref: Ref) -> Unit,
|
||||||
|
onPushRemoteBranch: (ref: Ref) -> Unit,
|
||||||
|
onPullRemoteBranch: (ref: Ref) -> Unit,
|
||||||
) {
|
) {
|
||||||
val textColor = if (selected) {
|
val textColor = if (selected) {
|
||||||
MaterialTheme.colors.primary
|
MaterialTheme.colors.primary
|
||||||
@ -632,11 +636,14 @@ fun CommitMessage(
|
|||||||
BranchChip(
|
BranchChip(
|
||||||
ref = ref,
|
ref = ref,
|
||||||
color = nodeColor,
|
color = nodeColor,
|
||||||
|
currentBranch = currentBranch?.name.orEmpty(),
|
||||||
isCurrentBranch = ref.isSameBranch(currentBranch),
|
isCurrentBranch = ref.isSameBranch(currentBranch),
|
||||||
onCheckoutBranch = { onCheckoutRef(ref) },
|
onCheckoutBranch = { onCheckoutRef(ref) },
|
||||||
onMergeBranch = { onMergeBranch(ref) },
|
onMergeBranch = { onMergeBranch(ref) },
|
||||||
onDeleteBranch = { onDeleteBranch(ref) },
|
onDeleteBranch = { onDeleteBranch(ref) },
|
||||||
onRebaseBranch = { onRebaseBranch(ref) },
|
onRebaseBranch = { onRebaseBranch(ref) },
|
||||||
|
onPullRemoteBranch = { onPullRemoteBranch(ref) },
|
||||||
|
onPushRemoteBranch = { onPushRemoteBranch(ref) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -818,20 +825,27 @@ fun BranchChip(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isCurrentBranch: Boolean = false,
|
isCurrentBranch: Boolean = false,
|
||||||
ref: Ref,
|
ref: Ref,
|
||||||
|
currentBranch: String,
|
||||||
onCheckoutBranch: () -> Unit,
|
onCheckoutBranch: () -> Unit,
|
||||||
onMergeBranch: () -> Unit,
|
onMergeBranch: () -> Unit,
|
||||||
onDeleteBranch: () -> Unit,
|
onDeleteBranch: () -> Unit,
|
||||||
onRebaseBranch: () -> Unit,
|
onRebaseBranch: () -> Unit,
|
||||||
|
onPushRemoteBranch: () -> Unit,
|
||||||
|
onPullRemoteBranch: () -> Unit,
|
||||||
color: Color,
|
color: Color,
|
||||||
) {
|
) {
|
||||||
val contextMenuItemsList = {
|
val contextMenuItemsList = {
|
||||||
branchContextMenuItems(
|
branchContextMenuItems(
|
||||||
|
branch = ref,
|
||||||
|
currentBranchName = currentBranch,
|
||||||
isCurrentBranch = isCurrentBranch,
|
isCurrentBranch = isCurrentBranch,
|
||||||
isLocal = ref.isLocal,
|
isLocal = ref.isLocal,
|
||||||
onCheckoutBranch = onCheckoutBranch,
|
onCheckoutBranch = onCheckoutBranch,
|
||||||
onMergeBranch = onMergeBranch,
|
onMergeBranch = onMergeBranch,
|
||||||
onDeleteBranch = onDeleteBranch,
|
onDeleteBranch = onDeleteBranch,
|
||||||
onRebaseBranch = onRebaseBranch,
|
onRebaseBranch = onRebaseBranch,
|
||||||
|
onPushToRemoteBranch = onPushRemoteBranch,
|
||||||
|
onPullFromRemoteBranch = onPullRemoteBranch,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ class BranchesViewModel @Inject constructor(
|
|||||||
private val branchesManager: BranchesManager,
|
private val branchesManager: BranchesManager,
|
||||||
private val rebaseManager: RebaseManager,
|
private val rebaseManager: RebaseManager,
|
||||||
private val mergeManager: MergeManager,
|
private val mergeManager: MergeManager,
|
||||||
|
private val remoteOperationsManager: RemoteOperationsManager,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
) {
|
) {
|
||||||
private val _branches = MutableStateFlow<List<Ref>>(listOf())
|
private val _branches = MutableStateFlow<List<Ref>>(listOf())
|
||||||
@ -75,4 +76,25 @@ class BranchesViewModel @Inject constructor(
|
|||||||
fun selectBranch(ref: Ref) {
|
fun selectBranch(ref: Ref) {
|
||||||
tabState.newSelectedRef(ref.objectId)
|
tabState.newSelectedRef(ref.objectId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pushToRemoteBranch(branch: Ref) = tabState.safeProcessing(
|
||||||
|
refreshType = RefreshType.ALL_DATA,
|
||||||
|
) { git ->
|
||||||
|
remoteOperationsManager.pushToBranch(
|
||||||
|
git = git,
|
||||||
|
force = false,
|
||||||
|
pushTags = false,
|
||||||
|
remoteBranch = branch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pullFromRemoteBranch(branch: Ref) = tabState.safeProcessing(
|
||||||
|
refreshType = RefreshType.ALL_DATA,
|
||||||
|
) { git ->
|
||||||
|
remoteOperationsManager.pullFromBranch(
|
||||||
|
git = git,
|
||||||
|
rebase = false,
|
||||||
|
remoteBranch = branch,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
|
import app.extensions.simpleName
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import app.git.graph.GraphCommitList
|
import app.git.graph.GraphCommitList
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
@ -18,6 +19,7 @@ class LogViewModel @Inject constructor(
|
|||||||
private val tagsManager: TagsManager,
|
private val tagsManager: TagsManager,
|
||||||
private val mergeManager: MergeManager,
|
private val mergeManager: MergeManager,
|
||||||
private val repositoryManager: RepositoryManager,
|
private val repositoryManager: RepositoryManager,
|
||||||
|
private val remoteOperationsManager: RemoteOperationsManager,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
) {
|
) {
|
||||||
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
||||||
@ -42,6 +44,27 @@ class LogViewModel @Inject constructor(
|
|||||||
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary)
|
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pushToRemoteBranch(branch: Ref) = tabState.safeProcessing(
|
||||||
|
refreshType = RefreshType.ALL_DATA,
|
||||||
|
) { git ->
|
||||||
|
remoteOperationsManager.pushToBranch(
|
||||||
|
git = git,
|
||||||
|
force = false,
|
||||||
|
pushTags = false,
|
||||||
|
remoteBranch = branch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pullFromRemoteBranch(branch: Ref) = tabState.safeProcessing(
|
||||||
|
refreshType = RefreshType.ALL_DATA,
|
||||||
|
) { git ->
|
||||||
|
remoteOperationsManager.pullFromBranch(
|
||||||
|
git = git,
|
||||||
|
rebase = false,
|
||||||
|
remoteBranch = branch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing(
|
fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
) { git ->
|
) { git ->
|
||||||
|
Loading…
Reference in New Issue
Block a user