archive-refs (3051B)
1 #!/bin/sh 2 # Generate GitHub-style archive tarballs for a public repo into 3 # www-public/<name>/archive/. Called from stagit-update for each public repo. 4 # 5 # Produces: 6 # archive/<sha>.tar.gz for each referenced commit (immutable) 7 # archive/refs/heads/<branch>.tar.gz for each branch (rewritten on push) 8 # archive/refs/tags/<tag>.tar.gz for each tag (immutable) 9 # Each with a .b2 BLAKE2b-256 sidecar. 10 # 11 # Usage: archive-refs <path-to-bare-repo> 12 set -eu 13 14 REPOS=$HOME/repos 15 repo=$1 16 [ -d "$repo" ] || exit 0 17 name=$(basename "$repo" .git) 18 out=$REPOS/www-public/$name/archive 19 20 # Non-public repos never get archives. 21 if [ ! -f "$repo/public" ]; then 22 rm -rf "$out" 23 exit 0 24 fi 25 26 mkdir -p "$out/refs/heads" "$out/refs/tags" 27 28 write_b2() { 29 # Write sidecar unconditionally — cheap, and keeps .b2 in sync if tarball moved. 30 b2sum -l 256 < "$1" | awk '{print $1}' > "$1.b2" 31 } 32 33 sha_archive() { 34 sha=$1 35 dst=$out/$sha.tar.gz 36 # Immutable: skip if already written. 37 [ -f "$dst" ] && [ -f "$dst.b2" ] && return 0 38 git --git-dir="$repo" archive --format=tar.gz \ 39 --prefix="$name-$sha/" "$sha" > "$dst.new" 40 mv "$dst.new" "$dst" 41 write_b2 "$dst" 42 } 43 44 ref_archive() { 45 kind=$1 # heads | tags 46 ref=$2 47 sha=$3 48 dst=$out/refs/$kind/$ref.tar.gz 49 mkdir -p "$(dirname "$dst")" 50 tmp=$dst.new 51 git --git-dir="$repo" archive --format=tar.gz \ 52 --prefix="$name-$ref/" "$sha" > "$tmp" 53 if [ ! -f "$dst" ] || ! cmp -s "$tmp" "$dst"; then 54 mv "$tmp" "$dst" 55 write_b2 "$dst" 56 else 57 rm -f "$tmp" 58 fi 59 } 60 61 # HEAD — commit may or may not be reachable via a named ref (detached, etc.). 62 head_sha=$(git --git-dir="$repo" rev-parse HEAD 2>/dev/null || true) 63 [ -n "$head_sha" ] && sha_archive "$head_sha" 64 65 # Branches. 66 git --git-dir="$repo" for-each-ref \ 67 --format='%(refname:short) %(objectname)' refs/heads | 68 while read -r branch sha; do 69 [ -n "$branch" ] || continue 70 ref_archive heads "$branch" "$sha" 71 sha_archive "$sha" 72 done 73 74 # Tags — resolve annotated tags to their commit. 75 git --git-dir="$repo" for-each-ref \ 76 --format='%(refname:short) %(objectname)' refs/tags | 77 while read -r tag obj; do 78 [ -n "$tag" ] || continue 79 sha=$(git --git-dir="$repo" rev-parse "$obj^{commit}") 80 ref_archive tags "$tag" "$sha" 81 sha_archive "$sha" 82 done 83 84 # Prune branch archives whose ref no longer exists (branch deleted). 85 # Use find so nested branch names (feature/foo) are handled. 86 if [ -d "$out/refs/heads" ]; then 87 find "$out/refs/heads" -type f -name '*.tar.gz' | while read -r f; do 88 rel=${f#"$out/refs/heads/"} 89 branch=${rel%.tar.gz} 90 if ! git --git-dir="$repo" show-ref --verify --quiet "refs/heads/$branch"; then 91 rm -f "$f" "$f.b2" 92 fi 93 done 94 # Also clean up empty dirs left behind. 95 find "$out/refs/heads" -type d -empty -delete 2>/dev/null || true 96 fi 97 98 # Tags are immutable by convention — don't prune. If a tag is force-replaced, 99 # ref_archive will overwrite the .tar.gz at that path.