@@ -80,37 +80,67 @@ func PinAction(action, inputYaml string, exemptedActions []string, pinToImmutabl
80
80
return inputYaml , updated
81
81
}
82
82
83
- pinnedAction := fmt .Sprintf ("%s@%s # %s" , leftOfAt [0 ], commitSHA , tagOrBranch )
83
+ // pinnedAction := fmt.Sprintf("%s@%s # %s", leftOfAt[0], commitSHA, tagOrBranch)
84
+ // build separately so we can quote only the ref, not the comment
85
+ pinnedRef := fmt .Sprintf ("%s@%s" , leftOfAt [0 ], commitSHA )
86
+ comment := fmt .Sprintf (" # %s" , tagOrBranch )
87
+ fullPinned := pinnedRef + comment
84
88
85
89
// if the action with version is immutable, then pin the action with version instead of sha
86
90
pinnedActionWithVersion := fmt .Sprintf ("%s@%s" , leftOfAt [0 ], tagOrBranch )
87
91
if pinToImmutable && semanticTagRegex .MatchString (tagOrBranch ) && IsImmutableAction (pinnedActionWithVersion ) {
88
- pinnedAction = pinnedActionWithVersion
89
- }
92
+ // strings.ReplaceAll is not suitable here because it would incorrectly replace substrings
93
+ // For example, if we want to replace "actions/checkout@v1" to "actions/[email protected] ", it would also incorrectly match and replace in "actions/[email protected] "
94
+ // making new string to "actions/[email protected] "
95
+ //
96
+ // Instead, we use a regex pattern that ensures we only replace complete action references:
97
+ // Pattern: (<action>@<version>)($|\s|"|')
98
+ // - Group 1 (<action>@<version>): Captures the exact action reference
99
+ // - Group 2 ($|\s|"|'): Captures the delimiter that follows (end of line, whitespace, or quotes)
100
+ //
101
+ // Examples:
102
+ // - "actions/[email protected] " - No match (no delimiter after v1)
103
+ // - "actions/checkout@v1 " - Matches (space delimiter)
104
+ // - "actions/checkout@v1"" - Matches (quote delimiter)
105
+ // - "actions/checkout@v1" - Matches (quote delimiter)
106
+ // - "actions/checkout@v1\n" - Matches (newline is considered whitespace \s)
107
+
108
+ actionRegex := regexp .MustCompile (`(` + regexp .QuoteMeta (action ) + `)($|\s|"|')` )
109
+ inputYaml = actionRegex .ReplaceAllString (inputYaml , pinnedActionWithVersion + "$2" )
110
+
111
+ inputYaml , _ = removePreviousActionComments (pinnedActionWithVersion , inputYaml )
112
+ return inputYaml , ! strings .EqualFold (action , pinnedActionWithVersion )
113
+ }
114
+
115
+ updated = ! strings .EqualFold (action , fullPinned )
116
+
117
+ // 1) Double-quoted form: "owner/repo@oldRef"
118
+ doubleQuotedPattern := `"` + regexp .QuoteMeta (action ) + `"` + `($|\s|"|')`
119
+ doubleQuotedRe := regexp .MustCompile (doubleQuotedPattern )
120
+ inputYaml = doubleQuotedRe .ReplaceAllString (
121
+ inputYaml ,
122
+ fmt .Sprintf (`"%s"%s$1` , pinnedRef , comment ),
123
+ )
124
+ inputYaml , _ = removePreviousActionComments (fmt .Sprintf (`"%s"%s` , pinnedRef , comment ), inputYaml )
125
+
126
+ // 2) Single-quoted form: 'owner/repo@oldRef'
127
+ singleQuotedPattern := `'` + regexp .QuoteMeta (action ) + `'` + `($|\s|"|')`
128
+ singleQuotedRe := regexp .MustCompile (singleQuotedPattern )
129
+ inputYaml = singleQuotedRe .ReplaceAllString (
130
+ inputYaml ,
131
+ fmt .Sprintf (`'%s'%s$1` , pinnedRef , comment ),
132
+ )
133
+ inputYaml , _ = removePreviousActionComments (fmt .Sprintf (`'%s'%s` , pinnedRef , comment ), inputYaml )
134
+
135
+ // 3) Unquoted form: owner/repo@oldRef
136
+ unqPattern := `\b` + regexp .QuoteMeta (action ) + `\b` + `($|\s|"|')`
137
+ unqRe := regexp .MustCompile (unqPattern )
138
+ inputYaml = unqRe .ReplaceAllString (
139
+ inputYaml ,
140
+ fullPinned + `$1` ,
141
+ )
142
+ inputYaml , _ = removePreviousActionComments (fullPinned , inputYaml )
90
143
91
- updated = ! strings .EqualFold (action , pinnedAction )
92
-
93
- // strings.ReplaceAll is not suitable here because it would incorrectly replace substrings
94
- // For example, if we want to replace "actions/checkout@v1" to "actions/[email protected] ", it would also incorrectly match and replace in "actions/[email protected] "
95
- // making new string to "actions/[email protected] "
96
- //
97
- // Instead, we use a regex pattern that ensures we only replace complete action references:
98
- // Pattern: (<action>@<version>)($|\s|"|')
99
- // - Group 1 (<action>@<version>): Captures the exact action reference
100
- // - Group 2 ($|\s|"|'): Captures the delimiter that follows (end of line, whitespace, or quotes)
101
- //
102
- // Examples:
103
- // - "actions/[email protected] " - No match (no delimiter after v1)
104
- // - "actions/checkout@v1 " - Matches (space delimiter)
105
- // - "actions/checkout@v1"" - Matches (quote delimiter)
106
- // - "actions/checkout@v1" - Matches (quote delimiter)
107
- // - "actions/checkout@v1\n" - Matches (newline is considered whitespace \s)
108
- actionRegex := regexp .MustCompile (`(` + regexp .QuoteMeta (action ) + `)($|\s|"|')` )
109
- inputYaml = actionRegex .ReplaceAllString (inputYaml , pinnedAction + "$2" )
110
- yamlWithPreviousActionCommentsRemoved , wasModified := removePreviousActionComments (pinnedAction , inputYaml )
111
- if wasModified {
112
- return yamlWithPreviousActionCommentsRemoved , updated
113
- }
114
144
return inputYaml , updated
115
145
}
116
146
0 commit comments