@@ -203,36 +203,13 @@ impl FromStr for Uses {
203203#[ derive( Debug , PartialEq ) ]
204204pub struct LocalUses {
205205 pub path : String ,
206- pub git_ref : Option < String > ,
207206}
208207
209208impl FromStr for LocalUses {
210209 type Err = UsesError ;
211210
212211 fn from_str ( uses : & str ) -> Result < Self , Self :: Err > {
213- let ( path, git_ref) = match uses. rsplit_once ( '@' ) {
214- Some ( ( path, git_ref) ) => ( path, Some ( git_ref) ) ,
215- None => ( uses, None ) ,
216- } ;
217-
218- if path. is_empty ( ) {
219- return Err ( UsesError ( format ! (
220- "local uses has no path component: {uses}"
221- ) ) ) ;
222- }
223-
224- // TODO: Overly conservative? `uses: ./foo/bar@` might be valid if
225- // `./foo/bar@/action.yml` exists.
226- if git_ref. is_some_and ( |git_ref| git_ref. is_empty ( ) ) {
227- return Err ( UsesError ( format ! (
228- "local uses is missing git ref after '@': {uses}"
229- ) ) ) ;
230- }
231-
232- Ok ( LocalUses {
233- path : path. into ( ) ,
234- git_ref : git_ref. map ( Into :: into) ,
235- } )
212+ Ok ( LocalUses { path : uses. into ( ) } )
236213 }
237214}
238215
@@ -259,9 +236,8 @@ impl FromStr for RepositoryUses {
259236 // In theory we could do `From<String>` instead, but
260237 // `&mut str::split_mut` and similar don't exist yet.
261238
262- // NOTE: Technically both git refs and action paths can contain `@`,
263- // so this isn't guaranteed to be correct. In practice, however,
264- // splitting on the last `@` is mostly reliable.
239+ // NOTE: Both git refs and paths can contain `@`, but in practice
240+ // GHA refuses to run a `uses:` clause with more than one `@` in it.
265241 let ( path, git_ref) = match uses. rsplit_once ( '@' ) {
266242 Some ( ( path, git_ref) ) => ( path, Some ( git_ref) ) ,
267243 None => ( uses, None ) ,
@@ -362,12 +338,29 @@ where
362338 let uses = step_uses ( de) ?;
363339
364340 match uses {
365- Uses :: Repository ( repo) if repo. git_ref . is_none ( ) => Err ( de:: Error :: custom (
366- "repo action must have `@<ref> in reusable workflow" ,
367- ) ) ,
368- // NOTE: local reusable workflows do not have to be pinned.
369- Uses :: Local ( _) => Ok ( uses) ,
370- Uses :: Repository ( _) => Ok ( uses) ,
341+ Uses :: Repository ( ref repo) => {
342+ // Remote reusable workflows must be pinned.
343+ if repo. git_ref . is_none ( ) {
344+ Err ( de:: Error :: custom (
345+ "repo action must have `@<ref>` in reusable workflow" ,
346+ ) )
347+ } else {
348+ Ok ( uses)
349+ }
350+ }
351+ Uses :: Local ( ref local) => {
352+ // Local reusable workflows cannot be pinned.
353+ // We do this with a string scan because `@` *can* occur as
354+ // a path component in local actions uses, just not local reusable
355+ // workflow uses.
356+ if local. path . contains ( '@' ) {
357+ Err ( de:: Error :: custom (
358+ "local reusable workflow reference can't specify `@<ref>`" ,
359+ ) )
360+ } else {
361+ Ok ( uses)
362+ }
363+ }
371364 // `docker://` is never valid in reusable workflow uses.
372365 Uses :: Docker ( _) => Err ( de:: Error :: custom (
373366 "docker action invalid in reusable workflow `uses`" ,
@@ -555,19 +548,17 @@ mod tests {
555548 } ) ) ,
556549 ) ,
557550 (
558- // Valid: Local action ref
551+ // Valid: Local action " ref", actually part of the path
559552 "./.github/actions/hello-world-action@172239021f7ba04fe7327647b213799853a9eb89" ,
560553 Ok ( Uses :: Local ( LocalUses {
561- path : "./.github/actions/hello-world-action" . to_owned ( ) ,
562- git_ref : Some ( "172239021f7ba04fe7327647b213799853a9eb89" . to_owned ( ) ) ,
554+ path : "./.github/actions/hello-world-action@172239021f7ba04fe7327647b213799853a9eb89" . to_owned ( ) ,
563555 } ) ) ,
564556 ) ,
565557 (
566558 // Valid: Local action ref, unpinned
567559 "./.github/actions/hello-world-action" ,
568560 Ok ( Uses :: Local ( LocalUses {
569561 path : "./.github/actions/hello-world-action" . to_owned ( ) ,
570- git_ref : None ,
571562 } ) ) ,
572563 ) ,
573564 // Invalid: missing user/repo
@@ -616,13 +607,12 @@ mod tests {
616607 git_ref : Some ( "abcd" . to_owned ( ) ) ,
617608 } ) ) ,
618609 ) ,
619- // Valid: local reusable workflow
610+ // Invalid: remote reusable workflow without ref
611+ ( "octo-org/this-repo/.github/workflows/workflow-1.yml" , None ) ,
612+ // Invalid: local reusable workflow with ref
620613 (
621614 "./.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89" ,
622- Some ( Uses :: Local ( LocalUses {
623- path : "./.github/workflows/workflow-1.yml" . to_owned ( ) ,
624- git_ref : Some ( "172239021f7ba04fe7327647b213799853a9eb89" . to_owned ( ) ) ,
625- } ) ) ,
615+ None ,
626616 ) ,
627617 // Invalid: no ref at all
628618 ( "octo-org/this-repo/.github/workflows/workflow-1.yml" , None ) ,
0 commit comments