Added option to pull/push from specific branch

This commit is contained in:
Abdelilah El Aissaoui 2022-02-24 14:25:15 +01:00
parent b8e76aa7a8
commit 250ca295b9
8 changed files with 167 additions and 2 deletions

View File

@ -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.

View File

@ -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

View File

@ -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()

View File

@ -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,
) )
} }
) { ) {

View File

@ -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
)
)
}
} }
} }

View File

@ -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,
) )
} }

View File

@ -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,
)
}
} }

View File

@ -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 ->