#!/usr/bin/env bash set -euo pipefail ROOT=${3:-$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)} previous_commit=${1:-} target_commit=${2:-HEAD} policy_path=priv/repo/migration_application_compatibility.tsv usage() { echo "Usage: $0 PREVIOUS_COMMIT TARGET_COMMIT [REPOSITORY_ROOT]" >&2 } if [[ -z "$previous_commit" || -z "$target_commit" ]]; then usage exit 2 fi for commit in "$previous_commit" "$target_commit"; do git -C "$ROOT" cat-file -e "$commit^{commit}" 2>/dev/null || { echo "Commit is unavailable in the repository: $commit" >&2 exit 2 } done git -C "$ROOT" merge-base --is-ancestor "$previous_commit" "$target_commit" || { echo "The target commit is not a descendant of the previous commit." >&2 exit 2 } mapfile -t migration_changes < <( git -C "$ROOT" diff --name-status "$previous_commit..$target_commit" -- \ 'priv/repo/migrations/[0-9]*.exs' ) if [[ ${#migration_changes[@]} -eq 0 ]]; then echo "migration_policy=application_safe" echo "migration_versions=none" echo "migration_count=0" exit 0 fi registry=$(git -C "$ROOT" show "$target_commit:$policy_path" 2>/dev/null) || { echo "The target commit does not contain $policy_path." >&2 exit 2 } overall_policy=application_safe versions=() for change in "${migration_changes[@]}"; do status=${change%%$'\t'*} path=${change#*$'\t'} if [[ "$status" != A ]]; then echo "Applied migration files must not be modified, renamed, or deleted: $change" >&2 exit 2 fi filename=${path##*/} version=${filename%%_*} [[ "$version" =~ ^[0-9]{14}$ ]] || { echo "Migration does not have a 14-digit Ecto version: $path" >&2 exit 2 } mapfile -t matches < <( awk -F '\t' -v version="$version" ' $0 !~ /^#/ && $1 == version {print $2 "\t" $3} ' <<<"$registry" ) if [[ ${#matches[@]} -ne 1 ]]; then echo "Migration $version must have exactly one entry in $policy_path." >&2 exit 2 fi policy=${matches[0]%%$'\t'*} reason=${matches[0]#*$'\t'} case "$policy" in application_safe | forward_only) ;; *) echo "Migration $version has an invalid application rollback policy: $policy" >&2 exit 2 ;; esac [[ -n "$reason" && "$reason" != "$policy" ]] || { echo "Migration $version must document why its policy is correct." >&2 exit 2 } if [[ "$policy" == forward_only ]]; then overall_policy=forward_only fi versions+=("$version:$policy") done printf 'migration_policy=%s\n' "$overall_policy" printf 'migration_versions=%s\n' "$(IFS=,; echo "${versions[*]}")" printf 'migration_count=%s\n' "${#versions[@]}"