set -e
set -o pipefail
ROOT="patches"
ERRORS=()
check_root_patches() {
local files=("$@")
local nums=()
for f in "${files[@]}"; do
if [[ ! "$f" =~ ^([0-9]{4})\-(bugfix|feature)\-[A-Za-z0-9_]+\.patch$ ]]; then
ERRORS+=("[Naming error] $ROOT/$f must follow: NNNN-(bugfix|feature)-description.patch (description must use underscores)")
continue
fi
nums+=("${BASH_REMATCH[1]}:$ROOT/$f")
done
if [[ ${#nums[@]} -gt 0 ]]; then
IFS=$'\n'
sorted=($(sort <<< "${nums[*]}"))
unset IFS
first_seq="${sorted[0]%%:*}"
if (( 10#$first_seq != 1 )); then
ERRORS+=("[Number error] First patch must be 0001, but got $first_seq in ${sorted[0]#*:}")
fi
for ((i=1; i < ${#sorted[@]}; i++)); do
prev_seq="${sorted[$((i-1))]%%:*}"
curr_seq="${sorted[$i]%%:*}"
expected=$(( 10#$prev_seq + 1 ))
actual=10#$curr_seq
if (( actual != expected )); then
ERRORS+=("[Number error] Expected sequence $expected, but got $curr_seq in ${sorted[$i]#*:}")
fi
done
fi
}
check_subdir_patches() {
local subdir="$1"
shift
local files=("$@")
local nums=()
for f in "${files[@]}"; do
local full="$subdir/$f"
if [[ ! "$f" =~ ^([0-9]{4})\-([A-Za-z0-9_]+)\-(bugfix|feature)\-([A-Za-z0-9_]+)\.patch$ ]]; then
ERRORS+=("[Naming error] $full must follow: NNNN-module-(bugfix|feature)-description.patch (description must use underscores)")
continue
fi
nums+=("${BASH_REMATCH[1]}:$full")
seq="${BASH_REMATCH[1]}"
module="${BASH_REMATCH[2]}"
expected_module="$(basename "$subdir")"
if [[ "$module" != "$expected_module" ]]; then
ERRORS+=("[Module mismatch] $full: module '$module' does not match directory '$expected_module'")
continue
fi
done
if [[ ${#nums[@]} -gt 0 ]]; then
IFS=$'\n'
sorted=($(sort <<< "${nums[*]}"))
unset IFS
first_seq="${sorted[0]%%:*}"
if (( 10#$first_seq != 1 )); then
ERRORS+=("[Number error] First patch must be 0001, but got $first_seq in ${sorted[0]#*:}")
fi
for ((i=1; i < ${#sorted[@]}; i++)); do
prev_seq="${sorted[$((i-1))]%%:*}"
curr_seq="${sorted[$i]%%:*}"
expected=$(( 10#$prev_seq + 1 ))
actual=10#$curr_seq
if (( actual != expected )); then
ERRORS+=("[Number error] Expected sequence $expected, but got $curr_seq in ${sorted[$i]#*:}")
fi
done
fi
}
check_patch_references_in_readme() {
local readme_path="README.md"
local -a patches=("$@")
if [[ ! -f "$readme_path" ]]; then
ERRORS+=("[Missing README.md] README.md is not found")
return
fi
readme_content=$(cat "$readme_path")
for patch in "${patches[@]}"; do
if [[ "$readme_content" != *"$patch"* ]]; then
ERRORS+=("[Missing reference] Patch '$patch' is not mentioned in README.md.")
fi
done
}
if [ ! -d "$ROOT" ]; then
echo "[ERROR] Directory '$ROOT' does not exist"
exit 1
fi
root_files=()
while IFS= read -r f; do
root_files+=("$f")
done < <(find "$ROOT" -maxdepth 1 -type f -name "*.patch" -printf "%f\n")
check_root_patches "${root_files[@]}"
check_patch_references_in_readme "${root_files[@]}"
while IFS= read -r sub; do
subname=$(basename "$sub")
mod_files=()
while IFS= read -r f; do
mod_files+=("$f")
done < <(find "$sub" -maxdepth 1 -type f -name "*.patch" -printf "%f\n")
check_subdir_patches "$sub" "${mod_files[@]}"
check_patch_references_in_readme "${mod_files[@]}"
done < <(find "$ROOT" -maxdepth 1 -mindepth 1 -type d)
if (( ${#ERRORS[@]} > 0 )); then
echo "Validation issues found:"
for e in "${ERRORS[@]}"; do
echo " - $e"
done
exit 1
fi
echo "All patches follow the naming rules: "
echo " - NNNN-(bugfix|feature)-description.patch/NNNN-module-(bugfix|feature)-description.patch"
echo " - NNNN (Sequence number) are consecutive integers starting from 1."
echo " - Description should use only underscores(_)."
echo " - Appeared at least once in README.md."
exit 0