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
|