@@ -22,9 +22,10 @@ use josh::{josh_error, JoshError, JoshResult};
2222use josh_rpc:: calls:: RequestedCommand ;
2323use serde:: Serialize ;
2424use std:: collections:: HashMap ;
25+ use std:: ffi:: OsStr ;
2526use std:: io;
2627use std:: net:: IpAddr ;
27- use std:: path:: PathBuf ;
28+ use std:: path:: { Path , PathBuf } ;
2829use std:: process:: Stdio ;
2930use std:: str:: FromStr ;
3031use std:: sync:: { Arc , RwLock } ;
@@ -729,14 +730,15 @@ async fn serve_namespace(
729730 params : & josh_rpc:: calls:: ServeNamespace ,
730731 repo_path : std:: path:: PathBuf ,
731732 namespace : & str ,
733+ repo_update : RepoUpdate ,
732734) -> josh:: JoshResult < ( ) > {
733735 const SERVE_TIMEOUT : u64 = 60 ;
734736
735737 tracing:: trace!(
736- "serve_namespace: command: {:?}, query: {}, namespace: {}" ,
737- params. command ,
738- params . query ,
739- namespace
738+ command = ?params . command ,
739+ query = % params. query ,
740+ namespace = %namespace ,
741+ "serve_namespace" ,
740742 ) ;
741743
742744 enum ServeError {
@@ -746,25 +748,24 @@ async fn serve_namespace(
746748 SubprocessExited ( i32 ) ,
747749 }
748750
749- if params. command == RequestedCommand :: GitReceivePack {
750- return Err ( josh_error ( "Push over SSH is not supported" ) ) ;
751- }
752-
753751 let command = match params. command {
754752 RequestedCommand :: GitUploadPack => "git-upload-pack" ,
755753 RequestedCommand :: GitUploadArchive => "git-upload-archive" ,
756754 RequestedCommand :: GitReceivePack => "git-receive-pack" ,
757755 } ;
758756
757+ let overlay_path = repo_path. join ( "overlay" ) ;
758+
759759 let mut process = tokio:: process:: Command :: new ( command)
760- . arg ( repo_path . join ( "overlay" ) )
761- . current_dir ( repo_path . join ( "overlay" ) )
760+ . arg ( & overlay_path )
761+ . current_dir ( & overlay_path )
762762 . env ( "GIT_DIR" , & repo_path)
763763 . env ( "GIT_NAMESPACE" , namespace)
764764 . env (
765765 "GIT_ALTERNATE_OBJECT_DIRECTORIES" ,
766766 repo_path. join ( "mirror" ) . join ( "objects" ) ,
767767 )
768+ . env ( "JOSH_REPO_UPDATE" , serde_json:: to_string ( & repo_update) ?)
768769 . stdin ( Stdio :: piped ( ) )
769770 . stdout ( Stdio :: piped ( ) )
770771 . spawn ( ) ?;
@@ -912,6 +913,31 @@ fn head_ref_or_default(head_ref: &str) -> HeadRef {
912913 }
913914}
914915
916+ fn make_repo_update (
917+ remote_url : & str ,
918+ serv : Arc < JoshProxyService > ,
919+ filter : josh:: filter:: Filter ,
920+ remote_auth : RemoteAuth ,
921+ meta : & MetaConfig ,
922+ repo_path : & Path ,
923+ ns : Arc < josh_proxy:: TmpGitNamespace > ,
924+ ) -> RepoUpdate {
925+ let context_propagator = josh_proxy:: trace:: make_context_propagator ( ) ;
926+
927+ RepoUpdate {
928+ refs : HashMap :: new ( ) ,
929+ remote_url : remote_url. to_string ( ) ,
930+ remote_auth,
931+ port : serv. port . clone ( ) ,
932+ filter_spec : josh:: filter:: spec ( filter) ,
933+ base_ns : josh:: to_ns ( & meta. config . repo ) ,
934+ git_ns : ns. name ( ) . to_string ( ) ,
935+ git_dir : repo_path. display ( ) . to_string ( ) ,
936+ mirror_git_dir : serv. repo_path . join ( "mirror" ) . display ( ) . to_string ( ) ,
937+ context_propagator,
938+ }
939+ }
940+
915941async fn handle_serve_namespace_request (
916942 serv : Arc < JoshProxyService > ,
917943 req : Request < hyper:: Body > ,
@@ -958,6 +984,9 @@ async fn handle_serve_namespace_request(
958984 ) ) ;
959985 } ;
960986
987+ eprintln ! ( "params: {:?}" , params) ;
988+ eprintln ! ( "parsed_url.upstream_repo: {:?}" , parsed_url. upstream_repo) ;
989+
961990 let auth_socket = params. ssh_socket . clone ( ) ;
962991 let remote_auth = RemoteAuth :: Ssh {
963992 auth_socket : auth_socket. clone ( ) ,
@@ -1000,24 +1029,34 @@ async fn handle_serve_namespace_request(
10001029 let remote_url = upstream + meta_config. config . repo . as_str ( ) ;
10011030 let head_ref = head_ref_or_default ( & parsed_url. headref ) ;
10021031
1003- let remote_refs = [ head_ref. get ( ) ] ;
1004- let remote_refs = match ssh_list_refs ( & remote_url, auth_socket, Some ( & remote_refs) ) . await {
1005- Ok ( remote_refs) => remote_refs,
1006- Err ( e) => {
1007- return Ok ( make_response (
1008- hyper:: Body :: from ( e. to_string ( ) ) ,
1009- hyper:: StatusCode :: FORBIDDEN ,
1010- ) )
1011- }
1012- } ;
1032+ let resolved_ref = match params. command {
1033+ // When pushing over SSH, we need to fetch to get new references
1034+ // for searching for unapply base, so we don't bother with additional cache checks
1035+ RequestedCommand :: GitReceivePack => None ,
1036+ // Otherwise, list refs - it doesn't need locking and is faster -
1037+ // and use results to potentially skip fetching
1038+ _ => {
1039+ let remote_refs = [ head_ref. get ( ) ] ;
1040+ let remote_refs =
1041+ match ssh_list_refs ( & remote_url, auth_socket, Some ( & remote_refs) ) . await {
1042+ Ok ( remote_refs) => remote_refs,
1043+ Err ( e) => {
1044+ return Ok ( make_response (
1045+ hyper:: Body :: from ( e. to_string ( ) ) ,
1046+ hyper:: StatusCode :: FORBIDDEN ,
1047+ ) )
1048+ }
1049+ } ;
10131050
1014- let resolved_ref = match remote_refs. get ( head_ref. get ( ) ) {
1015- Some ( resolved_ref) => resolved_ref,
1016- None => {
1017- return Ok ( make_response (
1018- hyper:: Body :: from ( "Could not resolve remote ref" ) ,
1019- hyper:: StatusCode :: INTERNAL_SERVER_ERROR ,
1020- ) )
1051+ match remote_refs. get ( head_ref. get ( ) ) {
1052+ Some ( resolved_ref) => Some ( resolved_ref. clone ( ) ) ,
1053+ None => {
1054+ return Ok ( make_response (
1055+ hyper:: Body :: from ( "Could not resolve remote ref" ) ,
1056+ hyper:: StatusCode :: INTERNAL_SERVER_ERROR ,
1057+ ) )
1058+ }
1059+ }
10211060 }
10221061 } ;
10231062
@@ -1027,7 +1066,7 @@ async fn handle_serve_namespace_request(
10271066 & remote_auth,
10281067 remote_url. to_owned ( ) ,
10291068 Some ( head_ref. get ( ) ) ,
1030- Some ( resolved_ref) ,
1069+ resolved_ref. as_deref ( ) ,
10311070 false ,
10321071 )
10331072 . await
@@ -1095,7 +1134,19 @@ async fn handle_serve_namespace_request(
10951134 }
10961135 } ;
10971136
1098- let serve_result = serve_namespace ( & params, serv. repo_path . clone ( ) , temp_ns. name ( ) ) . await ;
1137+ let overlay_path = serv. repo_path . join ( "overlay" ) ;
1138+ let repo_update = make_repo_update (
1139+ & remote_url,
1140+ serv. clone ( ) ,
1141+ filter,
1142+ remote_auth,
1143+ & meta_config,
1144+ & overlay_path,
1145+ temp_ns. clone ( ) ,
1146+ ) ;
1147+
1148+ let serve_result =
1149+ serve_namespace ( & params, serv. repo_path . clone ( ) , temp_ns. name ( ) , repo_update) . await ;
10991150 std:: mem:: drop ( temp_ns) ;
11001151
11011152 match serve_result {
@@ -1295,68 +1346,39 @@ async fn call_service(
12951346 }
12961347
12971348 let temp_ns = prepare_namespace ( serv. clone ( ) , & meta, filter, & headref) . await ?;
1349+ let overlay_path = serv. repo_path . join ( "overlay" ) ;
12981350
1299- let repo_path = serv
1300- . repo_path
1301- . join ( "overlay" )
1302- . to_str ( )
1303- . ok_or ( josh:: josh_error ( "repo_path.to_str" ) ) ?
1304- . to_string ( ) ;
1305-
1306- let mirror_repo_path = serv
1307- . repo_path
1308- . join ( "mirror" )
1309- . to_str ( )
1310- . ok_or ( josh:: josh_error ( "repo_path.to_str" ) ) ?
1311- . to_string ( ) ;
1312-
1313- let context_propagator = {
1314- let span = tracing:: Span :: current ( ) ;
1315-
1316- let mut context_propagator = HashMap :: < String , String > :: default ( ) ;
1317- let context = span. context ( ) ;
1318- global:: get_text_map_propagator ( |propagator| {
1319- propagator. inject_context ( & context, & mut context_propagator) ;
1320- } ) ;
1321-
1322- tracing:: debug!( "context propagator: {:?}" , context_propagator) ;
1323- context_propagator
1324- } ;
1325-
1326- let repo_update = josh_proxy:: RepoUpdate {
1327- refs : HashMap :: new ( ) ,
1328- remote_url : remote_url. clone ( ) ,
1351+ let repo_update = make_repo_update (
1352+ & remote_url,
1353+ serv. clone ( ) ,
1354+ filter,
13291355 remote_auth,
1330- port : serv. port . clone ( ) ,
1331- filter_spec : josh:: filter:: spec ( filter) ,
1332- base_ns : josh:: to_ns ( & meta. config . repo ) ,
1333- git_ns : temp_ns. name ( ) . to_string ( ) ,
1334- git_dir : repo_path. clone ( ) ,
1335- mirror_git_dir : mirror_repo_path. clone ( ) ,
1336- context_propagator,
1337- } ;
1356+ & meta,
1357+ & overlay_path,
1358+ temp_ns. clone ( ) ,
1359+ ) ;
13381360
13391361 let cgi_response = async {
13401362 let mut cmd = Command :: new ( "git" ) ;
13411363 cmd. arg ( "http-backend" ) ;
1342- cmd. current_dir ( & serv . repo_path . join ( "overlay" ) ) ;
1343- cmd. env ( "GIT_DIR" , & repo_path ) ;
1364+ cmd. current_dir ( & overlay_path ) ;
1365+ cmd. env ( "GIT_DIR" , & overlay_path ) ;
13441366 cmd. env ( "GIT_HTTP_EXPORT_ALL" , "" ) ;
13451367 cmd. env (
13461368 "GIT_ALTERNATE_OBJECT_DIRECTORIES" ,
13471369 serv. repo_path
13481370 . join ( "mirror" )
13491371 . join ( "objects" )
1350- . to_str ( )
1351- . ok_or ( josh :: josh_error ( "repo_path.to_str" ) ) ? ,
1372+ . display ( )
1373+ . to_string ( ) ,
13521374 ) ;
13531375 cmd. env ( "GIT_NAMESPACE" , temp_ns. name ( ) ) ;
1354- cmd. env ( "GIT_PROJECT_ROOT" , repo_path ) ;
1376+ cmd. env ( "GIT_PROJECT_ROOT" , & overlay_path ) ;
13551377 cmd. env ( "JOSH_REPO_UPDATE" , serde_json:: to_string ( & repo_update) ?) ;
13561378 cmd. env ( "PATH_INFO" , parsed_url. pathinfo . clone ( ) ) ;
13571379
13581380 let ( response, stderr) = hyper_cgi:: do_cgi ( req, cmd) . await ;
1359- tracing:: debug!( "Git stderr: {}" , String :: from_utf8_lossy( & stderr) ) ;
1381+ tracing:: debug!( stderr = % String :: from_utf8_lossy( & stderr) , "http-backend exited" ) ;
13601382
13611383 Ok :: < _ , JoshError > ( response)
13621384 }
@@ -1655,35 +1677,41 @@ async fn run_housekeeping(local: std::path::PathBuf) -> josh::JoshResult<()> {
16551677 }
16561678}
16571679
1680+ fn repo_update_from_env ( ) -> josh:: JoshResult < josh_proxy:: RepoUpdate > {
1681+ let repo_update =
1682+ std:: env:: var ( "JOSH_REPO_UPDATE" ) . map_err ( |_| josh_error ( "JOSH_REPO_UPDATE not set" ) ) ?;
1683+
1684+ serde_json:: from_str ( & repo_update)
1685+ . map_err ( |e| josh_error ( & format ! ( "Failed to parse JOSH_REPO_UPDATE: {}" , e) ) )
1686+ }
1687+
16581688fn pre_receive_hook ( ) -> josh:: JoshResult < i32 > {
1659- let repo_update: josh_proxy:: RepoUpdate =
1660- serde_json:: from_str ( & std:: env:: var ( "JOSH_REPO_UPDATE" ) ?) ?;
1689+ let repo_update = repo_update_from_env ( ) ?;
16611690
1662- let p = std:: path:: PathBuf :: from ( repo_update. git_dir )
1691+ let push_options_path = std:: path:: PathBuf :: from ( repo_update. git_dir )
16631692 . join ( "refs/namespaces" )
16641693 . join ( repo_update. git_ns )
16651694 . join ( "push_options" ) ;
16661695
1667- let n : usize = std:: env:: var ( "GIT_PUSH_OPTION_COUNT" ) ?. parse ( ) ?;
1696+ let push_option_count : usize = std:: env:: var ( "GIT_PUSH_OPTION_COUNT" ) ?. parse ( ) ?;
16681697
1669- let mut push_options = std :: collections :: HashMap :: < String , String > :: new ( ) ;
1670- for i in 0 ..n {
1671- let s = std:: env:: var ( format ! ( "GIT_PUSH_OPTION_{}" , i) ) ?;
1672- if let [ key, value] = s . as_str ( ) . split ( '=' ) . collect :: < Vec < _ > > ( ) . as_slice ( ) {
1673- push_options. insert ( key. to_string ( ) , value. to_string ( ) ) ;
1698+ let mut push_options = HashMap :: < String , serde_json :: Value > :: new ( ) ;
1699+ for i in 0 ..push_option_count {
1700+ let push_option = std:: env:: var ( format ! ( "GIT_PUSH_OPTION_{}" , i) ) ?;
1701+ if let Some ( ( key, value) ) = push_option . split_once ( "=" ) {
1702+ push_options. insert ( key. into ( ) , value. into ( ) ) ;
16741703 } else {
1675- push_options. insert ( s , "" . to_string ( ) ) ;
1704+ push_options. insert ( push_option , true . into ( ) ) ;
16761705 }
16771706 }
16781707
1679- std:: fs:: write ( p , serde_json:: to_string ( & push_options) ?) ?;
1708+ std:: fs:: write ( push_options_path , serde_json:: to_string ( & push_options) ?) ?;
16801709
16811710 Ok ( 0 )
16821711}
16831712
16841713fn update_hook ( refname : & str , old : & str , new : & str ) -> josh:: JoshResult < i32 > {
1685- let mut repo_update: josh_proxy:: RepoUpdate =
1686- serde_json:: from_str ( & std:: env:: var ( "JOSH_REPO_UPDATE" ) ?) ?;
1714+ let mut repo_update = repo_update_from_env ( ) ?;
16871715
16881716 repo_update
16891717 . refs
@@ -1696,24 +1724,33 @@ fn update_hook(refname: &str, old: &str, new: &str) -> josh::JoshResult<i32> {
16961724 . send ( ) ;
16971725
16981726 match resp {
1699- Ok ( r) => {
1700- let success = r. status ( ) . is_success ( ) ;
1701- if let Ok ( body) = r. text ( ) {
1702- println ! ( "response from upstream:\n {}\n \n " , body) ;
1703- } else {
1704- println ! ( "no upstream response" ) ;
1727+ Ok ( resp) => {
1728+ let success = resp. status ( ) . is_success ( ) ;
1729+ println ! ( "upstream: response status: {}" , resp. status( ) ) ;
1730+
1731+ match resp. text ( ) {
1732+ Ok ( text) if text. trim ( ) . is_empty ( ) => {
1733+ println ! ( "upstream: no response body" ) ;
1734+ }
1735+ Ok ( text) => {
1736+ println ! ( "upstream: response body:\n \n {}" , text) ;
1737+ }
1738+ Err ( err) => {
1739+ println ! ( "upstream: warn: failed to read response body: {:?}" , err) ;
1740+ }
17051741 }
1742+
17061743 if success {
1707- return Ok ( 0 ) ;
1744+ Ok ( 0 )
17081745 } else {
1709- return Ok ( 1 ) ;
1746+ Ok ( 1 )
17101747 }
17111748 }
17121749 Err ( err) => {
17131750 tracing:: warn!( "/repo_update request failed {:?}" , err) ;
1751+ Ok ( 1 )
17141752 }
1715- } ;
1716- Ok ( 1 )
1753+ }
17171754}
17181755
17191756async fn serve_graphql (
@@ -1949,8 +1986,16 @@ fn main() {
19491986
19501987 if let [ a0, ..] = & std:: env:: args ( ) . collect :: < Vec < _ > > ( ) . as_slice ( ) {
19511988 if a0. ends_with ( "/pre-receive" ) {
1952- println ! ( "josh-proxy" ) ;
1953- std:: process:: exit ( pre_receive_hook ( ) . unwrap_or ( 1 ) ) ;
1989+ eprintln ! ( "josh-proxy: pre-receive hook" ) ;
1990+ let code = match pre_receive_hook ( ) {
1991+ Ok ( code) => code,
1992+ Err ( e) => {
1993+ eprintln ! ( "josh-proxy: pre-receive hook failed: {}" , e) ;
1994+ std:: process:: exit ( 1 ) ;
1995+ }
1996+ } ;
1997+
1998+ std:: process:: exit ( code) ;
19541999 }
19552000 }
19562001
0 commit comments