gitserver

self-hosted git server tooling
git clone https://git.ryansepassi.com/git/gitserver.git
Log | Files | Refs | README

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.