#!/bin/sh

usage () {
  echo 1>&2 "
usage:
  $0
    [--git commit..commit | --git commit]
    [--svn rev:rev | --svn rev]
    [--path path[:path]*]
    [--help]

This program is used to investigate how any changes inside your nixpkgs
repository may hurt.  With these kind of information you may choose wisely
where you should commit your changes.

This program adapts it-self to your versionning system to avoid too much
effort on your Internet bandwidth.  If you need to check more than one
commits / revisions, you may use the following commands:

  --git remotes/trunk..master
  --svn 17670:17677

    Check the differences between each commit separating the first and the
    last commit.

  --path /etc/nixos/nixpkgs:/tmp/nixpkgs_1:/tmp/nixpkgs_2

    Check the differences between multiple directories containing different
    versions of nixpkgs.

All these options exist with one commit / revision argument.  Such options
are used to compare your \$NIXPKGS path with the specified version.

If you omit to mention any other commit / revision, then your \$NIXPKGS path
is compared with its last update.  This command is useful to test code from
a dirty repository.

"

  exit 1;
}

#####################
# Process Arguments #
#####################

: ${NIXPKGS=/etc/nixos/nixpkgs/}

vcs=""
gitCommits=""
svnRevisions=""
pathLocations=""
verbose=false

argfun=""
for arg; do
  if test -z "$argfun"; then
    case $arg in
      --git) vcs="git"; argfun="set_gitCommits";;
      --svn) vcs="svn"; argfun="set_svnRevisions";;
      --path) vcs="path"; argfun="set_pathLocations";;
      --verbose) verbose=true;;
      --help) usage;;
      *) usage;;
    esac
  else
    case $argfun in
      set_*)
        var=$(echo $argfun | sed 's,^set_,,')
        eval $var=$arg
        ;;
    esac
    argfun=""
  fi
done

if $verbose; then
  set -x
else
  set +x
fi

############################
# Find the repository type #
############################

if test -z "$vcs"; then
  if test -x "$NIXPKGS/.git"; then
    if git --git-dir="$NIXPKGS/.git" branch > /dev/null 2>&1; then
      vcs="git"
      gitCommits=$(git --git-dir="$NIXPKGS/.git" log -n 1 --pretty=format:%H 2> /dev/null)
    fi
  elif test -x "$NIXPKGS/.svn"; then
    cd "$NIXPKGS"
    if svn info > /dev/null 2>&1; then
      vcs="svn";
      svnRevisions=$(svn info | sed -n 's,Revision: ,,p')
    fi
    cd -
  else
    usage
  fi
fi

###############################
# Define a storage directory. #
###############################

pkgListDir=""
exitCode=1
cleanup(){
  test -e "$pkgListDir" && rm -rf "$pkgListDir"
  exit $exitCode;
}

trap cleanup EXIT SIGINT SIGQUIT ERR

pkgListDir=$(mktemp --tmpdir -d rebuild-amount-XXXXXXXX)
vcsDir="$pkgListDir/.vcs"

###########################
# Versionning for Dummies #
###########################

path_init() {
  if test "${pathLocations#*:}" = "$pathLocations"; then
    pathLocations="$NIXPKGS:$pathLocations"
  fi
  pathLocations="${pathLocations}:"
}

path_getNext() {
  pathLoc="${pathLocations%%:*}"
  pathLocations="${pathLocations#*:}"
}

path_setPath() {
  path="$pathLoc"
}

path_setName() {
  name=$(echo "$pathLoc" | tr '/' '_')
}

################
# Git Commands #
################

git_init() {
  git clone "$NIXPKGS/.git" "$vcsDir" > /dev/null 2>&1
  if echo "gitCommits" | grep -c "\.\." > /dev/null 2>&1; then
    gitCommits=$(git --git-dir="$vcsDir/.git" log --reverse --pretty=format:%H $gitCommits 2> /dev/null)
  else
    pathLocations="$vcsDir:$NIXPKGS"
    vcs="path"
    path_init
  fi
}

git_getNext() {
  git --git-dir="$vcsDir/.git" checkout $(echo "$gitCommits" | head -n 1) > /dev/null 2>&1
  gitCommits=$(echo "$gitCommits" | sed '1 d')
}

git_setPath() {
  path="$vcsDir"
}

git_setName() {
  name=$(git --git-dir="$vcsDir/.git" log -n 1 --pretty=format:%H  2> /dev/null)
}

#######################
# Subversion Commands #
#######################

svn_init() {
  cp -r "$NIXPKGS" "$vcsDir" > /dev/null 2>&1
  if echo "svnRevisions" | grep -c ":" > /dev/null 2>&1; then
    svnRevisions=$(seq ${svnRevisions%:*} ${svnRevisions#*:})
  else
    pathLocations="$vcsDir:$NIXPKGS"
    vcs="path"
    path_init
  fi
}

svn_getNext() {
  cd "$vcsDir"
  svn checkout $(echo "$svnRevisions" | head -n 1) > /dev/null 2>&1
  cd -
  svnRevisions=$(echo "$svnRevisions" | sed '1 d')
}

svn_setPath() {
  path="$vcsDir"
}

svn_setName() {
  name=$(svn info  2> /dev/null | sed -n 's,Revision: ,,p')
}

####################
# Logical Commands #
####################

init    () { ${vcs}_init; }
getNext () { ${vcs}_getNext; }
setPath () { ${vcs}_setPath; }
setName () { ${vcs}_setName; }


#####################
# Check for Rebuild #
#####################

# Generate the list of all derivations that could be build from a nixpkgs
# respository.  This list of derivation hashes is compared with previous
# lists and a brief summary is produced on the output.

compareNames () {
    nb=$(diff -y --suppress-common-lines --speed-large-files "$pkgListDir/$1.drvs" "$pkgListDir/$2.drvs" 2> /dev/null | wc -l)
    echo "$1 -> $2: $nb"
}

echo "Please wait, this may take some minutes ..."

init
first=""
oldPrev=""

prev=""
curr=""

while true; do
  getNext
  setPath # set path=...
  setName # set name=...
  curr="$name"

  test -z "$curr" && break || true

  nix-instantiate "$path" > "$pkgListDir/$curr.drvs" > /dev/null 2>&1 || true

  if test -n "$prev"; then
    compareNames "$prev" "$curr"
  else
    echo "Number of package to rebuild:"
    first="$curr"
  fi
  oldPrev="$prev"
  prev="$curr"
done

if test "$first" != "$oldPrev"; then
  echo "Number of package to rebuild (first -> last):"
  compareNames "$first" "$curr"
fi

exitCode=0