You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
277 lines
7.4 KiB
277 lines
7.4 KiB
#!/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
|
|
|