278 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			278 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env nix-shell
 | |
| #! nix-shell -i bash -p coreutils gnugrep gnused
 | |
| 
 | |
| ################################################################################
 | |
| # nix-diff.sh                                                                  #
 | |
| ################################################################################
 | |
| # This script "diffs" Nix profile generations.                                 #
 | |
| #                                                                              #
 | |
| # Example:                                                                     #
 | |
| ################################################################################
 | |
| # > nix-diff.sh 90 92                                                          #
 | |
| # + gnumake-4.2.1                                                              #
 | |
| # + gnumake-4.2.1-doc                                                          #
 | |
| # - htmldoc-1.8.29                                                             #
 | |
| ################################################################################
 | |
| # The example shows that as of generation 92 and since generation 90,          #
 | |
| # gnumake-4.2.1 and gnumake-4.2.1-doc have been installed, while               #
 | |
| # htmldoc-1.8.29 has been removed.                                             #
 | |
| #                                                                              #
 | |
| # The example above shows the default, minimal output mode of this script.     #
 | |
| # For more features, run `nix-diff.sh -h` for usage instructions.              #
 | |
| ################################################################################
 | |
| 
 | |
| usage() {
 | |
|     cat <<EOF
 | |
| usage: nix-diff.sh [-h | [-p profile | -s] [-q] [-l] [range]]
 | |
| -h:         print this message before exiting
 | |
| -q:         list the derivations installed in the parent generation
 | |
| -l:         diff every available intermediate generation between parent and
 | |
|             child
 | |
| -p profile: specify the Nix profile to use
 | |
|             * defaults to ~/.nix-profile
 | |
| -s:         use the system profile
 | |
|             * equivalent to: -p /nix/var/nix/profiles/system
 | |
| profile:    * should be something like /nix/var/nix/profiles/default, not a
 | |
|               generation link like /nix/var/nix/profiles/default-2-link
 | |
| range:      the range of generations to diff
 | |
|             * the following patterns are allowed, where A, B, and N are positive
 | |
|               integers, and G is the currently active generation:
 | |
|                 A..B => diffs from generation A to generation B
 | |
|                 ~N   => diffs from the Nth newest generation (older than G) to G
 | |
|                 A    => diffs from generation A to G
 | |
|             * defaults to ~1
 | |
| EOF
 | |
| }
 | |
| 
 | |
| usage_tip() {
 | |
|     echo 'run `nix-diff.sh -h` for usage instructions' >&2
 | |
|     exit 1
 | |
| }
 | |
| 
 | |
| while getopts :hqlp:s opt; do
 | |
|     case $opt in
 | |
|         h)
 | |
|             usage
 | |
|             exit
 | |
|             ;;
 | |
|         q)
 | |
|             opt_query=1
 | |
|             ;;
 | |
|         l)
 | |
|             opt_log=1
 | |
|             ;;
 | |
|         p)
 | |
|             opt_profile=$OPTARG
 | |
|             ;;
 | |
|         s)
 | |
|             opt_profile=/nix/var/nix/profiles/system
 | |
|             ;;
 | |
|         \?)
 | |
|             echo "error: invalid option -$OPTARG" >&2
 | |
|             usage_tip
 | |
|             ;;
 | |
|     esac
 | |
| done
 | |
| shift $((OPTIND-1))
 | |
| 
 | |
| if [ -n "$opt_profile" ]; then
 | |
|     if ! [ -L "$opt_profile" ]; then
 | |
|         echo "error: expecting \`$opt_profile\` to be a symbolic link" >&2
 | |
|         usage_tip
 | |
|     fi
 | |
| else
 | |
|     opt_profile=$(readlink ~/.nix-profile)
 | |
|     if (( $? != 0 )); then
 | |
|         echo 'error: unable to dereference `~/.nix-profile`' >&2
 | |
|         echo 'specify the profile manually with the `-p` flag' >&2
 | |
|         usage_tip
 | |
|     fi
 | |
| fi
 | |
| 
 | |
| list_gens() {
 | |
|     nix-env -p "$opt_profile" --list-generations \
 | |
|         | sed -r 's:^\s*::' \
 | |
|         | cut -d' ' -f1
 | |
| }
 | |
| 
 | |
| current_gen() {
 | |
|     nix-env -p "$opt_profile" --list-generations \
 | |
|         | grep -E '\(current\)\s*$' \
 | |
|         | sed -r 's:^\s*::' \
 | |
|         | cut -d' ' -f1
 | |
| }
 | |
| 
 | |
| neg_gen() {
 | |
|     local i=0 from=$1 n=$2 tmp
 | |
|     for gen in $(list_gens | sort -rn); do
 | |
|         if ((gen < from)); then
 | |
|             tmp=$gen
 | |
|             ((i++))
 | |
|             ((i == n)) && break
 | |
|         fi
 | |
|     done
 | |
|     if ((i < n)); then
 | |
|         echo -n "error: there aren't $n generation(s) older than" >&2
 | |
|         echo " generation $from" >&2
 | |
|         return 1
 | |
|     fi
 | |
|     echo $tmp
 | |
| }
 | |
| 
 | |
| match() {
 | |
|     argv=("$@")
 | |
|     for i in $(seq $(($#-1))); do
 | |
|         if grep -E "^${argv[$i]}\$" <(echo "$1") >/dev/null; then
 | |
|             echo $i
 | |
|             return
 | |
|         fi
 | |
|     done
 | |
|     echo 0
 | |
| }
 | |
| 
 | |
| case $(match "$1" '' '[0-9]+' '[0-9]+\.\.[0-9]+' '~[0-9]+') in
 | |
|     1)
 | |
|         diffTo=$(current_gen)
 | |
|         diffFrom=$(neg_gen $diffTo 1)
 | |
|         (($? == 1)) && usage_tip
 | |
|         ;;
 | |
|     2)
 | |
|         diffFrom=$1
 | |
|         diffTo=$(current_gen)
 | |
|         ;;
 | |
|     3)
 | |
