2
1

helpers 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. # Generate a reproducible archive from the content of a directory
  2. #
  3. # $1 : input directory
  4. # $2 : leading component in archive
  5. # $3 : ISO8601 date: YYYY-MM-DDThh:mm:ssZZ
  6. # $4 : output file
  7. # $5... : globs of filenames to exclude from the archive, suitable for
  8. # find's -path option, and relative to the input directory $1
  9. #
  10. # Notes :
  11. # - the timestamp is internally rounded to the highest entire second
  12. # less than or equal to the timestamp (i.e. any sub-second fractional
  13. # part is ignored)
  14. # - must not be called with CWD as, or below, the input directory
  15. # - some temporary files are created in CWD, and removed at the end
  16. #
  17. # Example:
  18. # $ find /path/to/temp/dir
  19. # /path/to/temp/dir/
  20. # /path/to/temp/dir/some-file
  21. # /path/to/temp/dir/some-dir/
  22. # /path/to/temp/dir/some-dir/some-other-file
  23. #
  24. # $ mk_tar_gz /path/to/some/dir \
  25. # foo_bar-1.2.3 \
  26. # 1970-01-01T00:00:00Z \
  27. # /path/to/foo.tar.gz \
  28. # '.git/*' '.svn/*'
  29. #
  30. # $ tar tzf /path/to/foo.tar.gz
  31. # foo_bar-1.2.3/some-file
  32. # foo_bar-1.2.3/some-dir/some-other-file
  33. #
  34. mk_tar_gz() {
  35. local in_dir="${1}"
  36. local base_dir="${2}"
  37. local date="${3}"
  38. local out="${4}"
  39. shift 4
  40. local glob tmp pax_options
  41. local -a find_opts
  42. for glob; do
  43. find_opts+=( -or -path "./${glob#./}" )
  44. done
  45. # Drop sub-second precision to play nice with GNU tar's valid_timespec check
  46. date="$(date -d "${date}" -u +%Y-%m-%dT%H:%M:%S+00:00)"
  47. pax_options="delete=atime,delete=ctime,delete=mtime"
  48. pax_options+=",exthdr.name=%d/PaxHeaders/%f,exthdr.mtime={${date}}"
  49. tmp="$(mktemp --tmpdir="$(pwd)")"
  50. pushd "${in_dir}" >/dev/null
  51. # Establish list
  52. find . -not -type d -and -not \( -false "${find_opts[@]}" \) >"${tmp}.list"
  53. # Sort list for reproducibility
  54. LC_ALL=C sort <"${tmp}.list" >"${tmp}.sorted"
  55. # Create POSIX tarballs, since that's the format the most reproducible
  56. ${TAR} cf - --transform="s#^\./#${base_dir}/#S" \
  57. --numeric-owner --owner=0 --group=0 --mtime="${date}" \
  58. --format=posix --pax-option="${pax_options}" --mode='go=u,go-w' \
  59. -T "${tmp}.sorted" >"${tmp}.tar"
  60. # Compress the archive
  61. gzip -6 -n <"${tmp}.tar" >"${out}"
  62. rm -f "${tmp}"{.list,.sorted,.tar}
  63. popd >/dev/null
  64. }
  65. post_process_unpack() {
  66. local dest="${1}"
  67. local tarball="${2}"
  68. local one_file
  69. mkdir "${dest}"
  70. ${TAR} -C "${dest}" --strip-components=1 -xzf "${tarball}"
  71. one_file="$(find "${dest}" -type f -print0 |LC_ALL=C sort -z |sed 's/\x0.*//')"
  72. touch -r "${one_file}" "${dest}.timestamp"
  73. }
  74. post_process_repack() {
  75. local in_dir="${1}"
  76. local base_dir="${2}"
  77. local out="${3}"
  78. local date
  79. date="@$(stat -c '%Y' "${in_dir}/${base_dir}.timestamp")"
  80. mk_tar_gz "${in_dir}/${base_dir}" "${base_dir}" "${date}" "${out}"
  81. }
  82. trace() { local msg="${1}"; shift; printf "%s: ${msg}" "${my_name}" "${@}"; }
  83. warn() { trace "${@}" >&2; }
  84. error() { warn "${@}"; exit 1; }
  85. # Keep this line and the following as last lines in this file.
  86. # vim: ft=bash