| 1 | #!/bin/bash |
| 2 | |
| 3 | # usage: |
| 4 | # ./compare_overrides.sh GIT_HASH_A GIT_HASH_B |
| 5 | |
| 6 | set -e |
| 7 | cd "$(dirname "$0")/.." |
| 8 | |
| 9 | HASH_A=${1:?usage: ./compare_overrides.sh GIT_HASH_A GIT_HASH_B} |
| 10 | HASH_B=${2:?usage: ./compare_overrides.sh GIT_HASH_A GIT_HASH_B} |
| 11 | |
| 12 | sdk_git_url=https://github.com/dart-lang/sdk |
| 13 | |
| 14 | yq() { |
| 15 | go run github.com/mikefarah/yq/v4@latest "$@" |
| 16 | } |
| 17 | |
| 18 | normalize_url() { |
| 19 | echo "$1" | sed 's|\.git$||' |
| 20 | } |
| 21 | |
| 22 | repo_cache_dir() { |
| 23 | echo "scripts/.cache/repos/$(normalize_url "$1" | sed 's|https://github.com/||; s|/|_|g')" |
| 24 | } |
| 25 | |
| 26 | ensure_repo_clone() { |
| 27 | local url=$1 cache_dir=$2 |
| 28 | if [[ -d "${cache_dir}/.git" ]]; then |
| 29 | git -C "$cache_dir" fetch --quiet origin |
| 30 | else |
| 31 | mkdir -p "$(dirname "$cache_dir")" |
| 32 | if [[ "$(normalize_url "$url")" == "$sdk_git_url" ]]; then |
| 33 | git clone --quiet --filter=blob:none "$url" "$cache_dir" || return 1 |
| 34 | else |
| 35 | git clone --quiet "$url" "$cache_dir" || return 1 |
| 36 | fi |
| 37 | fi |
| 38 | } |
| 39 | |
| 40 | normalize_repo_path() { |
| 41 | local path=$1 |
| 42 | [[ -z "$path" || "$path" == "-" ]] && return 0 |
| 43 | echo "${path#./}" |
| 44 | } |
| 45 | |
| 46 | ensure_ref() { |
| 47 | local cache_dir=$1 ref=$2 |
| 48 | git -C "$cache_dir" cat-file -e "${ref}^{commit}" 2>/dev/null && return 0 |
| 49 | git -C "$cache_dir" fetch --quiet origin "$ref" 2>/dev/null || true |
| 50 | git -C "$cache_dir" cat-file -e "${ref}^{commit}" 2>/dev/null |
| 51 | } |
| 52 | |
| 53 | count_lines_at_ref() { |
| 54 | local cache_dir=$1 ref=$2 path=$3 |
| 55 | local repo_path file lines total=0 |
| 56 | |
| 57 | ensure_ref "$cache_dir" "$ref" || return 1 |
| 58 | repo_path=$(normalize_repo_path "$path") |
| 59 | |
| 60 | while IFS= read -r file; do |
| 61 | [[ -z "$file" ]] && continue |
| 62 | lines=$(git -C "$cache_dir" show "$ref:$file" 2>/dev/null | wc -l | tr -d ' ') |
| 63 | total=$((total + lines)) |
| 64 | done < <( |
| 65 | if [[ -n "$repo_path" ]]; then |
| 66 | git -C "$cache_dir" ls-tree -r --name-only "$ref" -- "$repo_path" 2>/dev/null |
| 67 | else |
| 68 | git -C "$cache_dir" ls-tree -r --name-only "$ref" 2>/dev/null |
| 69 | fi |
| 70 | ) |
| 71 | |
| 72 | echo "$total" |
| 73 | } |
| 74 | |
| 75 | print_ref_diff() { |
| 76 | local cache_dir=$1 old_ref=$2 new_ref=$3 old_path=$4 new_path=$5 |
| 77 | local repo_path diff_paths=() path |
| 78 | |
| 79 | ensure_ref "$cache_dir" "$old_ref" || { |
| 80 | echo " diff (old ref not found: $old_ref)" |
| 81 | return 1 |
| 82 | } |
| 83 | ensure_ref "$cache_dir" "$new_ref" || { |
| 84 | echo " diff (new ref not found: $new_ref)" |
| 85 | return 1 |
| 86 | } |
| 87 | |
| 88 | for path in "$old_path" "$new_path"; do |
| 89 | repo_path=$(normalize_repo_path "$path") |
| 90 | [[ -z "$repo_path" ]] && continue |
| 91 | [[ " ${diff_paths[*]} " == *" $repo_path "* ]] && continue |
| 92 | diff_paths+=("$repo_path") |
| 93 | done |
| 94 | |
| 95 | if [[ ${#diff_paths[@]} -eq 0 ]]; then |
| 96 | git -C "$cache_dir" diff --stat "$old_ref" "$new_ref" | sed 's/^/ /' |
| 97 | else |
| 98 | git -C "$cache_dir" diff --stat "$old_ref" "$new_ref" -- "${diff_paths[@]}" | sed 's/^/ /' |
| 99 | fi |
| 100 | } |
| 101 | |
| 102 | overrides_json_git() { |
| 103 | git show "${1}:pubspec_overrides.yaml" | yq -o=json '.dependency_overrides // {}' |
| 104 | } |
| 105 | |
| 106 | overrides_json_worktree() { |
| 107 | yq -o=json '.dependency_overrides // {}' pubspec_overrides.yaml |
| 108 | } |
| 109 | |
| 110 | print_git_fields() { |
| 111 | local pkg=$1 old_url=$2 new_url=$3 old_ref=$4 new_ref=$5 old_path=$6 new_path=$7 |
| 112 | echo " $pkg" |
| 113 | echo " url $old_url -> $new_url" |
| 114 | echo " ref $old_ref -> $new_ref" |
| 115 | echo " path $old_path -> $new_path" |
| 116 | } |
| 117 | |
| 118 | print_new_repo_stats() { |
| 119 | local new_url=$1 new_ref=$2 new_path=$3 cache_dir loc |
| 120 | |
| 121 | [[ "$new_url" == "-" || "$new_ref" == "-" ]] && return 0 |
| 122 | |
| 123 | cache_dir=$(repo_cache_dir "$new_url") |
| 124 | if ! ensure_repo_clone "$new_url" "$cache_dir"; then |
| 125 | echo " loc (clone failed)" |
| 126 | return 0 |
| 127 | fi |
| 128 | |
| 129 | if loc=$(count_lines_at_ref "$cache_dir" "$new_ref" "$new_path"); then |
| 130 | echo " loc $loc lines" |
| 131 | else |
| 132 | echo " loc (ref not found: $new_ref)" |
| 133 | fi |
| 134 | } |
| 135 | |
| 136 | print_changed_ref_stats() { |
| 137 | local url=$1 old_ref=$2 new_ref=$3 old_path=$4 new_path=$5 cache_dir |
| 138 | |
| 139 | [[ "$url" == "-" || "$old_ref" == "-" || "$new_ref" == "-" ]] && return 0 |
| 140 | |
| 141 | cache_dir=$(repo_cache_dir "$url") |
| 142 | if ! ensure_repo_clone "$url" "$cache_dir"; then |
| 143 | echo " diff (clone failed)" |
| 144 | return 0 |
| 145 | fi |
| 146 | |
| 147 | print_ref_diff "$cache_dir" "$old_ref" "$new_ref" "$old_path" "$new_path" |
| 148 | } |
| 149 | |
| 150 | compare_overrides_jq=' |
| 151 | def git($obj; $pkg): $obj[$pkg].git // null; |
| 152 | def fields($obj; $pkg): |
| 153 | git($obj; $pkg) as $g | |
| 154 | [($g.url // ""), ($g.ref // ""), ($g.path // "")]; |
| 155 | def dash($v): if $v == "" then "-" else $v end; |
| 156 | ' |
| 157 | |
| 158 | resolved_a=$(git rev-parse "$HASH_A") |
| 159 | resolved_b=$(git rev-parse "$HASH_B") |
| 160 | resolved_head=$(git rev-parse HEAD) |
| 161 | |
| 162 | if [[ "$resolved_a" == "$resolved_b" ]]; then |
| 163 | json_a=$(overrides_json_git "$HASH_A") |
| 164 | json_b=$(overrides_json_git "$HASH_B") |
| 165 | elif [[ "$resolved_b" == "$resolved_head" ]]; then |
| 166 | json_a=$(overrides_json_git "$HASH_A") |
| 167 | json_b=$(overrides_json_worktree) |
| 168 | else |
| 169 | json_a=$(overrides_json_git "$HASH_A") |
| 170 | json_b=$(overrides_json_git "$HASH_B") |
| 171 | fi |
| 172 | |
| 173 | new_output=$( |
| 174 | jq -r -n --argjson a "$json_a" --argjson b "$json_b" "$compare_overrides_jq"' |
| 175 | ($b | keys[] | select(git($b; .))) as $pkg | |
| 176 | select((git($a; $pkg) | not) or git($a; $pkg).url != git($b; $pkg).url) | |
| 177 | (fields($a; $pkg)) as $old | |
| 178 | (fields($b; $pkg)) as $new | |
| 179 | [ |
| 180 | $pkg, |
| 181 | dash($old[0]), dash($new[0]), |
| 182 | dash($old[1]), dash($new[1]), |
| 183 | dash($old[2]), dash($new[2]) |
| 184 | ] | @tsv |
| 185 | ' | while IFS=$'\t' read -r pkg old_url new_url old_ref new_ref old_path new_path; do |
| 186 | print_git_fields "$pkg" "$old_url" "$new_url" "$old_ref" "$new_ref" "$old_path" "$new_path" |
| 187 | print_new_repo_stats "$new_url" "$new_ref" "$new_path" |
| 188 | done |
| 189 | ) |
| 190 | |
| 191 | changed_output=$( |
| 192 | jq -r -n --argjson a "$json_a" --argjson b "$json_b" "$compare_overrides_jq"' |
| 193 | ($b | keys[] | select(git($b; .) and git($a; .))) as $pkg | |
| 194 | select(git($a; $pkg).url == git($b; $pkg).url) | |
| 195 | select((git($a; $pkg).ref // "") != (git($b; $pkg).ref // "")) | |
| 196 | (fields($a; $pkg)) as $old | |
| 197 | (fields($b; $pkg)) as $new | |
| 198 | [ |
| 199 | $pkg, |
| 200 | dash($old[0]), dash($new[0]), |
| 201 | dash($old[1]), dash($new[1]), |
| 202 | dash($old[2]), dash($new[2]) |
| 203 | ] | @tsv |
| 204 | ' | while IFS=$'\t' read -r pkg old_url new_url old_ref new_ref old_path new_path; do |
| 205 | print_git_fields "$pkg" "$old_url" "$new_url" "$old_ref" "$new_ref" "$old_path" "$new_path" |
| 206 | print_changed_ref_stats "$new_url" "$old_ref" "$new_ref" "$old_path" "$new_path" |
| 207 | done |
| 208 | ) |
| 209 | |
| 210 | if [[ -n "$new_output" ]]; then |
| 211 | echo "new git repos:" |
| 212 | echo "$new_output" |
| 213 | fi |
| 214 | |
| 215 | if [[ -n "$new_output" && -n "$changed_output" ]]; then |
| 216 | echo |
| 217 | fi |
| 218 | |
| 219 | if [[ -n "$changed_output" ]]; then |
| 220 | echo "changed git refs:" |
| 221 | echo "$changed_output" |
| 222 | fi |