11use super :: { repository:: repo, RepoPath } ;
22use 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 } ;
69pub use git2_hooks:: { PrePushRef , PrepareCommitMsgSource } ;
710use 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) ]
197226mod tests {
198227 use std:: { ffi:: OsString , io:: Write as _, path:: Path } ;
0 commit comments