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
 |