-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yaml
More file actions
232 lines (211 loc) · 8.02 KB
/
Copy pathaction.yaml
File metadata and controls
232 lines (211 loc) · 8.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
name: Detect Relevant Changes
description: "Detect changes matching file type and glob filters between two refs"
inputs:
repo-path:
description: "Path to the checked-out repository"
required: true
base-ref:
description: "Git reference or commit hash representing the diff base"
required: false
default: ""
head-ref:
description: "Git reference or commit hash representing the diff head"
required: false
default: ""
file-type:
description: "File type key or list of keys (comma or newline separated)"
required: false
default: ""
include-globs:
description: "Optional glob filters (comma or newline separated)"
required: false
default: ""
exclude-globs:
description: "Optional glob filters to exclude (comma or newline separated)"
required: false
default: ""
type-pattern-add:
description: >-
Additional type-to-glob mappings (comma or newline separated, e.g. cpp:*.cc). '.in' variants are added
automatically.
required: false
default: ""
outputs:
matched:
description: "true if matching files were found"
value: ${{ steps.detect.outputs.matched }}
matched_files:
description: "Newline-delimited list of matching files"
value: ${{ steps.detect.outputs.matched_files }}
runs:
using: composite
steps:
- name: Detect matching files
id: detect
shell: bash
working-directory: ${{ inputs.repo-path }}
env:
BASE_REF: ${{ inputs.base-ref }}
HEAD_REF: ${{ inputs.head-ref }}
FILE_TYPE: ${{ inputs.file-type }}
INCLUDE_GLOBS: ${{ inputs.include-globs }}
EXCLUDE_GLOBS: ${{ inputs.exclude-globs }}
TYPE_PATTERN_ADD: ${{ inputs.type-pattern-add }}
# yamllint disable rule:line-length
run: |
set -euo pipefail
shopt -s extglob globstar nullglob
declare -A DEFAULT_TYPE_PATTERNS
DEFAULT_TYPE_PATTERNS[cpp]=$'*.cpp\n*.hpp'
DEFAULT_TYPE_PATTERNS[cmake]=$'CMakeLists.txt\n*.cmake'
DEFAULT_TYPE_PATTERNS[python]=$'*.py'
DEFAULT_TYPE_PATTERNS[md]=$'*.md'
DEFAULT_TYPE_PATTERNS[jsonnet]=$'*.jsonnet\n*.libsonnet'
DEFAULT_TYPE_PATTERNS[yaml]=$'*.yaml\n*.yml'
# Types that do not support .in variants for relevance detection
declare -A NO_IN_VARIANT_TYPES
NO_IN_VARIANT_TYPES[jsonnet]="1"
NO_IN_VARIANT_TYPES[yaml]="1"
parse_list() {
local input="$1"
printf '%s' "$input" | tr ',' '\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e '/^$/d'
}
declare -A TYPE_PATTERNS
for key in "${!DEFAULT_TYPE_PATTERNS[@]}"; do
TYPE_PATTERNS[$key]="${DEFAULT_TYPE_PATTERNS[$key]}"
done
while IFS= read -r add; do
[ -z "$add" ] && continue
if [[ "$add" != *:* ]]; then
echo "::warning::Ignoring invalid type definition '$add'"
continue
fi
type_name=${add%%:*}
pattern=${add#*:}
pattern=${pattern#./}
if [ -n "$pattern" ]; then
TYPE_PATTERNS[$type_name]="${TYPE_PATTERNS[$type_name]}${TYPE_PATTERNS[$type_name]:+$'\n'}$pattern"
fi
done < <(parse_list "$TYPE_PATTERN_ADD")
mapfile -t REQUESTED_TYPES < <(parse_list "$FILE_TYPE")
mapfile -t INCLUDE_PATTERNS < <(parse_list "$INCLUDE_GLOBS")
mapfile -t EXCLUDE_PATTERNS < <(parse_list "$EXCLUDE_GLOBS")
if [ -z "$HEAD_REF" ]; then
HEAD_REF=$(git rev-parse HEAD)
fi
if [ -z "$BASE_REF" ]; then
if git rev-parse HEAD^ >/dev/null 2>&1; then
BASE_REF=$(git rev-parse HEAD^)
else
BASE_REF=$(git rev-parse HEAD)
fi
fi
# Fail fast if refs are not present
for ref in "$HEAD_REF" "$BASE_REF"; do
if ! git cat-file -e "$ref" >/dev/null 2>&1; then
echo "::warning::Could not find ref '$ref' in repository. Assuming changes are present."
echo "matched=true" >> "$GITHUB_OUTPUT"
echo "matched_files=" >> "$GITHUB_OUTPUT"
exit 0
fi
done
git diff --name-only "$BASE_REF...$HEAD_REF" > "$RUNNER_TEMP/changed-files.txt"
if [ ! -s "$RUNNER_TEMP/changed-files.txt" ]; then
: > "$RUNNER_TEMP/matched-files.txt"
echo "matched=false" >> "$GITHUB_OUTPUT"
echo "matched_files=" >> "$GITHUB_OUTPUT"
exit 0
fi
declare -A PATTERN_SET=()
if [ "${#REQUESTED_TYPES[@]}" -ne 0 ]; then
add_pattern_variant() {
local base_pattern="$1"
[ -z "$base_pattern" ] && return
PATTERN_SET["$base_pattern"]=1
if [[ "$base_pattern" != *.in ]]; then
PATTERN_SET["$base_pattern.in"]=1
fi
}
for type in "${REQUESTED_TYPES[@]}"; do
patterns=${TYPE_PATTERNS[$type]:-}
if [ -z "$patterns" ]; then
echo "::warning::No patterns defined for type '$type'"
continue
fi
while IFS= read -r pattern; do
pattern=${pattern#./}
[ -z "$pattern" ] && continue
if [ -n "${NO_IN_VARIANT_TYPES[$type]:-}" ]; then
PATTERN_SET["$pattern"]=1
else
add_pattern_variant "$pattern"
fi
done <<< "$patterns"
done
fi
declare -a ALL_PATTERNS
if [ "${#PATTERN_SET[@]}" -gt 0 ]; then
mapfile -t ALL_PATTERNS < <(printf '%s\n' "${!PATTERN_SET[@]}" | LC_ALL=C sort)
else
ALL_PATTERNS=()
fi
# Use git ls-tree instead of find so this works even when the working
# tree contains no files (e.g. after a sparse checkout with no patterns).
{
if [ "${#ALL_PATTERNS[@]}" -gt 0 ]; then
git ls-tree -r --name-only "${HEAD_REF}" | while IFS= read -r file; do
basename="${file##*/}"
for pattern in "${ALL_PATTERNS[@]}"; do
if [[ "$basename" == $pattern ]]; then
printf '%s\n' "$file"
break
fi
done
done
else
git ls-tree -r --name-only "${HEAD_REF}"
fi
} | LC_ALL=C sort > "$RUNNER_TEMP/find-matches.txt"
LC_ALL=C sort "$RUNNER_TEMP/changed-files.txt" > "$RUNNER_TEMP/changed-files-sorted.txt"
if [ -s "$RUNNER_TEMP/find-matches.txt" ]; then
comm -12 "$RUNNER_TEMP/changed-files-sorted.txt" "$RUNNER_TEMP/find-matches.txt" > "$RUNNER_TEMP/initial-matched.txt" || true
else
cp "$RUNNER_TEMP/changed-files-sorted.txt" "$RUNNER_TEMP/initial-matched.txt"
fi
filter_by_include() {
local file="$1"
if [ "${#INCLUDE_PATTERNS[@]}" -eq 0 ]; then
return 0
fi
for include_pattern in "${INCLUDE_PATTERNS[@]}"; do
[[ "$file" == $include_pattern ]] && return 0
done
return 1
}
filter_by_exclude() {
local file="$1"
for exclude_pattern in "${EXCLUDE_PATTERNS[@]}"; do
# $exclude_pattern intentionally unquoted to enable glob matching
[[ "$file" == $exclude_pattern ]] && return 1
done
return 0
}
: > "$RUNNER_TEMP/matched-files.txt"
while IFS= read -r matched_file; do
[ -z "$matched_file" ] && continue
if filter_by_include "$matched_file" && filter_by_exclude "$matched_file"; then
echo "$matched_file" >> "$RUNNER_TEMP/matched-files.txt"
fi
done < "$RUNNER_TEMP/initial-matched.txt"
if [ -s "$RUNNER_TEMP/matched-files.txt" ]; then
echo "matched=true" >> "$GITHUB_OUTPUT"
{
echo "matched_files<<EOF"
cat "$RUNNER_TEMP/matched-files.txt"
echo "EOF"
} >> "$GITHUB_OUTPUT"
else
echo "matched=false" >> "$GITHUB_OUTPUT"
echo "matched_files=" >> "$GITHUB_OUTPUT"
fi
# yamllint enable