/ devlog / check_git_clone.sh
#!/bin/sh
# Serves a real repository from a work clone and clones it back with git,
# so a change to how a reply reaches a client is judged by a client.
#
#   sh devlog/check_git_clone.sh
#
# Unit cases cannot catch what breaks a git clone: a missing status line, a
# content length that does not match the body, a request that runs on past
# where it should end. Each of those shipped once because the only evidence
# was a passing case. Run this before cutting any patch that touches how a
# response is emitted, and paste what it prints.
#
# The script leaves the server running so a following command can use it,
# and prints where the repository was put.
WORK=${WORK:-/home/claude/work}
SEED=${SEED:-/tmp/seed_repo}
OUT=${OUT:-/tmp/cloned_back}
PAGE=${PAGE:-CheckRepo}
COMMITS=${COMMITS:-6}
BYTES_PER_FILE=${BYTES_PER_FILE:-8000000}
cd "$WORK" || exit 1
if [ ! -d "$SEED/.git" ]; then
    rm -rf "$SEED"
    mkdir -p "$SEED"
    cd "$SEED" || exit 1
    git init -q .
    git config user.email check@dev.null
    git config user.name Check
    # Enough content that the reply cannot be sent in one piece and the
    # draining, the window credit and the running-time limit are all
    # exercised. A small repository passed while a large one failed.
    n=1
    while [ "$n" -le "$COMMITS" ]; do
        head -c "$BYTES_PER_FILE" /dev/urandom | base64 > "file$n.txt"
        git add . > /dev/null
        git -c commit.gpgsign=false commit -q -m "commit $n"
        n=`expr $n + 1`
    done
    cd "$WORK" || exit 1
fi
php -r '
require "'"$WORK"'/src/library/Utility.php";
$gm = new seekquarry\yioop\models\GroupModel();
$gm->setPageName(1, 2, "'"$PAGE"'", "words", "en-US", "made", "t", "a", "",
    true, time(), time());
$page = $gm->getPageId(2, "'"$PAGE"'", "en-US");
$folders = $gm->getGroupPageResourcesFolders(2, $page, "", true);
$row = $gm->db->fetchArray($gm->db->execute(
    "SELECT PAGE FROM GROUP_PAGE WHERE ID=?", [$page]));
$at = strpos($row["PAGE"], "END_HEAD_VARS");
$head = substr($row["PAGE"], 0, $at);
$head = preg_replace("/page_type=[a-z_]*/", "page_type=git_repository",
    $head);
if (strpos($head, "page_type") === false) {
    $head .= "page_type=git_repository\n";
}
$gm->db->execute("UPDATE GROUP_PAGE SET PAGE=? WHERE ID=?",
    [$head . substr($row["PAGE"], $at), $page]);
echo $folders[0];
' > /tmp/check_repo_folder.txt 2>/dev/null
folder=`cat /tmp/check_repo_folder.txt`
if [ -z "$folder" ]; then
    echo "check: could not make the repository page"
    exit 1
fi
echo "repository folder: $folder"
cp -r "$SEED"/.git/* "$folder"/ 2>/dev/null
# A server left on the port answers with the code it loaded at startup and
# hides the change under test, which cost several rounds once. Free the
# ports and wait until nothing holds them before starting a fresh one.
pkill -9 -f "php index.php" 2>/dev/null
fuser -k 443/tcp 2>/dev/null
fuser -k 80/tcp 2>/dev/null
sleep 3
holding=`fuser 443/tcp 2>/dev/null | wc -w`
if [ "$holding" -gt 0 ]; then
    echo "check: something still holds port 443; nothing was tested"
    exit 1
fi
setsid sh -c "cd $WORK && timeout -s KILL 600 php index.php secure-start \
    </dev/null >/tmp/check_server.log 2>&1" &
waited=0
while [ "$waited" -lt 40 ]; do
    sleep 2
    waited=`expr $waited + 2`
    code=`curl -sk --max-time 5 -o /dev/null -w "%{http_code}" \
        https://localhost/ 2>/dev/null`
    if [ "$code" = "200" ]; then
        break
    fi
done
echo "server answered: $code after ${waited}s, pid `pgrep -f \
    'php index.php' | head -1`"
if [ "$code" != "200" ]; then
    echo "check: the server never answered; nothing was tested"
    exit 1
fi
echo "advertisement: `curl -sk --max-time 15 -o /dev/null \
    -w '%{http_code} %{size_download} bytes' \
    \"https://localhost/group/2/$PAGE.git/info/refs?service=git-upload-pack\"`"
rm -rf "$OUT"
git -c http.sslVerify=false clone "https://localhost/group/2/$PAGE.git" \
    "$OUT" > /tmp/check_clone.txt 2>&1
if [ ! -d "$OUT/.git" ]; then
    echo "clone: FAIL"
    tail -4 /tmp/check_clone.txt
    exit 1
fi
echo "clone: PASS, `ls \"$OUT\" | wc -l` files, `cd \"$OUT\" && \
    git log --oneline | wc -l` commits"
# A pull is a fetch with what the client already holds, which asks the
# server for a smaller pack built against those; a fresh clone never
# exercises that. Add a commit to what is served, then pull it.
cd "$SEED" || exit 1
echo "another line" >> pulled_change.txt
git add . > /dev/null
git -c commit.gpgsign=false commit -q -m "a change to pull"
cp -r "$SEED"/.git/* "$folder"/ 2>/dev/null
cd "$OUT" || exit 1
git -c http.sslVerify=false pull > /tmp/check_pull.txt 2>&1
if grep -q "pulled_change" /tmp/check_pull.txt ||
    [ -f "$OUT/pulled_change.txt" ]; then
    echo "pull: PASS"
    exit 0
fi
echo "pull: FAIL"
tail -4 /tmp/check_pull.txt
exit 1
X