@@ -22,9 +22,10 @@ use josh::{josh_error, JoshError, JoshResult};
22
22
use josh_rpc:: calls:: RequestedCommand ;
23
23
use serde:: Serialize ;
24
24
use std:: collections:: HashMap ;
25
+ use std:: ffi:: OsStr ;
25
26
use std:: io;
26
27
use std:: net:: IpAddr ;
27
- use std:: path:: PathBuf ;
28
+ use std:: path:: { Path , PathBuf } ;
28
29
use std:: process:: Stdio ;
29
30
use std:: str:: FromStr ;
30
31
use std:: sync:: { Arc , RwLock } ;
@@ -729,14 +730,15 @@ async fn serve_namespace(
729
730
params : & josh_rpc:: calls:: ServeNamespace ,
730
731
repo_path : std:: path:: PathBuf ,
731
732
namespace : & str ,
733
+ repo_update : RepoUpdate ,
732
734
) -> josh:: JoshResult < ( ) > {
733
735
const SERVE_TIMEOUT : u64 = 60 ;
734
736
735
737
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" ,
740
742
) ;
741
743
742
744
enum ServeError {
@@ -746,25 +748,24 @@ async fn serve_namespace(
746
748
SubprocessExited ( i32 ) ,
747
749
}
748
750
749
- if params. command == RequestedCommand :: GitReceivePack {
750
- return Err ( josh_error ( "Push over SSH is not supported" ) ) ;
751
- }
752
-
753
751
let command = match params. command {
754
752
RequestedCommand :: GitUploadPack => "git-upload-pack" ,
755
753
RequestedCommand :: GitUploadArchive => "git-upload-archive" ,
756
754
RequestedCommand :: GitReceivePack => "git-receive-pack" ,
757
755
} ;
758
756
757
+ let overlay_path = repo_path. join ( "overlay" ) ;
758
+
759
759
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 )
762
762
. env ( "GIT_DIR" , & repo_path)
763
763
. env ( "GIT_NAMESPACE" , namespace)
764
764
. env (
765
765
"GIT_ALTERNATE_OBJECT_DIRECTORIES" ,
766
766
repo_path. join ( "mirror" ) . join ( "objects" ) ,
767
767
)
768
+ . env ( "JOSH_REPO_UPDATE" , serde_json:: to_string ( & repo_update) ?)
768
769
. stdin ( Stdio :: piped ( ) )
769
770
. stdout ( Stdio :: piped ( ) )
770
771
. spawn ( ) ?;
@@ -912,6 +913,31 @@ fn head_ref_or_default(head_ref: &str) -> HeadRef {
912
913
}
913
914
}
914
915
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
+
915
941
async fn handle_serve_namespace_request (
916
942
serv : Arc < JoshProxyService > ,
917
943
req : Request < hyper:: Body > ,
@@ -958,6 +984,9 @@ async fn handle_serve_namespace_request(
958
984
) ) ;
959
985
} ;
960
986
987
+ eprintln ! ( "params: {:?}" , params) ;
988
+ eprintln ! ( "parsed_url.upstream_repo: {:?}" , parsed_url. upstream_repo) ;
989
+
961
990
let auth_socket = params. ssh_socket . clone ( ) ;
962
991
let remote_auth = RemoteAuth :: Ssh {
963
992
auth_socket : auth_socket. clone ( ) ,
@@ -1000,24 +1029,34 @@ async fn handle_serve_namespace_request(
1000
1029
let remote_url = upstream + meta_config. config . repo . as_str ( ) ;
1001
1030
let head_ref = head_ref_or_default ( & parsed_url. headref ) ;
1002
1031
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
+ } ;
1013
1050
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
+ }
1021
1060
}
1022
1061
} ;
1023
1062
@@ -1027,7 +1066,7 @@ async fn handle_serve_namespace_request(
1027
1066
& remote_auth,
1028
1067
remote_url. to_owned ( ) ,
1029
1068
Some ( head_ref. get ( ) ) ,
1030
- Some ( resolved_ref) ,
1069
+ resolved_ref. as_deref ( ) ,
1031
1070
false ,
1032
1071
)
1033
1072
. await
@@ -1095,7 +1134,19 @@ async fn handle_serve_namespace_request(
1095
1134
}
1096
1135
} ;
1097
1136
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 ;
1099
1150
std:: mem:: drop ( temp_ns) ;
1100
1151
1101
1152
match serve_result {
@@ -1295,68 +1346,39 @@ async fn call_service(
1295
1346
}
1296
1347
1297
1348
let temp_ns = prepare_namespace ( serv. clone ( ) , & meta, filter, & headref) . await ?;
1349
+ let overlay_path = serv. repo_path . join ( "overlay" ) ;
1298
1350
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,
1329
1355
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
+ ) ;
1338
1360
1339
1361
let cgi_response = async {
1340
1362
let mut cmd = Command :: new ( "git" ) ;
1341
1363
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 ) ;
1344
1366
cmd. env ( "GIT_HTTP_EXPORT_ALL" , "" ) ;
1345
1367
cmd. env (
1346
1368
"GIT_ALTERNATE_OBJECT_DIRECTORIES" ,
1347
1369
serv. repo_path
1348
1370
. join ( "mirror" )
1349
1371
. join ( "objects" )
1350
- . to_str ( )
1351
- . ok_or ( josh :: josh_error ( "repo_path.to_str" ) ) ? ,
1372
+ . display ( )
1373
+ . to_string ( ) ,
1352
1374
) ;
1353
1375
cmd. env ( "GIT_NAMESPACE" , temp_ns. name ( ) ) ;
1354
- cmd. env ( "GIT_PROJECT_ROOT" , repo_path ) ;
1376
+ cmd. env ( "GIT_PROJECT_ROOT" , & overlay_path ) ;
1355
1377
cmd. env ( "JOSH_REPO_UPDATE" , serde_json:: to_string ( & repo_update) ?) ;
1356
1378
cmd. env ( "PATH_INFO" , parsed_url. pathinfo . clone ( ) ) ;
1357
1379
1358
1380
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" ) ;
1360
1382
1361
1383
Ok :: < _ , JoshError > ( response)
1362
1384
}
@@ -1655,35 +1677,41 @@ async fn run_housekeeping(local: std::path::PathBuf) -> josh::JoshResult<()> {
1655
1677
}
1656
1678
}
1657
1679
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
+
1658
1688
fn 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 ( ) ?;
1661
1690
1662
- let p = std:: path:: PathBuf :: from ( repo_update. git_dir )
1691
+ let push_options_path = std:: path:: PathBuf :: from ( repo_update. git_dir )
1663
1692
. join ( "refs/namespaces" )
1664
1693
. join ( repo_update. git_ns )
1665
1694
. join ( "push_options" ) ;
1666
1695
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 ( ) ?;
1668
1697
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 ( ) ) ;
1674
1703
} else {
1675
- push_options. insert ( s , "" . to_string ( ) ) ;
1704
+ push_options. insert ( push_option , true . into ( ) ) ;
1676
1705
}
1677
1706
}
1678
1707
1679
- std:: fs:: write ( p , serde_json:: to_string ( & push_options) ?) ?;
1708
+ std:: fs:: write ( push_options_path , serde_json:: to_string ( & push_options) ?) ?;
1680
1709
1681
1710
Ok ( 0 )
1682
1711
}
1683
1712
1684
1713
fn 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 ( ) ?;
1687
1715
1688
1716
repo_update
1689
1717
. refs
@@ -1696,24 +1724,33 @@ fn update_hook(refname: &str, old: &str, new: &str) -> josh::JoshResult<i32> {
1696
1724
. send ( ) ;
1697
1725
1698
1726
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
+ }
1705
1741
}
1742
+
1706
1743
if success {
1707
- return Ok ( 0 ) ;
1744
+ Ok ( 0 )
1708
1745
} else {
1709
- return Ok ( 1 ) ;
1746
+ Ok ( 1 )
1710
1747
}
1711
1748
}
1712
1749
Err ( err) => {
1713
1750
tracing:: warn!( "/repo_update request failed {:?}" , err) ;
1751
+ Ok ( 1 )
1714
1752
}
1715
- } ;
1716
- Ok ( 1 )
1753
+ }
1717
1754
}
1718
1755
1719
1756
async fn serve_graphql (
@@ -1949,8 +1986,16 @@ fn main() {
1949
1986
1950
1987
if let [ a0, ..] = & std:: env:: args ( ) . collect :: < Vec < _ > > ( ) . as_slice ( ) {
1951
1988
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) ;
1954
1999
}
1955
2000
}
1956
2001
0 commit comments