@@ -722,34 +722,53 @@ it.effect("classifies an authentication failure without retaining the remote it
722722 ) . pipe ( Effect . provide ( failing . layer ) ) ;
723723} ) ;
724724
725- it . effect ( "does not read a local permission error as an authentication failure" , ( ) => {
726- // `git init` into an unwritable directory says "Permission denied" too.
727- // Matching that bare phrase would answer a filesystem problem with advice
728- // about remote credentials, which is worse than saying nothing.
729- const failing = makeFailingGitLayer (
730- `error: could not create work tree dir 'projects': Permission denied\n` ,
731- ) ;
732-
733- return Effect . scoped (
734- Effect . gen ( function * ( ) {
735- const driver = yield * GitVcsDriver . GitVcsDriver ;
736- const cwd = yield * makeTmpDir ( ) ;
737- const error = yield * driver
738- . execute ( {
739- operation : "GitVcsDriver.test.localPermissionFailure" ,
740- cwd,
741- args : [ "init" , "projects" ] ,
742- } )
743- . pipe ( Effect . flip ) ;
725+ // Git writes a bare "Permission denied" for local filesystem problems, using
726+ // the same two words ssh uses for a rejected key. Matching the phrase alone
727+ // would answer an unwritable directory or a stale lock with advice about
728+ // remote credentials, which is worse than saying nothing.
729+ const localPermissionFailures = [
730+ {
731+ name : "an unwritable clone target" ,
732+ args : [ "init" , "projects" ] ,
733+ stderr : `error: could not create work tree dir 'projects': Permission denied\n` ,
734+ } ,
735+ {
736+ name : "a lock file it cannot take" ,
737+ args : [ "commit" , "-m" , "wip" ] ,
738+ stderr : `fatal: Unable to create '/repo/.git/index.lock': Permission denied\n` ,
739+ } ,
740+ {
741+ name : "a ref file it cannot write" ,
742+ args : [ "fetch" , "origin" ] ,
743+ stderr : `error: cannot open .git/FETCH_HEAD: Permission denied\n` ,
744+ } ,
745+ ] as const ;
746+
747+ for ( const failure of localPermissionFailures ) {
748+ it . effect ( `reads ${ failure . name } as a command failure, not an auth failure` , ( ) => {
749+ const failing = makeFailingGitLayer ( failure . stderr ) ;
750+
751+ return Effect . scoped (
752+ Effect . gen ( function * ( ) {
753+ const driver = yield * GitVcsDriver . GitVcsDriver ;
754+ const cwd = yield * makeTmpDir ( ) ;
755+ const error = yield * driver
756+ . execute ( {
757+ operation : "GitVcsDriver.test.localPermissionFailure" ,
758+ cwd,
759+ args : [ ...failure . args ] ,
760+ } )
761+ . pipe ( Effect . flip ) ;
744762
745- assert . equal ( failing . spawns . length , 1 ) ;
746- assert . instanceOf ( error , GitCommandError ) ;
747- assert . equal ( error . failureKind , "command-failed" ) ;
748- assert . notInclude ( error . detail . toLowerCase ( ) , "authentication" ) ;
749- assert . notInclude ( error . detail . toLowerCase ( ) , "credentials" ) ;
750- } ) ,
751- ) . pipe ( Effect . provide ( failing . layer ) ) ;
752- } ) ;
763+ assert . equal ( failing . spawns . length , 1 ) ;
764+ assert . instanceOf ( error , GitCommandError ) ;
765+ assert . equal ( error . failureKind , "command-failed" ) ;
766+ assert . notInclude ( error . detail . toLowerCase ( ) , "authentication" ) ;
767+ assert . notInclude ( error . detail . toLowerCase ( ) , "credentials" ) ;
768+ } ) ,
769+ ) . pipe ( Effect . provide ( failing . layer ) ) ;
770+ } ) ;
771+ }
753772
754773it . layer ( TestLayer ) ( "GitVcsDriver core integration" , ( it ) => {
755774 describe ( "process environment" , ( ) => {
0 commit comments