Skip to content

Commit 914231b

Browse files
committed
use correct remote advertised tips for pre_push hook
1 parent 21a245c commit 914231b

File tree

4 files changed

+91
-72
lines changed

4 files changed

+91
-72
lines changed

asyncgit/src/sync/hooks.rs

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use super::{repository::repo, RepoPath};
22
use crate::{
3-
error::Result, sync::remotes::tags::tags_missing_remote,
3+
error::Result,
4+
sync::remotes::{
5+
proxy_auto, tags::tags_missing_remote, Callbacks,
6+
},
47
};
5-
use git2::BranchType;
8+
use git2::{BranchType, Direction, Oid};
69
pub use git2_hooks::{PrePushRef, PrepareCommitMsgSource};
710
use scopetime::scope_time;
11+
use std::collections::HashMap;
812

913
///
1014
#[derive(Debug, PartialEq, Eq)]
@@ -33,36 +37,32 @@ impl From<git2_hooks::HookResult> for HookResult {
3337
}
3438
}
3539

36-
fn pre_push_branch_update(
37-
repo: &git2::Repository,
40+
/// Retrieve advertised refs from the remote for the upcoming push.
41+
pub fn advertised_remote_refs(
42+
repo_path: &RepoPath,
3843
remote: Option<&str>,
39-
branch_name: &str,
40-
remote_branch_name: Option<&str>,
41-
delete: bool,
42-
) -> PrePushRef {
43-
let local_ref = format!("refs/heads/{branch_name}");
44-
let local_oid = (!delete)
45-
.then(|| {
46-
repo.find_branch(branch_name, BranchType::Local)
47-
.ok()
48-
.and_then(|branch| branch.get().peel_to_commit().ok())
49-
.map(|commit| commit.id())
50-
})
51-
.flatten();
52-
53-
let remote_branch = remote_branch_name.unwrap_or(branch_name);
54-
let remote_ref = format!("refs/heads/{remote_branch}");
55-
56-
let remote_oid = remote.and_then(|remote_name| {
57-
repo.find_reference(&format!(
58-
"refs/remotes/{remote_name}/{remote_branch}"
59-
))
60-
.ok()
61-
.and_then(|r| r.peel_to_commit().ok())
62-
.map(|c| c.id())
63-
});
44+
url: &str,
45+
) -> Result<HashMap<String, Oid>> {
46+
let repo = repo(repo_path)?;
47+
let mut remote_handle = if let Some(name) = remote {
48+
repo.find_remote(name)?
49+
} else {
50+
repo.remote_anonymous(url)?
51+
};
52+
53+
let callbacks = Callbacks::new(None, None);
54+
let conn = remote_handle.connect_auth(
55+
Direction::Push,
56+
Some(callbacks.callbacks()),
57+
Some(proxy_auto()),
58+
)?;
59+
60+
let mut map = HashMap::new();
61+
for head in conn.list()? {
62+
map.insert(head.name().to_string(), head.oid());
63+
}
6464

65-
PrePushRef::new(local_ref, local_oid, remote_ref, remote_oid)
65+
Ok(map)
6666
}
6767

6868
/// see `git2_hooks::hooks_commit_msg`
@@ -116,27 +116,45 @@ pub fn hooks_pre_push(
116116
repo_path: &RepoPath,
117117
remote: Option<&str>,
118118
url: &str,
119-
updates: &[PrePushRef],
119+
push: &PrePushTarget<'_>,
120120
) -> Result<HookResult> {
121121
scope_time!("hooks_pre_push");
122122

123123
let repo = repo(repo_path)?;
124+
let advertised = advertised_remote_refs(repo_path, remote, url)?;
125+
let updates = match push {
126+
PrePushTarget::Branch {
127+
branch,
128+
remote_branch,
129+
delete,
130+
} => vec![pre_push_branch_update(
131+
repo_path,
132+
branch,
133+
*remote_branch,
134+
*delete,
135+
&advertised,
136+
)?],
137+
PrePushTarget::Tags => {
138+
// If remote is None, use url per git spec
139+
let remote = remote.unwrap_or(url);
140+
pre_push_tag_updates(repo_path, remote, &advertised)?
141+
}
142+
};
124143

125-
Ok(
126-
git2_hooks::hooks_pre_push(
127-
&repo, None, remote, url, updates,
128-
)?
129-
.into(),
130-
)
144+
Ok(git2_hooks::hooks_pre_push(
145+
&repo, None, remote, url, &updates,
146+
)?
147+
.into())
131148
}
132149

133150
/// Build a single pre-push update line for a branch.
134-
pub fn pre_push_branch_update(
151+
#[allow(clippy::implicit_hasher)]
152+
fn pre_push_branch_update(
135153
repo_path: &RepoPath,
136-
remote: Option<&str>,
137154
branch_name: &str,
138155
remote_branch_name: Option<&str>,
139156
delete: bool,
157+
advertised: &HashMap<String, Oid>,
140158
) -> Result<PrePushRef> {
141159
let repo = repo(repo_path)?;
142160
let local_ref = format!("refs/heads/{branch_name}");
@@ -152,24 +170,19 @@ pub fn pre_push_branch_update(
152170
let remote_branch = remote_branch_name.unwrap_or(branch_name);
153171
let remote_ref = format!("refs/heads/{remote_branch}");
154172

155-
let remote_oid = remote.and_then(|remote_name| {
156-
repo.find_reference(&format!(
157-
"refs/remotes/{remote_name}/{remote_branch}"
158-
))
159-
.ok()
160-
.and_then(|r| r.peel_to_commit().ok())
161-
.map(|c| c.id())
162-
});
173+
let remote_oid = advertised.get(&remote_ref).copied();
163174

164175
Ok(PrePushRef::new(
165176
local_ref, local_oid, remote_ref, remote_oid,
166177
))
167178
}
168179

169180
/// Build pre-push updates for tags that are missing on the remote.
170-
pub fn pre_push_tag_updates(
181+
#[allow(clippy::implicit_hasher)]
182+
fn pre_push_tag_updates(
171183
repo_path: &RepoPath,
172184
remote: &str,
185+
advertised: &HashMap<String, Oid>,
173186
) -> Result<Vec<PrePushRef>> {
174187
let repo = repo(repo_path)?;
175188
let tags = tags_missing_remote(repo_path, remote, None)?;
@@ -180,19 +193,35 @@ pub fn pre_push_tag_updates(
180193
let tag_oid = reference.target().or_else(|| {
181194
reference.peel_to_commit().ok().map(|c| c.id())
182195
});
183-
196+
let remote_ref = tag_ref.clone();
197+
let advertised_oid = advertised.get(&remote_ref).copied();
184198
updates.push(PrePushRef::new(
185199
tag_ref.clone(),
186200
tag_oid,
187-
tag_ref,
188-
None,
201+
remote_ref,
202+
advertised_oid,
189203
));
190204
}
191205
}
192206

193207
Ok(updates)
194208
}
195209

210+
/// What is being pushed.
211+
pub enum PrePushTarget<'a> {
212+
/// Push a single branch.
213+
Branch {
214+
/// Local branch name being pushed.
215+
branch: &'a str,
216+
/// Optional remote branch name if different from local.
217+
remote_branch: Option<&'a str>,
218+
/// Whether this is a delete push.
219+
delete: bool,
220+
},
221+
/// Push tags.
222+
Tags,
223+
}
224+
196225
#[cfg(test)]
197226
mod tests {
198227
use std::{ffi::OsString, io::Write as _, path::Path};

asyncgit/src/sync/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,9 @@ pub use config::{
6666
pub use diff::get_diff_commit;
6767
pub use git2::BranchType;
6868
pub use hooks::{
69-
hooks_commit_msg, hooks_post_commit, hooks_pre_commit,
70-
hooks_pre_push, hooks_prepare_commit_msg, pre_push_branch_update,
71-
pre_push_tag_updates, HookResult, PrePushRef,
72-
PrepareCommitMsgSource,
69+
advertised_remote_refs, hooks_commit_msg, hooks_post_commit,
70+
hooks_pre_commit, hooks_pre_push, hooks_prepare_commit_msg,
71+
HookResult, PrePushRef, PrePushTarget, PrepareCommitMsgSource,
7372
};
7473
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
7574
pub use ignore::add_to_ignore;

src/popups/push.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,22 +161,17 @@ impl PushPopup {
161161
return Ok(());
162162
};
163163

164-
// build pre-push updates (single branch)
165-
let repo = self.repo.borrow();
166-
let branch_update = asyncgit::sync::pre_push_branch_update(
167-
&repo,
168-
Some(&remote),
169-
&self.branch,
170-
None,
171-
self.modifier.delete(),
172-
)?;
173-
174164
// run pre push hook - can reject push
165+
let repo = self.repo.borrow();
175166
if let HookResult::NotOk(e) = hooks_pre_push(
176167
&repo,
177168
Some(&remote),
178169
&remote_url,
179-
&[branch_update],
170+
&asyncgit::sync::PrePushTarget::Branch {
171+
branch: &self.branch,
172+
remote_branch: None,
173+
delete: self.modifier.delete(),
174+
},
180175
)? {
181176
log::error!("pre-push hook failed: {e}");
182177
self.queue.push(InternalEvent::ShowErrorMsg(format!(

src/popups/push_tags.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,13 @@ impl PushTagsPopup {
103103
return Ok(());
104104
};
105105

106-
// build updates for tags we are going to push
107-
let repo = self.repo.borrow();
108-
let updates =
109-
asyncgit::sync::pre_push_tag_updates(&repo, &remote)?;
110-
111106
// run pre push hook - can reject push
107+
let repo = self.repo.borrow();
112108
if let HookResult::NotOk(e) = hooks_pre_push(
113109
&repo,
114110
Some(&remote),
115111
&remote_url,
116-
&updates,
112+
&asyncgit::sync::PrePushTarget::Tags,
117113
)? {
118114
log::error!("pre-push hook failed: {e}");
119115
self.queue.push(InternalEvent::ShowErrorMsg(format!(

0 commit comments

Comments
 (0)