# Generate a reproducible archive from the content of a directory
#
# $1    : input directory
# $2    : leading component in archive
# $3    : ISO8601 date: YYYY-MM-DDThh:mm:ssZZ
# $4    : output file
# $5... : globs of filenames to exclude from the archive, suitable for
#         find's -path option, and relative to the input directory $1
#
# Notes :
#   - the timestamp is internally rounded to the highest entire second
#     less than or equal to the timestamp (i.e. any sub-second fractional
#     part is ignored)
#   - must not be called with CWD as, or below, the input directory
#   - some temporary files are created in CWD, and removed at the end
#
# Example:
#   $ find /path/to/temp/dir
#   /path/to/temp/dir/
#   /path/to/temp/dir/some-file
#   /path/to/temp/dir/some-dir/
#   /path/to/temp/dir/some-dir/some-other-file
#
#   $ mk_tar_gz /path/to/some/dir \
#               foo_bar-1.2.3 \
#               1970-01-01T00:00:00Z \
#               /path/to/foo.tar.gz \
#               '.git/*' '.svn/*'
#
#   $ tar tzf /path/to/foo.tar.gz
#   foo_bar-1.2.3/some-file
#   foo_bar-1.2.3/some-dir/some-other-file
#
mk_tar_gz() {
    local in_dir="${1}"
    local base_dir="${2}"
    local date="${3}"
    local out="${4}"
    shift 4
    local glob tmp pax_options
    local -a find_opts

    for glob; do
        find_opts+=( -or -path "./${glob#./}" )
    done

    # Drop sub-second precision to play nice with GNU tar's valid_timespec check
    date="$(date -d "${date}" -u +%Y-%m-%dT%H:%M:%S+00:00)"

    pax_options="delete=atime,delete=ctime,delete=mtime"
    pax_options+=",exthdr.name=%d/PaxHeaders/%f,exthdr.mtime={${date}}"

    tmp="$(mktemp --tmpdir="$(pwd)")"
    pushd "${in_dir}" >/dev/null

    # Establish list
    find . -not -type d -and -not \( -false "${find_opts[@]}" \) >"${tmp}.list"
    # Sort list for reproducibility
    LC_ALL=C sort <"${tmp}.list" >"${tmp}.sorted"

    # Create POSIX tarballs, since that's the format the most reproducible
    tar cf - --transform="s#^\./#${base_dir}/#S" \
             --numeric-owner --owner=0 --group=0 --mtime="${date}" \
             --format=posix --pax-option="${pax_options}" \
             -T "${tmp}.sorted" >"${tmp}.tar"

    # Compress the archive
    gzip -6 -n <"${tmp}.tar" >"${out}"

    rm -f "${tmp}"{.list,.sorted,.tar}

    popd >/dev/null
}

post_process_unpack() {
    local dest="${1}"
    local tarball="${2}"
    local one_file

    mkdir "${dest}"
    tar -C "${dest}" --strip-components=1 -xzf "${tarball}"
    one_file="$(find "${dest}" -type f -print0 |LC_ALL=C sort -z |head -z -n1 |tr -d "\0")"
    touch -r "${one_file}" "${dest}.timestamp"
}

post_process_repack() {
    local in_dir="${1}"
    local base_dir="${2}"
    local out="${3}"
    local date

    date="@$(stat -c '%Y' "${in_dir}/${base_dir}.timestamp")"

    mk_tar_gz "${in_dir}/${base_dir}" "${base_dir}" "${date}" "${out}"
}

# Keep this line and the following as last lines in this file.
# vim: ft=bash