Skip to content

Commit 979fa68

Browse files
authored
Push with refspec (#2542)
* push: respect `branch.*.merge` when push default is upstream
1 parent a91132d commit 979fa68

File tree

6 files changed

+141
-6
lines changed

6 files changed

+141
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
* After commit: jump back to unstaged area [[@tommady](https://github.com/tommady)] ([#2476](https://github.com/extrawurst/gitui/issues/2476))
1818
* 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))
1919
* use OSC52 copying in case other methods fail [[@naseschwarz](https://github.com/naseschwarz)] ([#2366](https://github.com/gitui-org/gitui/issues/2366))
20+
* 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))
2021

2122
## [0.27.0] - 2024-01-14
2223

asyncgit/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ pub enum Error {
5353
#[error("git error:{0}")]
5454
Git(#[from] git2::Error),
5555

56+
///
57+
#[error("git config error: {0}")]
58+
GitConfig(String),
59+
5660
///
5761
#[error("strip prefix error: {0}")]
5862
StripPrefix(#[from] StripPrefixError),

asyncgit/src/sync/branch/mod.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,25 @@ pub fn get_branch_remote(
243243
}
244244
}
245245

246+
/// Retrieve the upstream merge of a local `branch`,
247+
/// configured in "branch.*.merge"
248+
///
249+
/// For details check git2 `branch_upstream_merge`
250+
pub fn get_branch_upstream_merge(
251+
repo_path: &RepoPath,
252+
branch: &str,
253+
) -> Result<Option<String>> {
254+
let repo = repo(repo_path)?;
255+
let branch = repo.find_branch(branch, BranchType::Local)?;
256+
let reference = bytes2string(branch.get().name_bytes())?;
257+
let remote_name = repo.branch_upstream_merge(&reference).ok();
258+
if let Some(remote_name) = remote_name {
259+
Ok(Some(bytes2string(remote_name.as_ref())?))
260+
} else {
261+
Ok(None)
262+
}
263+
}
264+
246265
/// returns whether the pull merge strategy is set to rebase
247266
pub fn config_is_pull_rebase(repo_path: &RepoPath) -> Result<bool> {
248267
let repo = repo(repo_path)?;
@@ -673,6 +692,49 @@ mod tests_branches {
673692

674693
assert!(get_branch_remote(repo_path, "foo").is_err());
675694
}
695+
696+
#[test]
697+
fn test_branch_no_upstream_merge_config() {
698+
let (_r, repo) = repo_init().unwrap();
699+
let root = repo.path().parent().unwrap();
700+
let repo_path: &RepoPath =
701+
&root.as_os_str().to_str().unwrap().into();
702+
703+
let upstream_merge_res =
704+
get_branch_upstream_merge(&repo_path, "master");
705+
assert!(
706+
upstream_merge_res.is_ok_and(|v| v.as_ref().is_none())
707+
);
708+
}
709+
710+
#[test]
711+
fn test_branch_with_upstream_merge_config() {
712+
let (_r, repo) = repo_init().unwrap();
713+
let root = repo.path().parent().unwrap();
714+
let repo_path: &RepoPath =
715+
&root.as_os_str().to_str().unwrap().into();
716+
717+
let branch_name = "master";
718+
let upstrem_merge = "refs/heads/master";
719+
720+
let mut config = repo.config().unwrap();
721+
config
722+
.set_str(
723+
&format!("branch.{branch_name}.merge"),
724+
&upstrem_merge,
725+
)
726+
.expect("fail set branch merge config");
727+
728+
let upstream_merge_res =
729+
get_branch_upstream_merge(&repo_path, &branch_name);
730+
assert!(upstream_merge_res
731+
.as_ref()
732+
.is_ok_and(|v| v.as_ref().is_some()));
733+
assert_eq!(
734+
&upstream_merge_res.unwrap().unwrap(),
735+
upstrem_merge
736+
);
737+
}
676738
}
677739

678740
#[cfg(test)]

asyncgit/src/sync/config.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,52 @@ pub fn untracked_files_config_repo(
6262
Ok(ShowUntrackedFilesConfig::All)
6363
}
6464

65+
// see https://git-scm.com/docs/git-config#Documentation/git-config.txt-pushdefault
66+
/// represents `push.default` git config
67+
#[derive(PartialEq, Eq)]
68+
pub enum PushDefaultStrategyConfig {
69+
Nothing,
70+
Current,
71+
Upstream,
72+
Simple,
73+
Matching,
74+
}
75+
76+
impl Default for PushDefaultStrategyConfig {
77+
fn default() -> Self {
78+
Self::Simple
79+
}
80+
}
81+
82+
impl<'a> TryFrom<&'a str> for PushDefaultStrategyConfig {
83+
type Error = crate::Error;
84+
fn try_from(
85+
value: &'a str,
86+
) -> std::result::Result<Self, Self::Error> {
87+
match value {
88+
"nothing" => Ok(Self::Nothing),
89+
"current" => Ok(Self::Current),
90+
"upstream" | "tracking" => Ok(Self::Upstream),
91+
"simple" => Ok(Self::Simple),
92+
"matching" => Ok(Self::Matching),
93+
_ => Err(crate::Error::GitConfig(format!(
94+
"malformed value for push.default: {value}, must be one of nothing, matching, simple, upstream or current"
95+
))),
96+
}
97+
}
98+
}
99+
100+
pub fn push_default_strategy_config_repo(
101+
repo: &Repository,
102+
) -> Result<PushDefaultStrategyConfig> {
103+
(get_config_string_repo(repo, "push.default")?).map_or_else(
104+
|| Ok(PushDefaultStrategyConfig::default()),
105+
|entry_str| {
106+
PushDefaultStrategyConfig::try_from(entry_str.as_str())
107+
},
108+
)
109+
}
110+
65111
///
66112
pub fn untracked_files_config(
67113
repo_path: &RepoPath,

asyncgit/src/sync/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub use blame::{blame_file, BlameHunk, FileBlame};
3939
pub use branch::{
4040
branch_compare_upstream, checkout_branch, checkout_commit,
4141
config_is_pull_rebase, create_branch, delete_branch,
42-
get_branch_remote, get_branches_info,
42+
get_branch_remote, get_branch_upstream_merge, get_branches_info,
4343
merge_commit::merge_upstream_commit,
4444
merge_ff::branch_merge_upstream_fastforward,
4545
merge_rebase::merge_upstream_rebase, rename::rename_branch,

asyncgit/src/sync/remotes/push.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ use crate::{
33
progress::ProgressPercent,
44
sync::{
55
branch::branch_set_upstream_after_push,
6+
config::{
7+
push_default_strategy_config_repo,
8+
PushDefaultStrategyConfig,
9+
},
610
cred::BasicAuthCredential,
11+
get_branch_upstream_merge,
712
remotes::{proxy_auto, Callbacks},
813
repository::repo,
914
CommitId, RepoPath,
@@ -92,7 +97,7 @@ impl AsyncProgress for ProgressNotification {
9297
}
9398

9499
///
95-
#[derive(Copy, Clone, Debug)]
100+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
96101
pub enum PushType {
97102
///
98103
Branch,
@@ -145,6 +150,9 @@ pub fn push_raw(
145150
let repo = repo(repo_path)?;
146151
let mut remote = repo.find_remote(remote)?;
147152

153+
let push_default_strategy =
154+
push_default_strategy_config_repo(&repo)?;
155+
148156
let mut options = PushOptions::new();
149157
options.proxy_options(proxy_auto());
150158

@@ -158,14 +166,28 @@ pub fn push_raw(
158166
(true, false) => "+",
159167
(false, false) => "",
160168
};
161-
let ref_type = match ref_type {
169+
let git_ref_type = match ref_type {
162170
PushType::Branch => "heads",
163171
PushType::Tag => "tags",
164172
};
165173

166-
let branch_name =
167-
format!("{branch_modifier}refs/{ref_type}/{branch}");
168-
remote.push(&[branch_name.as_str()], Some(&mut options))?;
174+
let mut push_ref =
175+
format!("{branch_modifier}refs/{git_ref_type}/{branch}");
176+
177+
if !delete
178+
&& ref_type == PushType::Branch
179+
&& push_default_strategy
180+
== PushDefaultStrategyConfig::Upstream
181+
{
182+
if let Ok(Some(branch_upstream_merge)) =
183+
get_branch_upstream_merge(repo_path, branch)
184+
{
185+
push_ref.push_str(&format!(":{branch_upstream_merge}"));
186+
}
187+
}
188+
189+
log::debug!("push to: {push_ref}");
190+
remote.push(&[push_ref], Some(&mut options))?;
169191

170192
if let Some((reference, msg)) =
171193
callbacks.get_stats()?.push_rejected_msg

0 commit comments

Comments
 (0)