gitpages

A collection of scripts to securely webhost and publish git repositories.
git clone https://scm.kuandu.systems/git-raw/gitpages.git
Log | Files | Refs | README | LICENSE

gitpages-mirror-git.sh (2259B)


      1 #!/bin/sh
      2 #
      3 # Copyright (c) 2026 Fred Großkopf
      4 #
      5 # Permission to use, copy, modify, and/or distribute this software for any
      6 # purpose with or without fee is hereby granted, provided that the above
      7 # copyright notice and this permission notice appear in all copies.
      8 #
      9 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16 
     17 set -eu
     18 
     19 usage() {
     20   cat << EOF
     21 usage: $(basename "$0") SRC DST_DIR
     22 
     23 Mirror bare git repositories to destination.
     24 
     25   SRC     Bare repo OR directory of *.git repos
     26   DST_DIR Output directory
     27 
     28 Examples:
     29   $(basename "$0") /src/repo.git /srv/git
     30   $(basename "$0") /src/repos /srv/git
     31 EOF
     32 }
     33 
     34 error() {
     35   printf 'error: %s\n' "$1" >&2
     36   exit 1
     37 }
     38 warning() { printf 'warning: %s\n' "$1" >&2; }
     39 info() { printf 'info: %s\n' "$1"; }
     40 
     41 check_args() { [ $# -eq 2 ] || {
     42   usage >&2
     43   error "usage: $0 SRC DST_DIR"
     44 }; }
     45 
     46 is_bare_repo() { git --git-dir="$1" rev-parse --is-bare-repository > /dev/null 2>&1; }
     47 has_repos() { [ -d "$1" ] && ls "$1"/*.git > /dev/null 2>&1 2> /dev/null; }
     48 
     49 sync_repo() {
     50   src="$1" dst_dir="$2" name=$(basename "$src")
     51   dst="$dst_dir/$name"
     52 
     53   is_bare_repo "$src" || {
     54     warning "skipping $name (not bare)"
     55     return 0
     56   }
     57 
     58   rm -rf "$dst"
     59   cp -R "$src" "$dst"
     60   git --git-dir="$dst" update-server-info > /dev/null 2>&1
     61 }
     62 
     63 main() {
     64   check_args "$@"
     65   SRC="$1" DST_DIR="$2"
     66 
     67   [ -d "$DST_DIR" ] && [ -w "$DST_DIR" ] || error "DST_DIR '$DST_DIR' not writable"
     68 
     69   if is_bare_repo "$SRC"; then
     70     sync_repo "$SRC" "$DST_DIR"
     71     info "synced: $SRC → $DST_DIR"
     72   elif has_repos "$SRC"; then
     73     [ "$DST_DIR" != "/" ] && rm -rf "$DST_DIR"/*
     74     for repo in "$SRC"/*.git; do
     75       [ -d "$repo" ] || break
     76       sync_repo "$repo" "$DST_DIR"
     77     done
     78     info "synced all: $SRC → $DST_DIR"
     79   else
     80     error "$SRC: neither bare repo nor *.git directory"
     81   fi
     82 }
     83 
     84 main "$@"