hc
2024-07-16 5fbd6e2385615a225453562361c4bdab3b15fda1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 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
}
 
# Keep this line and the following as last lines in this file.
# vim: ft=bash