From 259fc62cc3c89ff74cbfc379bec0a71d7269b134 Mon Sep 17 00:00:00 2001 From: Nicolas Cantu Date: Sat, 21 Mar 2026 18:34:47 +0100 Subject: [PATCH] fix(split): stage deploy root files only if present for git add MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivations:** - split script failed after subtree split when optional root files (e.g. .dockerignore) were missing on branch test **Root causes:** - git add with a missing path in the list fails entirely and stages nothing; git commit then fails under set -e **Correctifs:** - add each copied file only if it exists in the deploy repo; skip commit if index empty **Evolutions:** - none **Pages affectées:** - setup/split-lecoffre-ng-to-five-repos.sh --- setup/split-lecoffre-ng-to-five-repos.sh | 208 +++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100755 setup/split-lecoffre-ng-to-five-repos.sh diff --git a/setup/split-lecoffre-ng-to-five-repos.sh b/setup/split-lecoffre-ng-to-five-repos.sh new file mode 100755 index 0000000..906e57f --- /dev/null +++ b/setup/split-lecoffre-ng-to-five-repos.sh @@ -0,0 +1,208 @@ +#!/bin/bash +# Split lecoffre_ng (branch test) into five Git repos with history (git subtree split). +# Does NOT read or write /home/ncantu/code/lecoffre_ng: work is done in a disposable clone +# from Gitea (or SPLIT_CLONE_URL). See docs/split-lecoffre-repos.md +# +# Env: +# SPLIT_CLONE_URL clone source (default: git@git.4nkweb.com:4nk/lecoffre_ng.git) +# SOURCE_BRANCH branch to split (default: test) +# OUTPUT_BASE parent directory for output repos (default: /home/ncantu/code) +# REPLACE_OUTPUT=1 remove existing lecoffre-io-*_ng dirs before rebuilding +# PUSH=1 git push -u origin main for each sub-repo after commits +# MAIN_BRANCH default: main +# +# Usage: +# REPLACE_OUTPUT=1 ./split-lecoffre-ng-to-five-repos.sh +# REPLACE_OUTPUT=1 PUSH=1 ./split-lecoffre-ng-to-five-repos.sh + +set -euo pipefail + +CLONE_URL="${SPLIT_CLONE_URL:-git@git.4nkweb.com:4nk/lecoffre_ng.git}" +SOURCE_BRANCH="${SOURCE_BRANCH:-test}" +OUT="${OUTPUT_BASE:-/home/ncantu/code}" +MAIN="${MAIN_BRANCH:-main}" +PUSH="${PUSH:-0}" +REPLACE_OUTPUT="${REPLACE_OUTPUT:-0}" + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +declare -A REMOTES=( + [docs]="git@git.4nkweb.com:4nk/lecoffre-io-docs_ng.git" + [deploy]="git@git.4nkweb.com:4nk/lecoffre-io-deploy_ng.git" + [shared]="git@git.4nkweb.com:4nk/lecoffre-io-shared_ng.git" + [frontend]="git@git.4nkweb.com:4nk/lecoffre-io-frontend_ng.git" + [backend]="git@git.4nkweb.com:4nk/lecoffre-io-backend_ng.git" +) + +declare -A DIRS=( + [docs]="lecoffre-io-docs_ng" + [deploy]="lecoffre-io-deploy_ng" + [shared]="lecoffre-io-shared_ng" + [frontend]="lecoffre-io-frontend_ng" + [backend]="lecoffre-io-backend_ng" +) + +declare -A PREFIX=( + [docs]="docs" + [deploy]="deploy" + [shared]="lecoffre-ressources-dev" + [frontend]="lecoffre-front-main" + [backend]="lecoffre-back-main" +) + +WORK="$(mktemp -d)" +MONO="${WORK}/mono" +cleanup() { + rm -rf "${WORK}" +} +trap cleanup EXIT + +echo "Clone: ${CLONE_URL} (branch ${SOURCE_BRANCH})" +echo "Output: ${OUT}" +echo "Replace existing: ${REPLACE_OUTPUT}" +echo "Push: ${PUSH}" +echo "" + +git clone --branch "${SOURCE_BRANCH}" --single-branch "${CLONE_URL}" "${MONO}" +cd "${MONO}" +git rev-parse --verify "${SOURCE_BRANCH}" >/dev/null || die "Branch ${SOURCE_BRANCH} missing after clone" + +split_branch() { + local key="$1" + local br="split-lecoffre-${key}" + cd "${MONO}" + git branch -D "${br}" 2>/dev/null || true + git subtree split -P "${PREFIX[${key}]}" -b "${br}" "${SOURCE_BRANCH}" >/dev/null +} + +# On branch test, docs/ may be absent: ship top-level *.md into the docs repo instead. +init_docs_repo() { + local dest="${OUT}/${DIRS[docs]}" + if [ "${REPLACE_OUTPUT}" = "1" ] && [ -d "${dest}" ]; then + rm -rf "${dest}" + fi + if [ -d "${dest}" ]; then + die "Destination exists: ${dest} (set REPLACE_OUTPUT=1 to overwrite)" + fi + mkdir -p "${dest}" + cd "${dest}" + git init -b "${MAIN}" + shopt -s nullglob + local f + for f in "${MONO}"/*.md; do + cp -a "${f}" "${dest}/$(basename "${f}")" + done + shopt -u nullglob + if [ -z "$(git status --porcelain)" ]; then + die "No *.md at monorepo root; cannot build docs repo without docs/" + fi + git add . + git commit -m "Import root markdown from lecoffre_ng (branch ${SOURCE_BRANCH})" + git remote add origin "${REMOTES[docs]}" +} + +init_from_split() { + local key="$1" + local dest="${OUT}/${DIRS[${key}]}" + split_branch "${key}" + local br="split-lecoffre-${key}" + if [ "${REPLACE_OUTPUT}" = "1" ] && [ -d "${dest}" ]; then + rm -rf "${dest}" + fi + if [ -d "${dest}" ]; then + die "Destination exists: ${dest} (set REPLACE_OUTPUT=1 to overwrite)" + fi + mkdir -p "${dest}" + cd "${dest}" + git init -b "${MAIN}" + git pull "${MONO}" "${br}" + git remote add origin "${REMOTES[${key}]}" + cd "${MONO}" + git branch -D "split-lecoffre-${key}" 2>/dev/null || true +} + +add_root_docs() { + local dest="${OUT}/${DIRS[docs]}" + local f + for f in CHANGELOG.md; do + if [ -f "${MONO}/${f}" ]; then + cp -a "${MONO}/${f}" "${dest}/${f}" + fi + done + cd "${dest}" + if git status --porcelain | grep -q .; then + git add CHANGELOG.md 2>/dev/null || true + git commit -m "Add CHANGELOG from monorepo root (branch ${SOURCE_BRANCH})" + fi +} + +add_root_deploy() { + local dest="${OUT}/${DIRS[deploy]}" + local files=( + VERSION + package.json + package-lock.json + .dockerignore + .editorconfig + .gitattributes + .gitmessage + .prettierignore + README.md + ) + local f + for f in "${files[@]}"; do + if [ -e "${MONO}/${f}" ]; then + cp -a "${MONO}/${f}" "${dest}/${f}" + fi + done + cd "${dest}" + if ! git status --porcelain | grep -q .; then + return 0 + fi + # git add with a missing path fails the whole command and stages nothing; add only present files. + for f in "${files[@]}"; do + if [ -e "${f}" ]; then + git add "${f}" + fi + done + if git diff --cached --quiet; then + return 0 + fi + git commit -m "Add monorepo root metadata for deploy repo (branch ${SOURCE_BRANCH})" +} + +do_push() { + local key="$1" + local dest="${OUT}/${DIRS[${key}]}" + cd "${dest}" + git push -u origin "${MAIN}" +} + +echo "=== docs -> ${DIRS[docs]} ===" +if [ -d "${MONO}/docs" ]; then + init_from_split "docs" + add_root_docs +else + echo "(no docs/ on ${SOURCE_BRANCH}: using root *.md)" + init_docs_repo +fi + +for key in deploy shared frontend backend; do + echo "=== ${key} -> ${DIRS[${key}]} (${PREFIX[${key}]}) ===" + init_from_split "${key}" +done + +add_root_deploy + +if [ "${PUSH}" = "1" ]; then + for key in docs deploy shared frontend backend; do + echo "=== push ${key} ===" + do_push "${key}" + done +fi + +echo "" +echo "Done. Repos under ${OUT}/lecoffre-io-*_ng (from ${SOURCE_BRANCH}, lecoffre_ng local untouched)."