Push with refspec (#2542)

* push: respect `branch.*.merge` when push default is upstream
This commit is contained in:
vlad-anger 2025-03-18 14:35:39 -03:00 committed by GitHub
parent a91132d187
commit 979fa68837
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 141 additions and 6 deletions

View file

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* After commit: jump back to unstaged area [[@tommady](https://github.com/tommady)] ([#2476](https://github.com/extrawurst/gitui/issues/2476))
* The default key to close the commit error message popup is now the Escape key [[@wessamfathi](https://github.com/wessamfathi)] ([#2552](https://github.com/extrawurst/gitui/issues/2552))
* use OSC52 copying in case other methods fail [[@naseschwarz](https://github.com/naseschwarz)] ([#2366](https://github.com/gitui-org/gitui/issues/2366))
* push: respect `branch.*.merge` when push default is upstream [[@vlad-anger](https://github.com/vlad-anger)] ([#2542](https://github.com/gitui-org/gitui/pull/2542))
## [0.27.0] - 2024-01-14

View file

@ -53,6 +53,10 @@ pub enum Error {
#[error("git error:{0}")]
Git(#[from] git2::Error),
///
#[error("git config error: {0}")]
GitConfig(String),
///
#[error("strip prefix error: {0}")]
StripPrefix(#[from] StripPrefixError),

View file

@ -243,6 +243,25 @@ pub fn get_branch_remote(
}
}
/// Retrieve the upstream merge of a local `branch`,
/// configured in "branch.*.merge"
///
/// For details check git2 `branch_upstream_merge`
pub fn get_branch_upstream_merge(
repo_path: &RepoPath,
branch: &str,
) -> Result<Option<String>> {
let repo = repo(repo_path)?;
let branch = repo.find_branch(branch, BranchType::Local)?;
let reference = bytes2string(branch.get().name_bytes())?;
let remote_name = repo.branch_upstream_merge(&reference).ok();
if let Some(remote_name) = remote_name {
Ok(Some(bytes2string(remote_name.as_ref())?))
} else {
Ok(None)
}
}
/// returns whether the pull merge strategy is set to rebase
pub fn config_is_pull_rebase(repo_path: &RepoPath) -> Result<bool> {
let repo = repo(repo_path)?;
@ -673,6 +692,49 @@ mod tests_branches {
assert!(get_branch_remote(repo_path, "foo").is_err());
}
#[test]
fn test_branch_no_upstream_merge_config() {
let (_r, repo) = repo_init().unwrap();
let root = repo.path().parent().unwrap();
let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into();
let upstream_merge_res =
get_branch_upstream_merge(&repo_path, "master");
assert!(
upstream_merge_res.is_ok_and(|v| v.as_ref().is_none())
);
}
#[test]
fn test_branch_with_upstream_merge_config() {
let (_r, repo) = repo_init().unwrap();
let root = repo.path().parent().unwrap();
let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into();
let branch_name = "master";
let upstrem_merge = "refs/heads/master";
let mut config = repo.config().unwrap();
config
.set_str(
&format!("branch.{branch_name}.merge"),
&upstrem_merge,
)
.expect("fail set branch merge config");
let upstream_merge_res =
get_branch_upstream_merge(&repo_path, &branch_name);
assert!(upstream_merge_res
.as_ref()
.is_ok_and(|v| v.as_ref().is_some()));
assert_eq!(
&upstream_merge_res.unwrap().unwrap(),
upstrem_merge
);
}
}
#[cfg(test)]

View file

@ -62,6 +62,52 @@ pub fn untracked_files_config_repo(
Ok(ShowUntrackedFilesConfig::All)
}
// see https://git-scm.com/docs/git-config#Documentation/git-config.txt-pushdefault
/// represents `push.default` git config
#[derive(PartialEq, Eq)]
pub enum PushDefaultStrategyConfig {
Nothing,
Current,
Upstream,
Simple,
Matching,
}
impl Default for PushDefaultStrategyConfig {
fn default() -> Self {
Self::Simple
}
}
impl<'a> TryFrom<&'a str> for PushDefaultStrategyConfig {
type Error = crate::Error;
fn try_from(
value: &'a str,
) -> std::result::Result<Self, Self::Error> {
match value {
"nothing" => Ok(Self::Nothing),
"current" => Ok(Self::Current),
"upstream" | "tracking" => Ok(Self::Upstream),
"simple" => Ok(Self::Simple),
"matching" => Ok(Self::Matching),
_ => Err(crate::Error::GitConfig(format!(
"malformed value for push.default: {value}, must be one of nothing, matching, simple, upstream or current"
))),
}
}
}
pub fn push_default_strategy_config_repo(
repo: &Repository,
) -> Result<PushDefaultStrategyConfig> {
(get_config_string_repo(repo, "push.default")?).map_or_else(
|| Ok(PushDefaultStrategyConfig::default()),
|entry_str| {
PushDefaultStrategyConfig::try_from(entry_str.as_str())
},
)
}
///
pub fn untracked_files_config(
repo_path: &RepoPath,

View file

@ -39,7 +39,7 @@ pub use blame::{blame_file, BlameHunk, FileBlame};
pub use branch::{
branch_compare_upstream, checkout_branch, checkout_commit,
config_is_pull_rebase, create_branch, delete_branch,
get_branch_remote, get_branches_info,
get_branch_remote, get_branch_upstream_merge, get_branches_info,
merge_commit::merge_upstream_commit,
merge_ff::branch_merge_upstream_fastforward,
merge_rebase::merge_upstream_rebase, rename::rename_branch,

View file

@ -3,7 +3,12 @@ use crate::{
progress::ProgressPercent,
sync::{
branch::branch_set_upstream_after_push,
config::{
push_default_strategy_config_repo,
PushDefaultStrategyConfig,
},
cred::BasicAuthCredential,
get_branch_upstream_merge,
remotes::{proxy_auto, Callbacks},
repository::repo,
CommitId, RepoPath,
@ -92,7 +97,7 @@ impl AsyncProgress for ProgressNotification {
}
///
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PushType {
///
Branch,
@ -145,6 +150,9 @@ pub fn push_raw(
let repo = repo(repo_path)?;
let mut remote = repo.find_remote(remote)?;
let push_default_strategy =
push_default_strategy_config_repo(&repo)?;
let mut options = PushOptions::new();
options.proxy_options(proxy_auto());
@ -158,14 +166,28 @@ pub fn push_raw(
(true, false) => "+",
(false, false) => "",
};
let ref_type = match ref_type {
let git_ref_type = match ref_type {
PushType::Branch => "heads",
PushType::Tag => "tags",
};
let branch_name =
format!("{branch_modifier}refs/{ref_type}/{branch}");
remote.push(&[branch_name.as_str()], Some(&mut options))?;
let mut push_ref =
format!("{branch_modifier}refs/{git_ref_type}/{branch}");
if !delete
&& ref_type == PushType::Branch
&& push_default_strategy
== PushDefaultStrategyConfig::Upstream
{
if let Ok(Some(branch_upstream_merge)) =
get_branch_upstream_merge(repo_path, branch)
{
push_ref.push_str(&format!(":{branch_upstream_merge}"));
}
}
log::debug!("push to: {push_ref}");
remote.push(&[push_ref], Some(&mut options))?;
if let Some((reference, msg)) =
callbacks.get_stats()?.push_rejected_msg