Added push with lease + fixed push after changing tracking branch

Fixes #147
This commit is contained in:
Abdelilah El Aissaoui 2023-07-16 19:24:30 +02:00
parent 333d57e162
commit 3611f3339c
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
5 changed files with 101 additions and 14 deletions

View File

@ -9,11 +9,15 @@ import javax.inject.Inject
class GetTrackingBranchUseCase @Inject constructor() { class GetTrackingBranchUseCase @Inject constructor() {
operator fun invoke(git: Git, ref: Ref): TrackingBranch? { operator fun invoke(git: Git, ref: Ref): TrackingBranch? {
return this.invoke(git, ref.simpleName)
}
operator fun invoke(git: Git, refName: String): TrackingBranch? {
val repository: Repository = git.repository val repository: Repository = git.repository
val config: Config = repository.config val config: Config = repository.config
val remote: String? = config.getString("branch", ref.simpleName, "remote") val remote: String? = config.getString("branch", refName, "remote")
val branch: String? = config.getString("branch", ref.simpleName, "merge") val branch: String? = config.getString("branch", refName, "merge")
if (remote != null && branch != null) { if (remote != null && branch != null) {
return TrackingBranch(remote, branch.removePrefix(BranchesConstants.UPSTREAM_BRANCH_CONFIG_PREFIX)) return TrackingBranch(remote, branch.removePrefix(BranchesConstants.UPSTREAM_BRANCH_CONFIG_PREFIX))

View File

@ -1,28 +1,70 @@
package com.jetpackduba.gitnuro.git.remote_operations package com.jetpackduba.gitnuro.git.remote_operations
import com.jetpackduba.gitnuro.git.branches.GetTrackingBranchUseCase
import com.jetpackduba.gitnuro.git.isRejected import com.jetpackduba.gitnuro.git.isRejected
import com.jetpackduba.gitnuro.git.statusMessage import com.jetpackduba.gitnuro.git.statusMessage
import com.jetpackduba.gitnuro.preferences.AppSettings
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ProgressMonitor import org.eclipse.jgit.lib.ProgressMonitor
import org.eclipse.jgit.transport.RefLeaseSpec
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
import javax.inject.Inject import javax.inject.Inject
class PushBranchUseCase @Inject constructor( class PushBranchUseCase @Inject constructor(
private val handleTransportUseCase: HandleTransportUseCase, private val handleTransportUseCase: HandleTransportUseCase,
private val getTrackingBranchUseCase: GetTrackingBranchUseCase,
private val appSettings: AppSettings,
) { ) {
suspend operator fun invoke(git: Git, force: Boolean, pushTags: Boolean) = withContext(Dispatchers.IO) { suspend operator fun invoke(git: Git, force: Boolean, pushTags: Boolean) = withContext(Dispatchers.IO) {
val currentBranchRefSpec = git.repository.fullBranch val currentBranch = git.repository.fullBranch
val tracking = getTrackingBranchUseCase(git, git.repository.branch)
val refSpecStr = if (tracking != null) {
"$currentBranch:${Constants.R_HEADS}${tracking.branch}"
} else {
currentBranch
}
val pushResult = git val pushResult = git
.push() .push()
.setRefSpecs(RefSpec(currentBranchRefSpec)) .setRefSpecs(RefSpec(refSpecStr))
.run {
if (tracking != null) {
setRemote(tracking.remote)
} else {
this
}
}
.setForce(force) .setForce(force)
.apply { .run {
if (pushTags) if (force && appSettings.pushWithLease) {
if (tracking != null) {
val remoteBranchName = "${Constants.R_REMOTES}$remote/${tracking.branch}"
val remoteBranchRef = git.repository.findRef(remoteBranchName)
if (remoteBranchRef != null) {
return@run setRefLeaseSpecs(
RefLeaseSpec(
"${Constants.R_HEADS}${tracking.branch}",
remoteBranchRef.objectId.name
)
)
}
}
}
return@run this
}
.run {
if (pushTags) {
setPushTags() setPushTags()
} else {
this
}
} }
.setTransportConfigCallback { handleTransportUseCase(it, git) } .setTransportConfigCallback { handleTransportUseCase(it, git) }
.setProgressMonitor(object : ProgressMonitor { .setProgressMonitor(object : ProgressMonitor {
@ -35,14 +77,28 @@ class PushBranchUseCase @Inject constructor(
}) })
.call() .call()
val results = val results = pushResult
pushResult.map { it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected } } .map {
.flatten() it.remoteUpdates.filter { remoteRefUpdate -> remoteRefUpdate.status.isRejected }
}
.flatten()
if (results.isNotEmpty()) { if (results.isNotEmpty()) {
val error = StringBuilder() val error = StringBuilder()
results.forEach { result -> results.forEach { result ->
error.append(result.statusMessage) val statusMessage = result.statusMessage
val extraMessage = if (statusMessage == "Ref rejected, old object id in remote has changed.") {
"Force push can't be completed without fetching first the remote changes."
} else
null
error.append(statusMessage)
if (extraMessage != null) {
error.append("\n")
error.append(extraMessage)
}
error.append("\n") error.append("\n")
} }

View File

@ -7,10 +7,7 @@ import com.jetpackduba.gitnuro.theme.ColorsScheme
import com.jetpackduba.gitnuro.theme.Theme import com.jetpackduba.gitnuro.theme.Theme
import com.jetpackduba.gitnuro.viewmodels.TextDiffType import com.jetpackduba.gitnuro.viewmodels.TextDiffType
import com.jetpackduba.gitnuro.viewmodels.textDiffTypeFromValue import com.jetpackduba.gitnuro.viewmodels.textDiffTypeFromValue
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
@ -37,6 +34,7 @@ private const val PREF_TERMINAL_PATH = "terminalPath"
private const val PREF_GIT_FF_MERGE = "gitFFMerge" private const val PREF_GIT_FF_MERGE = "gitFFMerge"
private const val PREF_GIT_PULL_REBASE = "gitPullRebase" private const val PREF_GIT_PULL_REBASE = "gitPullRebase"
private const val PREF_GIT_PUSH_WITH_LEASE = "gitPushWithLease"
private const val DEFAULT_COMMITS_LIMIT = 1000 private const val DEFAULT_COMMITS_LIMIT = 1000
private const val DEFAULT_COMMITS_LIMIT_ENABLED = true private const val DEFAULT_COMMITS_LIMIT_ENABLED = true
@ -62,6 +60,9 @@ class AppSettings @Inject constructor() {
private val _pullRebaseFlow = MutableStateFlow(pullRebase) private val _pullRebaseFlow = MutableStateFlow(pullRebase)
val pullRebaseFlow = _pullRebaseFlow.asStateFlow() val pullRebaseFlow = _pullRebaseFlow.asStateFlow()
private val _pushWithLeaseFlow = MutableStateFlow(pushWithLease)
val pushWithLeaseFlow: StateFlow<Boolean> = _pushWithLeaseFlow.asStateFlow()
private val _commitsLimitFlow = MutableSharedFlow<Int>() private val _commitsLimitFlow = MutableSharedFlow<Int>()
val commitsLimitFlow = _commitsLimitFlow.asSharedFlow() val commitsLimitFlow = _commitsLimitFlow.asSharedFlow()
@ -164,6 +165,15 @@ class AppSettings @Inject constructor() {
_pullRebaseFlow.value = value _pullRebaseFlow.value = value
} }
var pushWithLease: Boolean
get() {
return preferences.getBoolean(PREF_GIT_PUSH_WITH_LEASE, true)
}
set(value) {
preferences.putBoolean(PREF_GIT_PUSH_WITH_LEASE, value)
_pushWithLeaseFlow.value = value
}
val commitsLimit: Int val commitsLimit: Int
get() { get() {
return preferences.getInt(PREF_COMMITS_LIMIT, DEFAULT_COMMITS_LIMIT) return preferences.getInt(PREF_COMMITS_LIMIT, DEFAULT_COMMITS_LIMIT)

View File

@ -219,6 +219,7 @@ private fun CommitsHistory(settingsViewModel: SettingsViewModel) {
@Composable @Composable
private fun RemoteActions(settingsViewModel: SettingsViewModel) { private fun RemoteActions(settingsViewModel: SettingsViewModel) {
val pullRebase by settingsViewModel.pullRebaseFlow.collectAsState() val pullRebase by settingsViewModel.pullRebaseFlow.collectAsState()
val pushWithLease by settingsViewModel.pushWithLeaseFlow.collectAsState()
SettingToggle( SettingToggle(
title = "Pull with rebase as default", title = "Pull with rebase as default",
@ -228,6 +229,15 @@ private fun RemoteActions(settingsViewModel: SettingsViewModel) {
settingsViewModel.pullRebase = value settingsViewModel.pullRebase = value
} }
) )
SettingToggle(
title = "Force push with lease",
subtitle = "Check if the local version remote branch is up to date to avoid accidentally overriding unintended commits",
value = pushWithLease,
onValueChanged = { value ->
settingsViewModel.pushWithLease = value
}
)
} }
@Composable @Composable

View File

@ -24,6 +24,7 @@ class SettingsViewModel @Inject constructor(
val themeState = appSettings.themeState val themeState = appSettings.themeState
val ffMergeFlow = appSettings.ffMergeFlow val ffMergeFlow = appSettings.ffMergeFlow
val pullRebaseFlow = appSettings.pullRebaseFlow val pullRebaseFlow = appSettings.pullRebaseFlow
val pushWithLeaseFlow = appSettings.pushWithLeaseFlow
val commitsLimitEnabledFlow = appSettings.commitsLimitEnabledFlow val commitsLimitEnabledFlow = appSettings.commitsLimitEnabledFlow
val swapUncommitedChangesFlow = appSettings.swapUncommitedChangesFlow val swapUncommitedChangesFlow = appSettings.swapUncommitedChangesFlow
val terminalPathFlow = appSettings.terminalPathFlow val terminalPathFlow = appSettings.terminalPathFlow
@ -58,6 +59,12 @@ class SettingsViewModel @Inject constructor(
appSettings.pullRebase = value appSettings.pullRebase = value
} }
var pushWithLease: Boolean
get() = appSettings.pushWithLease
set(value) {
appSettings.pushWithLease = value
}
var theme: Theme var theme: Theme
get() = appSettings.theme get() = appSettings.theme
set(value) { set(value) {