at 25.11-pre 7.6 kB view raw
1#!/usr/bin/env nix-shell 2#! nix-shell -i bash -p coreutils gnugrep gnused 3 4################################################################################ 5# nix-diff.sh # 6################################################################################ 7# This script "diffs" Nix profile generations. # 8# # 9# Example: # 10################################################################################ 11# > nix-diff.sh 90 92 # 12# + gnumake-4.2.1 # 13# + gnumake-4.2.1-doc # 14# - htmldoc-1.8.29 # 15################################################################################ 16# The example shows that as of generation 92 and since generation 90, # 17# gnumake-4.2.1 and gnumake-4.2.1-doc have been installed, while # 18# htmldoc-1.8.29 has been removed. # 19# # 20# The example above shows the default, minimal output mode of this script. # 21# For more features, run `nix-diff.sh -h` for usage instructions. # 22################################################################################ 23 24usage() { 25 cat <<EOF 26usage: nix-diff.sh [-h | [-p profile | -s] [-q] [-l] [range]] 27-h: print this message before exiting 28-q: list the derivations installed in the parent generation 29-l: diff every available intermediate generation between parent and 30 child 31-p profile: specify the Nix profile to use 32 * defaults to ~/.nix-profile 33-s: use the system profile 34 * equivalent to: -p /nix/var/nix/profiles/system 35profile: * should be something like /nix/var/nix/profiles/default, not a 36 generation link like /nix/var/nix/profiles/default-2-link 37range: the range of generations to diff 38 * the following patterns are allowed, where A, B, and N are positive 39 integers, and G is the currently active generation: 40 A..B => diffs from generation A to generation B 41 ~N => diffs from the Nth newest generation (older than G) to G 42 A => diffs from generation A to G 43 * defaults to ~1 44EOF 45} 46 47usage_tip() { 48 echo 'run `nix-diff.sh -h` for usage instructions' >&2 49 exit 1 50} 51 52while getopts :hqlp:s opt; do 53 case $opt in 54 h) 55 usage 56 exit 57 ;; 58 q) 59 opt_query=1 60 ;; 61 l) 62 opt_log=1 63 ;; 64 p) 65 opt_profile=$OPTARG 66 ;; 67 s) 68 opt_profile=/nix/var/nix/profiles/system 69 ;; 70 \?) 71 echo "error: invalid option -$OPTARG" >&2 72 usage_tip 73 ;; 74 esac 75done 76shift $((OPTIND-1)) 77 78if [ -n "$opt_profile" ]; then 79 if ! [ -L "$opt_profile" ]; then 80 echo "error: expecting \`$opt_profile\` to be a symbolic link" >&2 81 usage_tip 82 fi 83else 84 opt_profile=$(readlink ~/.nix-profile) 85 if (( $? != 0 )); then 86 echo 'error: unable to dereference `~/.nix-profile`' >&2 87 echo 'specify the profile manually with the `-p` flag' >&2 88 usage_tip 89 fi 90fi 91 92list_gens() { 93 nix-env -p "$opt_profile" --list-generations \ 94 | sed -r 's:^\s*::' \ 95 | cut -d' ' -f1 96} 97 98current_gen() { 99 nix-env -p "$opt_profile" --list-generations \ 100 | grep -E '\(current\)\s*$' \ 101 | sed -r 's:^\s*::' \ 102 | cut -d' ' -f1 103} 104 105neg_gen() { 106 local i=0 from=$1 n=$2 tmp 107 for gen in $(list_gens | sort -rn); do 108 if ((gen < from)); then 109 tmp=$gen 110 ((i++)) 111 ((i == n)) && break 112 fi 113 done 114 if ((i < n)); then 115 echo -n "error: there aren't $n generation(s) older than" >&2 116 echo " generation $from" >&2 117 return 1 118 fi 119 echo $tmp 120} 121 122match() { 123 argv=("$@") 124 for i in $(seq $(($#-1))); do 125 if grep -E "^${argv[$i]}\$" <(echo "$1") >/dev/null; then 126 echo $i 127 return 128 fi 129 done 130 echo 0 131} 132 133case $(match "$1" '' '[0-9]+' '[0-9]+\.\.[0-9]+' '~[0-9]+') in 134 1) 135 diffTo=$(current_gen) 136 diffFrom=$(neg_gen $diffTo 1) 137 (($? == 1)) && usage_tip 138 ;; 139 2) 140 diffFrom=$1 141 diffTo=$(current_gen) 142 ;; 143 3) 144 diffFrom=${1%%.*} 145 diffTo=${1##*.} 146 ;; 147 4) 148 diffTo=$(current_gen) 149 diffFrom=$(neg_gen $diffTo ${1#*~}) 150 (($? == 1)) && usage_tip 151 ;; 152 0) 153 echo 'error: invalid invocation' >&2 154 usage_tip 155 ;; 156esac 157 158dirA="${opt_profile}-${diffFrom}-link" 159dirB="${opt_profile}-${diffTo}-link" 160 161declare -a temp_files 162temp_length() { 163 echo -n ${#temp_files[@]} 164} 165temp_make() { 166 temp_files[$(temp_length)]=$(mktemp) 167} 168temp_clean() { 169 rm -f ${temp_files[@]} 170} 171temp_name() { 172 echo -n "${temp_files[$(($(temp_length)-1))]}" 173} 174trap 'temp_clean' EXIT 175 176temp_make 177versA=$(temp_name) 178refs=$(nix-store -q --references "$dirA") 179(( $? != 0 )) && exit 1 180echo "$refs" \ 181 | grep -v env-manifest.nix \ 182 | sort \ 183 > "$versA" 184 185print_tag() { 186 local gen=$1 187 nix-env -p "$opt_profile" --list-generations \ 188 | grep -E "^\s*${gen}" \ 189 | sed -r 's:^\s*::' \ 190 | sed -r 's:\s*$::' 191} 192 193if [ -n "$opt_query" ]; then 194 print_tag $diffFrom 195 cat "$versA" \ 196 | sed -r 's:^[^-]+-(.*)$: \1:' 197 198 print_line=1 199fi 200 201if [ -n "$opt_log" ]; then 202 gens=$(for gen in $(list_gens); do 203 ((diffFrom < gen && gen < diffTo)) && echo $gen 204 done) 205 # Force the $diffTo generation to be included in this list, instead of using 206 # `gen <= diffTo` in the preceding loop, so we encounter an error upon the 207 # event of its nonexistence. 208 gens=$(echo "$gens" 209 echo $diffTo) 210else 211 gens=$diffTo 212fi 213 214temp_make 215add=$(temp_name) 216temp_make 217rem=$(temp_name) 218temp_make 219out=$(temp_name) 220 221for gen in $gens; do 222 223 [ -n "$print_line" ] && echo 224 225 temp_make 226 versB=$(temp_name) 227 228 dirB="${opt_profile}-${gen}-link" 229 refs=$(nix-store -q --references "$dirB") 230 (( $? != 0 )) && exit 1 231 echo "$refs" \ 232 | grep -v env-manifest.nix \ 233 | sort \ 234 > "$versB" 235 236 in=$(comm -3 -1 "$versA" "$versB") 237 sed -r 's:^[^-]*-(.*)$:\1+:' <(echo "$in") \ 238 | sort -f \ 239 > "$add" 240 241 un=$(comm -3 -2 "$versA" "$versB") 242 sed -r 's:^[^-]*-(.*)$:\1-:' <(echo "$un") \ 243 | sort -f \ 244 > "$rem" 245 246 cat "$rem" "$add" \ 247 | sort -f \ 248 | sed -r 's:(.*)-$:- \1:' \ 249 | sed -r 's:(.*)\+$:\+ \1:' \ 250 | grep -v '^$' \ 251 > "$out" 252 253 if [ -n "$opt_query" -o -n "$opt_log" ]; then 254 255 lines=$(wc -l "$out" | cut -d' ' -f1) 256 tag=$(print_tag "$gen") 257 (( $? != 0 )) && exit 1 258 if [ $lines -eq 0 ]; then 259 echo "$tag (no change)" 260 else 261 echo "$tag" 262 fi 263 cat "$out" \ 264 | sed 's:^: :' 265 266 print_line=1 267 268 else 269 echo "diffing from generation $diffFrom to $diffTo" 270 cat "$out" 271 fi 272 273 versA=$versB 274 275done 276 277exit 0