|         diffFrom=${1%%.*}
 | |
|         diffTo=${1##*.}
 | |
|         ;;
 | |
|     4)
 | |
|         diffTo=$(current_gen)
 | |
|         diffFrom=$(neg_gen $diffTo ${1#*~})
 | |
|         (($? == 1)) && usage_tip
 | |
|         ;;
 | |
|     0)
 | |
|         echo 'error: invalid invocation' >&2
 | |
|         usage_tip
 | |
|         ;;
 | |
| esac
 | |
| 
 | |
| dirA="${opt_profile}-${diffFrom}-link"
 | |
| dirB="${opt_profile}-${diffTo}-link"
 | |
| 
 | |
| declare -a temp_files
 | |
| temp_length() {
 | |
|     echo -n ${#temp_files[@]}
 | |
| }
 | |
| temp_make() {
 | |
|     temp_files[$(temp_length)]=$(mktemp)
 | |
| }
 | |
| temp_clean() {
 | |
|     rm -f ${temp_files[@]}
 | |
| }
 | |
| temp_name() {
 | |
|     echo -n "${temp_files[$(($(temp_length)-1))]}"
 | |
| }
 | |
| trap 'temp_clean' EXIT
 | |
| 
 | |
| temp_make
 | |
| versA=$(temp_name)
 | |
| refs=$(nix-store -q --references "$dirA")
 | |
| (( $? != 0 )) && exit 1
 | |
| echo "$refs" \
 | |
|     | grep -v env-manifest.nix \
 | |
|     | sort \
 | |
|           > "$versA"
 | |
| 
 | |
| print_tag() {
 | |
|     local gen=$1
 | |
|     nix-env -p "$opt_profile" --list-generations \
 | |
|         | grep -E "^\s*${gen}" \
 | |
|         | sed -r 's:^\s*::' \
 | |
|         | sed -r 's:\s*$::'
 | |
| }
 | |
| 
 | |
| if [ -n "$opt_query" ]; then
 | |
|     print_tag $diffFrom
 | |
|     cat "$versA" \
 | |
|         | sed -r 's:^[^-]+-(.*)$:    \1:'
 | |
| 
 | |
|     print_line=1
 | |
| fi
 | |
| 
 | |
| if [ -n "$opt_log" ]; then
 | |
|     gens=$(for gen in $(list_gens); do
 | |
|                ((diffFrom < gen && gen < diffTo)) && echo $gen
 | |
|            done)
 | |
|     # Force the $diffTo generation to be included in this list, instead of using
 | |
|     # `gen <= diffTo` in the preceding loop, so we encounter an error upon the
 | |
|     # event of its nonexistence.
 | |
|     gens=$(echo "$gens"
 | |
|            echo $diffTo)
 | |
| else
 | |
|     gens=$diffTo
 | |
| fi
 | |
| 
 | |
| temp_make
 | |
| add=$(temp_name)
 | |
| temp_make
 | |
| rem=$(temp_name)
 | |
| temp_make
 | |
| out=$(temp_name)
 | |
| 
 | |
| for gen in $gens; do
 | |
| 
 | |
|     [ -n "$print_line" ] && echo
 | |
| 
 | |
|     temp_make
 | |
|     versB=$(temp_name)
 | |
| 
 | |
|     dirB="${opt_profile}-${gen}-link"
 | |
|     refs=$(nix-store -q --references "$dirB")
 | |
|     (( $? != 0 )) && exit 1
 | |
|     echo "$refs" \
 | |
|         | grep -v env-manifest.nix \
 | |
|         | sort \
 | |
|               > "$versB"
 | |
| 
 | |
|     in=$(comm -3 -1 "$versA" "$versB")
 | |
|     sed -r 's:^[^-]*-(.*)$:\1+:' <(echo "$in") \
 | |
|         | sort -f \
 | |
|                > "$add"
 | |
| 
 | |
|     un=$(comm -3 -2 "$versA" "$versB")
 | |
|     sed -r 's:^[^-]*-(.*)$:\1-:' <(echo "$un") \
 | |
|         | sort -f \
 | |
|                > "$rem"
 | |
| 
 | |
|     cat "$rem" "$add" \
 | |
|         | sort -f \
 | |
|         | sed -r 's:(.*)-$:- \1:' \
 | |
|         | sed -r 's:(.*)\+$:\+ \1:' \
 | |
|         | grep -v '^$' \
 | |
|               > "$out"
 | |
| 
 | |
|     if [ -n "$opt_query" -o -n "$opt_log" ]; then
 | |
| 
 | |
|         lines=$(wc -l "$out" | cut -d' ' -f1)
 | |
|         tag=$(print_tag "$gen")
 | |
|         (( $? != 0 )) && exit 1
 | |
|         if [ $lines -eq 0 ]; then
 | |
|             echo "$tag   (no change)"
 | |
|         else
 | |
|             echo "$tag"
 | |
|         fi
 | |
|         cat "$out" \
 | |
|             | sed 's:^:    :'
 | |
| 
 | |
|         print_line=1
 | |
| 
 | |
|     else
 | |
|         echo "diffing from generation $diffFrom to $diffTo"
 | |
|         cat "$out"
 | |
|     fi
 | |
| 
 | |
|     versA=$versB
 | |
| 
 | |
| done
 | |
| 
 | |
| exit 0
 | 
