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.
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:
- 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
get() {
return this is ObjectIdRef.PeeledNonTag

View File

@ -2,6 +2,8 @@ package app.git
import app.credentials.GSessionManager
import app.credentials.HttpCredentialsProvider
import app.extensions.remoteName
import app.extensions.simpleName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
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) {
val remotes = git.remoteList().call()
@ -71,7 +100,7 @@ class RemoteOperationsManager @Inject constructor(
.setRefSpecs(RefSpec(currentBranchRefSpec))
.setForce(force)
.apply {
if(pushTags)
if (pushTags)
setPushTags()
}
.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?) {
if (transport is SshTransport) {
transport.sshSessionFactory = sessionManager.generateSshSessionFactory()

View File

@ -36,12 +36,15 @@ fun Branches(
itemContent = { branch ->
BranchLineEntry(
branch = branch,
currentBranchName = currentBranch,
isCurrentBranch = currentBranch == branch.name,
onBranchClicked = { branchesViewModel.selectBranch(branch) },
onCheckoutBranch = { branchesViewModel.checkoutRef(branch) },
onMergeBranch = { setMergeBranch(branch) },
onDeleteBranch = { branchesViewModel.deleteBranch(branch) },
onRebaseBranch = { setRebaseBranch(branch) },
onPushToRemoteBranch = { branchesViewModel.pushToRemoteBranch(branch) },
onPullFromRemoteBranch = { branchesViewModel.pullFromRemoteBranch(branch) },
)
}
)
@ -69,22 +72,29 @@ fun Branches(
@Composable
private fun BranchLineEntry(
branch: Ref,
currentBranchName: String,
isCurrentBranch: Boolean,
onBranchClicked: () -> Unit,
onCheckoutBranch: () -> Unit,
onMergeBranch: () -> Unit,
onRebaseBranch: () -> Unit,
onDeleteBranch: () -> Unit,
onPushToRemoteBranch: () -> Unit,
onPullFromRemoteBranch: () -> Unit,
) {
ContextMenuArea(
items = {
branchContextMenuItems(
branch = branch,
currentBranchName = currentBranchName,
isCurrentBranch = isCurrentBranch,
isLocal = branch.isLocal,
onCheckoutBranch = onCheckoutBranch,
onMergeBranch = onMergeBranch,
onDeleteBranch = onDeleteBranch,
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.ExperimentalFoundationApi
import app.extensions.simpleLogName
import org.eclipse.jgit.lib.Ref
@OptIn(ExperimentalFoundationApi::class)
fun branchContextMenuItems(
branch: Ref,
isCurrentBranch: Boolean,
currentBranchName: String,
isLocal: Boolean,
onCheckoutBranch: () -> Unit,
onMergeBranch: () -> Unit,
onRebaseBranch: () -> Unit,
onDeleteBranch: () -> Unit,
onPushToRemoteBranch: () -> Unit,
onPullFromRemoteBranch: () -> Unit,
): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply {
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) },
onDeleteTag = { ref -> logViewModel.deleteTag(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,
onRebaseBranch: (ref: Ref) -> Unit,
onDeleteTag: (ref: Ref) -> Unit,
onPushRemoteBranch: (ref: Ref) -> Unit,
onPullRemoteBranch: (ref: Ref) -> Unit,
) {
val textColor = if (selected) {
MaterialTheme.colors.primary
@ -632,11 +636,14 @@ fun CommitMessage(
BranchChip(
ref = ref,
color = nodeColor,
currentBranch = currentBranch?.name.orEmpty(),
isCurrentBranch = ref.isSameBranch(currentBranch),
onCheckoutBranch = { onCheckoutRef(ref) },
onMergeBranch = { onMergeBranch(ref) },
onDeleteBranch = { onDeleteBranch(ref) },
onRebaseBranch = { onRebaseBranch(ref) },
onPullRemoteBranch = { onPullRemoteBranch(ref) },
onPushRemoteBranch = { onPushRemoteBranch(ref) },
)
}
}
@ -818,20 +825,27 @@ fun BranchChip(
modifier: Modifier = Modifier,
isCurrentBranch: Boolean = false,
ref: Ref,
currentBranch: String,
onCheckoutBranch: () -> Unit,
onMergeBranch: () -> Unit,
onDeleteBranch: () -> Unit,
onRebaseBranch: () -> Unit,
onPushRemoteBranch: () -> Unit,
onPullRemoteBranch: () -> Unit,
color: Color,
) {
val contextMenuItemsList = {
branchContextMenuItems(
branch = ref,
currentBranchName = currentBranch,
isCurrentBranch = isCurrentBranch,
isLocal = ref.isLocal,
onCheckoutBranch = onCheckoutBranch,
onMergeBranch = onMergeBranch,
onDeleteBranch = onDeleteBranch,
onRebaseBranch = onRebaseBranch,
onPushToRemoteBranch = onPushRemoteBranch,
onPullFromRemoteBranch = onPullRemoteBranch,
)
}

View File

@ -11,6 +11,7 @@ class BranchesViewModel @Inject constructor(
private val branchesManager: BranchesManager,
private val rebaseManager: RebaseManager,
private val mergeManager: MergeManager,
private val remoteOperationsManager: RemoteOperationsManager,
private val tabState: TabState,
) {
private val _branches = MutableStateFlow<List<Ref>>(listOf())
@ -75,4 +76,25 @@ class BranchesViewModel @Inject constructor(
fun selectBranch(ref: Ref) {
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
import app.extensions.simpleName
import app.git.*
import app.git.graph.GraphCommitList
import app.ui.SelectedItem
@ -18,6 +19,7 @@ class LogViewModel @Inject constructor(
private val tagsManager: TagsManager,
private val mergeManager: MergeManager,
private val repositoryManager: RepositoryManager,
private val remoteOperationsManager: RemoteOperationsManager,
private val tabState: TabState,
) {
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
@ -42,6 +44,27 @@ class LogViewModel @Inject constructor(
_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(
refreshType = RefreshType.ALL_DATA,
) { git ->