11pub ( super ) mod function {
22 use anyhow:: { bail, Context } ;
33 use gix:: bstr:: ByteSlice ;
4- use std:: ffi:: OsString ;
5- use std:: io:: { BufRead , BufReader } ;
4+ use once_cell:: sync:: Lazy ;
5+ use regex:: bytes:: Regex ;
6+ use std:: ffi:: { OsStr , OsString } ;
7+ use std:: io:: { BufRead , BufReader , Read } ;
68 use std:: process:: { Command , Stdio } ;
79
810 pub fn check_mode ( ) -> anyhow:: Result < ( ) > {
911 let root = find_root ( ) ?;
10- let mut mismatch = false ;
12+ let mut any_mismatch = false ;
1113
12- let cmd = Command :: new ( "git" )
13- . arg ( "-C" )
14- . arg ( root)
14+ let mut child = git_on ( & root)
1515 . args ( [ "ls-files" , "-sz" , "--" , "*.sh" ] )
1616 . stdout ( Stdio :: piped ( ) )
1717 . spawn ( )
18- . context ( "Can't run `git` to list index" ) ?;
18+ . context ( "Can't start `git` subprocess to list index" ) ?;
1919
20- let stdout = cmd. stdout . expect ( "should have captured stdout" ) ;
21- let reader = BufReader :: new ( stdout) ;
22- for record in reader. split ( b'\0' ) {
23- // FIXME: Use the record, displaying messages and updating `mismatch`.
20+ let stdout = child. stdout . take ( ) . expect ( "should have captured stdout" ) ;
21+ for result in BufReader :: new ( stdout) . split ( b'\0' ) {
22+ let record = result. context ( r"Can't read '\0'-terminated record" ) ?;
23+ if check_for_mismatch ( & root, & record) ? {
24+ any_mismatch = true ;
25+ }
2426 }
2527
26- // FIXME: If `cmd` did not report successful completion, bail.
27- // FIXME: If `mismatch` (any mismatches), bail.
28- bail ! ( "not yet implemented" ) ;
28+ let status = child. wait ( ) . context ( "Failure running `git` subprocess to list index" ) ?;
29+ if !status. success ( ) {
30+ bail ! ( "`git` subprocess to list index did not complete successfully" ) ;
31+ }
32+ if any_mismatch {
33+ bail ! ( "Mismatch found - scan completed, finding at least one `#!` vs. `+x` mismatch" ) ;
34+ }
35+ Ok ( ( ) )
2936 }
3037
38+ /// Find the top-level directory of the current repository working tree.
3139 fn find_root ( ) -> anyhow:: Result < OsString > {
3240 let output = Command :: new ( "git" )
3341 . args ( [ "rev-parse" , "--show-toplevel" ] )
@@ -47,4 +55,66 @@ pub(super) mod function {
4755
4856 Ok ( root)
4957 }
58+
59+ /// Prepare a `git` command, passing `root` as an operand to `-C`.
60+ ///
61+ /// This is suitable when `git` gave us the path `root`. Then it should already be in a form
62+ /// where `git -C` will be able to use it, without alteration, regardless of the platform.
63+ /// (Otherwise, it may be preferable to set `root` as the `cwd` of the `git` process instead.)
64+ fn git_on ( root : & OsStr ) -> Command {
65+ let mut cmd = Command :: new ( "git" ) ;
66+ cmd. arg ( "-C" ) . arg ( root) ;
67+ cmd
68+ }
69+
70+ static RECORD_REGEX : Lazy < Regex > = Lazy :: new ( || {
71+ let pattern = r"(?-u)\A([0-7]+) ([[:xdigit:]]+) [[:digit:]]+\t(.+)\z" ;
72+ Regex :: new ( pattern) . expect ( "regex should be valid" )
73+ } ) ;
74+
75+ /// On mismatch, report it and return `Some(true)`.
76+ fn check_for_mismatch ( root : & OsStr , record : & [ u8 ] ) -> anyhow:: Result < bool > {
77+ let fields = RECORD_REGEX . captures ( record) . context ( "Malformed record from `git`" ) ?;
78+ let mode = fields. get ( 1 ) . expect ( "match should get mode" ) . as_bytes ( ) ;
79+ let oid = fields
80+ . get ( 2 )
81+ . expect ( "match should get oid" )
82+ . as_bytes ( )
83+ . to_os_str ( )
84+ . expect ( "oid field verified as hex digits, should be valid OsStr" ) ;
85+ let path = fields. get ( 3 ) . expect ( "match should get path" ) . as_bytes ( ) . as_bstr ( ) ;
86+
87+ match mode {
88+ b"100644" if blob_has_shebang ( root, oid) ? => {
89+ println ! ( "mode -x but has shebang: {}\n " , path) ;
90+ Ok ( true )
91+ }
92+ b"100755" if !blob_has_shebang ( root, oid) ? => {
93+ println ! ( "mode +x but no shebang: {}\n " , path) ;
94+ Ok ( true )
95+ }
96+ _ => Ok ( false ) ,
97+ }
98+ }
99+
100+ fn blob_has_shebang ( root : & OsStr , oid : & OsStr ) -> anyhow:: Result < bool > {
101+ let mut buf = [ 0u8 ; 2 ] ;
102+
103+ let mut child = git_on ( root)
104+ . args ( [ "cat-file" , "blob" ] )
105+ . arg ( oid)
106+ . stdout ( Stdio :: piped ( ) )
107+ . spawn ( )
108+ . context ( "Can't start `git` subprocess to read blob" ) ?;
109+
110+ let mut stdout = child. stdout . take ( ) . expect ( "should have captured stdout" ) ;
111+ let count = stdout. read ( & mut buf) . context ( "Error reading data from blob" ) ?;
112+ drop ( stdout) ; // Let the pipe break rather than waiting for the rest of the blob.
113+
114+ // TODO: Maybe check status? On Unix, it should be 0 or SIGPIPE. Not sure about Windows.
115+ _ = child. wait ( ) . context ( "Failure running `git` subprocess to read blob" ) ?;
116+
117+ let magic = & buf[ ..count] ;
118+ Ok ( magic == b"#!" )
119+ }
50120}
0 commit comments