]> infiniteadaptability.org Git - mirror-all/commitdiff
initial project completion v1.0.0
authoralex <[email protected]>
Sat, 14 Dec 2024 17:29:12 +0000 (09:29 -0800)
committermirror-all-test <[email protected]>
Sun, 15 Dec 2024 08:22:05 +0000 (00:22 -0800)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
manifest.scm
mirror-all [new file with mode: 0755]
test/clone_repos.tests.sh [new file with mode: 0755]
test/fetch_repos.tests.sh [new file with mode: 0755]
test/find_local_directories.tests.sh [new file with mode: 0755]
test/setup.sh [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..07d19e4
--- /dev/null
@@ -0,0 +1,2 @@
+test/source.sh
+test/.testdir
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..dbe3391
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+
+all:
+       @echo "mirror-all is a shell script; nothing to make. Try 'make install' instead."
+
+check:
+       @head -n `grep -n "main()" mirror-all | cut -f1 -d:` mirror-all | head -n -1 > test/source.sh
+       @cd test && run-parts --exit-on-error --regex '.*\.tests\.sh' .
+
+clean:
+       @rm -vrf test/.testdir
+
+install:
+       @install -v -d "$(BINDIR)/" && install -m 0755 -v mirror-all "$(BINDIR)/mirror-all"
+
+uninstall:
+       @rm -vrf \
+               $(BINDIR)/mirror-all
+
+.PHONY: install uninstall check clean
index 6859940e9414117745227ea6f15b023c3e68f893..2505962c606ea1d784511255c24858558593c8bc 100644 (file)
@@ -1,7 +1,8 @@
-(specifications->manifest
-  (list "bash"
-        "coreutils"
-        "git"
-        "make"
-        "openssh"
-        "shellcheck"))
+(specifications->manifest (list "bash"
+                                "coreutils"
+                                "debianutils"
+                                "git"
+                                "grep"
+                                "make"
+                                "openssh"
+                                "shellcheck"))
diff --git a/mirror-all b/mirror-all
new file mode 100755 (executable)
index 0000000..2cda380
--- /dev/null
@@ -0,0 +1,205 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# option variables
+LOG_LEVEL=default
+
+# binaries
+GIT="git"
+SSH="ssh"
+LS="ls"
+
+# helper variables
+TARGET=
+SERVER=
+TO_CLONE=()
+TO_FETCH=()
+
+detect_git() {
+       log_info "detecting git"
+       if [[ -z "$("$GIT" --version)" ]]; then
+               log_err "git not found in path"
+               exit 1
+       fi
+}
+
+detect_ls() {
+       log_info "detecting ls"
+       if [[ -z "$("$LS" --version)" ]]; then
+               log_err "ls not found in path"
+               exit 1
+       fi
+}
+
+detect_ssh() {
+       log_info "detecting ssh"
+       if [[ -z "$("$SSH" -V 2>&1)" ]]; then
+               log_err "ssh not found in path"
+               exit 1
+       fi
+}
+
+fetch_repos() {
+       local name
+       for repo in "${TO_FETCH[@]}"; do
+               name="$(basename "$repo")"
+               (cd "$name" && "$GIT" fetch --prune --prune-tags && git update-server-info) > /dev/null 2>&1
+       done
+
+       log_info "fetched ${#TO_FETCH[@]} repos"
+}
+
+find_local_directories() {
+       local repos
+
+       log_info "finding all git repos in $TARGET"
+
+       readarray -d '' repos < <("$LS" --zero "$TARGET")
+
+       for i in "${repos[@]}"; do
+               set +e
+               local target="$TARGET/${i}"
+               if [[ "true" == "$("$GIT" -C "$target" rev-parse --is-bare-repository 2>&1)" ]]; then
+                       if [[ -d "$i" ]]; then
+                               TO_FETCH+=("$target")
+                       else
+                               TO_CLONE+=("$target")
+                       fi
+               else
+                       log_info "$target is not a bare git repository, skipping"
+                       unset 
+               fi
+               set -e
+       done
+}
+
+find_remote_directories() {
+       local repos
+
+       log_info "finding all git repos in $SERVER:$TARGET"
+
+       readarray -d '' repos < <("$SSH" "$SERVER" -- "$LS" "$TARGET" --zero)
+       
+       for i in "${repos[@]}"; do
+               set +e
+               local target="$TARGET/${i}"
+               if [[ "true" == "$("$SSH" "$SERVER" -- "$GIT" -C "$target" rev-parse --is-bare-repository 2>&1)" ]]; then
+                       if [[ -d "$i" ]]; then
+                               TO_FETCH+=("$SERVER:$target")
+                       else
+                               TO_CLONE+=("$SERVER:$target")
+                       fi
+               else
+                       log_info "$target is not a git repo, skipping"
+               fi
+               set -e
+       done
+}
+
+clone_repos() {
+       local name
+       for repo in "${TO_CLONE[@]}"; do
+               name="$(basename "$repo")"
+               "$GIT" clone --mirror "$repo" "$name" > /dev/null 2>&1
+               echo "$name" > "$name/description"
+               (cd "$name" && "$GIT" update-server-info)
+       done
+
+       log_info "cloned ${#TO_CLONE[@]} repos"
+}
+
+log() {
+       if [[ "$LOG_LEVEL" != "error" ]]; then
+               echo -e "$@"
+       fi
+}
+
+log_err() {
+       echo -e "$@"
+}
+
+log_info() {
+       if [[ "$LOG_LEVEL" == "info" ]]; then
+               log "$@"
+       fi
+}
+
+parse_arguments() {
+       # transform long options into short opts
+       for arg in "$@"; do
+               shift
+               case "$arg" in
+                       "--quiet") set -- "$@" "-q" ;;
+                       "--verbose") set -- "$@" "-v" ;;
+                       *) set -- "$@" "$arg" ;;
+               esac
+       done
+
+       # parse short options
+       OPTIND=1
+       while getopts "qv" opt
+       do
+               case "$opt" in
+                       "q") set_loglevel "error" ;;
+                       "v") set_loglevel "info" ;;
+                       "?") usage ;;
+               esac
+       done
+
+       shift $(( OPTIND - 1 )) # remove options
+       if [[ "$#" -eq 1 ]]; then
+               # remove trailing slash (if it exists)
+               TARGET="${1%/}"
+       elif [[ "$#" -eq 2 ]]; then
+               SERVER="$1"
+               TARGET="${2%/}"
+       else
+               log_err "Requires at least one argument"
+               exit 1
+       fi
+}
+
+set_loglevel() {
+       log_info "setting loglevel to '$1'"
+       case "$1" in
+               "default") LOG_LEVEL="default" ;;
+               "info") LOG_LEVEL="info" ;;
+               "error") LOG_LEVEL="error" ;;
+               *) log_err "unknown log level '$1'" ;;
+       esac
+}
+
+usage() {
+       log_err "Usage:"
+       log_err ""
+       log_err "\tmirror-all [options] [SERVER] [TARGET]"
+       log_err ""
+       log_err "Options:"
+       log_err "\t--quiet, -q"
+       log_err "\t--verbose, -v"
+       exit 1
+}
+
+main() {
+       parse_arguments "$@"
+
+       detect_git
+
+       if [[ -n "$SERVER" ]]; then
+               detect_ssh
+               find_remote_directories
+       else
+               detect_ls
+               find_local_directories
+       fi
+
+       clone_repos
+       fetch_repos
+
+       log "Successfully updated repos"
+
+       exit
+}
+
+main "$@"
diff --git a/test/clone_repos.tests.sh b/test/clone_repos.tests.sh
new file mode 100755 (executable)
index 0000000..248b92f
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# this command will fail if not executed in test/ directory
+source setup.sh
+
+setup_env
+
+TO_CLONE+=("../repos/repo3")
+LOG_LEVEL=info
+
+cd mirrors
+
+clone_repos > result 2>&1
+EXPECTED="cloned 1 repos"
+RESULT=$(<result)
+assert "$EXPECTED" "$RESULT"
+
+assert "$(<repo3/description)" "repo3"
+
+assert "$(cd repo3 && git rev-parse HEAD)" "$(cd ../repos/repo1 && git rev-parse HEAD)"
+
+cd ..
+
+test_succeeded
diff --git a/test/fetch_repos.tests.sh b/test/fetch_repos.tests.sh
new file mode 100755 (executable)
index 0000000..b0c0b4e
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# this command will fail if not executed in test/ directory
+source setup.sh
+
+setup_env
+
+TO_FETCH+=("../repos/repo3")
+LOG_LEVEL=info
+
+cd mirrors
+
+git clone --mirror ../repos/repo1 repo3 > /dev/null 2>&1
+PREV="$(cd repo3 && git rev-parse HEAD)"
+
+cd ../repos/repo1
+echo "alskdjflasidf" > test4.txt
+git add test4.txt > /dev/null 2>&1
+git commit -m ... > /dev/null 2>&1
+NEXT="$(git rev-parse HEAD)"
+cd ../../mirrors
+
+refute "$PREV" "$NEXT"
+
+fetch_repos > result 2>&1
+EXPECTED="fetched 1 repos"
+RESULT=$(<result)
+assert "$EXPECTED" "$RESULT"
+
+assert "$NEXT" "$(cd repo3 && git rev-parse HEAD)"
+
+cd ..
+
+test_succeeded
diff --git a/test/find_local_directories.tests.sh b/test/find_local_directories.tests.sh
new file mode 100755 (executable)
index 0000000..69d3b87
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# this command will fail if not executed in test/ directory
+source setup.sh
+
+setup_env
+
+TARGET="../repos"
+LOG_LEVEL=info
+
+cd mirrors
+
+find_local_directories > result 2>&1
+EXPECTED=$'finding all git repos in ../repos\n'
+EXPECTED+=$'../repos/notarepo is not a bare git repository, skipping\n'
+EXPECTED+="../repos/repo1 is not a bare git repository, skipping"
+RESULT=$(<result)
+assert "$EXPECTED" "$RESULT"
+
+assert "${#TO_CLONE[@]}" 2
+assert "${TO_CLONE[0]}" "../repos/repo2"
+assert "${TO_CLONE[1]}" "../repos/repo3"
+
+cd ..
+
+test_succeeded
diff --git a/test/setup.sh b/test/setup.sh
new file mode 100644 (file)
index 0000000..ca09dfe
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+
+TEST_DIR=".testdir"
+
+source source.sh
+
+set +e
+(usage > temp)
+USAGE=$(<temp)
+rm -f temp
+set -e
+
+clean_env() {
+       cd ..
+       rm -rf "$TEST_DIR"
+}
+
+setup_env() {
+       mkdir -p "$TEST_DIR/repos/notarepo"
+       echo "thisisatest" > "$TEST_DIR/repos/notarepo/test1"
+       mkdir -p "$TEST_DIR/mirrors"
+       cd "$TEST_DIR/repos"
+       git init --bare repo2
+       git init repo1
+       cd repo1
+       echo "hello" > test.txt
+       echo "laskdjfalskdfj" > test2.txt
+       mkdir -p "hi/test/nope/what"
+       echo "alskdjflaksdjflkasjdflkasjdklasjdlkfjd" > "hi/test/nope/what/test3.txt"
+       git config user.email "[email protected]"
+       git config user.name "mirror-all-test"
+       git add test.txt
+       git add test2.txt
+       git add hi/test/nope/what/test3.txt
+       git commit -m "initial commit"
+       cd ..
+       git clone --mirror repo1 repo3
+       cd ..
+} > /dev/null 2>&1
+
+reset_env() {
+       clean_env
+       setup_env
+}
+
+assert() {
+       if [[ "$1" != "$2" ]]; then
+               echo -e "Expected:\n$1\nResult:\n$2"
+               echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]} failed"
+               exit 1
+       fi
+}
+
+refute() {
+       if [[ "$1" == "$2" ]]; then
+               echo "refute failed!"
+               echo -e "Expected:\n$1\nResult:\n$2"
+               echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]} failed"
+               exit 1
+       fi
+}
+
+test_failed() {
+       echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]} failed"
+       exit 1
+}
+
+test_succeeded() {
+       echo "${BASH_SOURCE[1]} succeeded"
+       clean_env
+}