fix(split): stage deploy root files only if present for git add

**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
This commit is contained in:
Nicolas Cantu 2026-03-21 18:34:47 +01:00
parent 74cd0050d8
commit 259fc62cc3

View File

@ -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)."