1
1
pub ( super ) mod function {
2
2
use anyhow:: { bail, Context } ;
3
3
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 } ;
6
8
use std:: process:: { Command , Stdio } ;
7
9
8
10
pub fn check_mode ( ) -> anyhow:: Result < ( ) > {
9
11
let root = find_root ( ) ?;
10
- let mut mismatch = false ;
12
+ let mut any_mismatch = false ;
11
13
12
- let cmd = Command :: new ( "git" )
13
- . arg ( "-C" )
14
- . arg ( root)
14
+ let mut child = git_on ( & root)
15
15
. args ( [ "ls-files" , "-sz" , "--" , "*.sh" ] )
16
16
. stdout ( Stdio :: piped ( ) )
17
17
. spawn ( )
18
- . context ( "Can't run `git` to list index" ) ?;
18
+ . context ( "Can't start `git` subprocess to list index" ) ?;
19
19
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
+ }
24
26
}
25
27
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 ( ( ) )
29
36
}
30
37
38
+ /// Find the top-level directory of the current repository working tree.
31
39
fn find_root ( ) -> anyhow:: Result < OsString > {
32
40
let output = Command :: new ( "git" )
33
41
. args ( [ "rev-parse" , "--show-toplevel" ] )
@@ -47,4 +55,66 @@ pub(super) mod function {
47
55
48
56
Ok ( root)
49
57
}
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
+ }
50
120
}
0 commit comments