Skip to content

Push with refspec #2542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions asyncgit/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
62 changes: 62 additions & 0 deletions asyncgit/src/sync/branch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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)]
Expand Down
46 changes: 46 additions & 0 deletions asyncgit/src/sync/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion asyncgit/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
32 changes: 27 additions & 5 deletions asyncgit/src/sync/remotes/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -92,7 +97,7 @@ impl AsyncProgress for ProgressNotification {
}

///
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PushType {
///
Branch,
Expand Down Expand Up @@ -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());

Expand All @@ -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
Expand Down