Added push with lease + fixed push after changing tracking branch
Fixes #147
This commit is contained in:
parent
333d57e162
commit
3611f3339c
@ -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))
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user