@@ -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