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.
|
||||
|
||||
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.
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
@ -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 ->
|
||||
|
Loading…
Reference in New Issue
Block a user