Add the local project, framework submodule, hooks and templates

After the repositories and the mirror, the script now creates the local
project: the directory, git on main, credential-free origin (and github)
remotes, and the license history from Gitea. Then it adds the framework as
a submodule over SSH, installs its skills and git hooks once (plan gate
optional), and copies the AGENTS.md and artifact registry templates.

Nothing is overwritten without a yes: an existing directory, core.hooksPath,
AGENTS.md or docs/artifact-registry.md each ask first (default no). Without
SSH the framework steps can only be skipped, after a yes. No commit is made
in the new project. New optional config key FRAMEWORK_REPO.

New library files git.sh, localproject.sh and framework.sh. Tests run real
git against local bare repositories that stand in for Gitea and the
framework, with a private git configuration (769 checks). The README is now
the full guide.

Task: MIL-003#1
Task: MIL-003#2
Task: MIL-003#3
Task: MIL-003#4
Task: MIL-003#5
Refs #15
Refs #16
Refs #17
Refs #18
Refs #19

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 15:54:02 +08:00
co-authored by Claude Sonnet 5.5
parent 736cdb42fc
commit 0030334e5e
19 changed files with 1185 additions and 68 deletions
+25 -13
View File
@@ -4,12 +4,14 @@
# Purpose
# RepoFoundry creates a Gitea repository, optionally an empty GitHub
# repository with a Gitea -> GitHub push mirror, and a local project with
# the SQA-QC-Framework. This version (MIL-002) validates the configuration
# and credentials, asks for the project details, checks both hosts with
# read-only requests (tokens, owners, names, licence, SSH) and, with
# --apply, creates the repositories and the mirror. Choosing GitHub also
# applies the AGPL-3.0 license to the Gitea repository. The local project
# is not created yet.
# the SQA-QC-Framework. It validates the configuration and credentials,
# asks for the project details, checks both hosts with read-only requests
# (tokens, owners, names, license, SSH) and, with --apply, creates the
# repositories and the mirror, then the local project: its directory, git
# repository, remotes (no credential in any address), the framework as a
# submodule, the framework's skills and git hooks (and the plan gate if
# chosen) and its templates. Choosing GitHub also applies the AGPL-3.0
# license to the Gitea repository. No commit is made in the new project.
#
# Dry run by default
# Without --apply the script only reads from GitHub and Gitea (GET
@@ -31,8 +33,9 @@
#
# Files (parsed, never sourced)
# config.env GITHUB_API_URL, GITHUB_WEB_URL, GITEA_URL, GITEA_API_URL and
# the optional GITEA_SSH_PORT (default 10022) and
# MIRROR_INTERVAL (default 10m0s)
# the optional GITEA_SSH_PORT (default 10022), MIRROR_INTERVAL
# (default 10m0s) and FRAMEWORK_REPO (default
# TirSystem/SQA-QC-Framework, the submodule's OWNER/NAME)
# .env GITHUB_PAT, GITHUB_USER, GITEA_TOKEN
#
# Environment
@@ -44,13 +47,15 @@
# Requires
# bash 4.4 or later, git, curl, mktemp; jq and ssh are optional (jq is used
# for JSON when present; ssh is used for the Gitea SSH test).
# Also the base tools sed, grep, head, tr, sleep, rm, rmdir and uname, and
# Also the base tools sed, grep, head, tr, sleep, find, cp, mkdir, chmod, env, rm,
# rmdir and uname, and
# stat (GNU "stat -c" or BSD "stat -f"; only used outside Windows).
#
# Implements
# MIL-001 tasks 1 to 6 and MIL-002 tasks 1 to 5 (issues #3 to #13), user
# stories US-001.01 and US-001.02, UC-001 steps 1 to 7; see docs/. Deviation
# from the request: its second GITEA_URL key is named GITEA_API_URL.
# MIL-001 tasks 1 to 6, MIL-002 tasks 1 to 5 and MIL-003 tasks 1 to 4
# (issues #3 to #13 and #15 to #18), user stories US-001.01 to US-001.03,
# UC-001 steps 1 to 10; see docs/. Deviation from the request: its second
# GITEA_URL key is named GITEA_API_URL.
#
# Tracing
# set -x is switched off while the script runs, because a trace would print
@@ -61,7 +66,8 @@
# the files in lib/ next to it (one job per file, see the first lines of
# each file): constants, output, temp, util, validate, config, tools, json,
# http, api, prompts, project, hosts, preflight, steps, plan, repositories,
# mirror, apply and cli. The files are loaded from this directory only.
# mirror, git, localproject, framework, apply and cli. The files are loaded
# from this directory only.
#
# Exit codes
# 0 success (or a dry run, or a "no" at the final question), 1 a failed
@@ -129,6 +135,12 @@ source "$SCRIPT_DIR/lib/plan.sh"
source "$SCRIPT_DIR/lib/repositories.sh"
# shellcheck source=lib/mirror.sh
source "$SCRIPT_DIR/lib/mirror.sh"
# shellcheck source=lib/git.sh
source "$SCRIPT_DIR/lib/git.sh"
# shellcheck source=lib/localproject.sh
source "$SCRIPT_DIR/lib/localproject.sh"
# shellcheck source=lib/framework.sh
source "$SCRIPT_DIR/lib/framework.sh"
# shellcheck source=lib/apply.sh
source "$SCRIPT_DIR/lib/apply.sh"
# shellcheck source=lib/cli.sh
+22 -1
View File
@@ -4,7 +4,7 @@
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: create_all, confirm_reuse, apply_plan
# Provides: create_all, confirm_reuse, confirm_framework_access, apply_plan
create_all() {
IS_CREATION_STARTED=1
@@ -15,6 +15,25 @@ create_all() {
if ((PROJECT[has_github])); then
configure_mirror
fi
create_local_project
add_framework
install_framework
copy_templates
}
# confirm_framework_access: the framework comes over SSH. Without SSH the
# Maintainer can still go on, without the framework steps, after a yes.
confirm_framework_access() {
if ((${STATE[is_ssh_ok]:-0})); then
STATE[skip_framework]=0
return 0
fi
warn "SSH to Gitea ($(gitea_host) port ${CONFIG[GITEA_SSH_PORT]}) did not work, so the framework cannot be added: ${STATE[ssh_note]}"
prompt_yes_no "Create the repositories and the local project without the framework" n
if ! ((REPLY)); then
die "stopped: set up SSH access to Gitea (see the README) and run again"
fi
STATE[skip_framework]=1
}
confirm_reuse() {
@@ -42,5 +61,7 @@ apply_plan() {
return 0
fi
confirm_reuse
confirm_local_directory
confirm_framework_access
create_all
}
+3
View File
@@ -100,6 +100,9 @@ validate_config() {
CONFIG[MIRROR_INTERVAL]="${CONFIG[MIRROR_INTERVAL]:-$DEFAULT_MIRROR_INTERVAL}"
is_valid_interval "${CONFIG[MIRROR_INTERVAL]}" ||
die "MIRROR_INTERVAL in $CONFIG_FILE must look like 10m0s or 8h0m0s"
CONFIG[FRAMEWORK_REPO]="${CONFIG[FRAMEWORK_REPO]:-$DEFAULT_FRAMEWORK_REPO}"
is_valid_framework_repo "${CONFIG[FRAMEWORK_REPO]}" ||
die "FRAMEWORK_REPO in $CONFIG_FILE must look like OWNER/NAME"
}
validate_credentials() {
+5 -3
View File
@@ -8,7 +8,7 @@
# shellcheck disable=SC2034 # read and written by the other library files
readonly PROJECT_NAME="${REPOFOUNDRY_NAME:-RepoFoundry}"
readonly VERSION="0.2.0"
readonly VERSION="0.3.0"
readonly EXIT_FAILURE=1
readonly EXIT_USAGE=2
readonly MAX_VALUE_LENGTH=2048
@@ -16,12 +16,14 @@ readonly MAX_DESCRIPTION_LENGTH=350
readonly HTTP_TIMEOUT_SECONDS=30
readonly DEFAULT_MIRROR_INTERVAL="10m0s"
readonly DEFAULT_SSH_PORT=10022
readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework"
readonly AGPL_LICENSE_KEY="AGPL-3.0"
readonly DEFAULT_BRANCH="main"
readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror")
readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror"
"Local project" "Framework" "Skills and hooks" "Templates")
# shellcheck disable=SC2034 # read through namerefs (parse_env_file)
readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL
GITEA_SSH_PORT MIRROR_INTERVAL)
GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO)
readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN)
CONFIG_FILE="$PROJECT_ROOT/config.env"
+153
View File
@@ -0,0 +1,153 @@
# shellcheck shell=bash
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
# framework.sh - The SQA-QC-Framework in the new project: submodule, skills, hooks and templates.
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: is_framework_skipped, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates
# is_framework_skipped: succeed when the framework steps are left out because
# SSH to Gitea is not available (the Maintainer agreed to that).
is_framework_skipped() {
[[ ${STATE[skip_framework]:-0} == 1 ]]
}
# add_framework: git submodule add of the framework as "framework". SSH is
# needed for it; a failure says how to test the access.
add_framework() {
local label="Framework" dir="${PROJECT[directory]}" url err existing
url="$(framework_url)"
if is_framework_skipped; then
finish_step "$label" "skipped" "(no SSH access to Gitea)"
return 0
fi
begin_step "$label"
if [[ -e $dir/framework ]]; then
existing="$(git_project "$dir" config -f .gitmodules --get submodule.framework.url || true)"
if [[ $existing != "$url" ]]; then
die "'framework' already exists in $dir and is not the framework submodule ($url)"
fi
finish_step "$label" "reused" "$url (already a submodule)"
return 0
fi
make_temp_file
err="$REPLY"
if ! git_project "$dir" submodule add -q "$url" framework 2>"$err"; then
die "git could not add the framework from $url. Check the SSH access first: ssh -p ${CONFIG[GITEA_SSH_PORT]} -T git@$(gitea_host). Git said: $(head -n 2 "$err" | tr '\n' ' ')"
fi
finish_step "$label" "created" "$url"
}
# run_framework_script DIR SCRIPT [ARG...]: run one of the framework's own
# scripts inside the project. PROJECT_ROOT is set explicitly so that a
# PROJECT_ROOT in the caller's environment cannot point it elsewhere.
run_framework_script() {
local dir="$1" script="$2" out abs
shift 2
abs="$(cd "$dir" && pwd)"
make_temp_file
out="$REPLY"
if ! (cd "$abs" && env PROJECT_ROOT="$abs" bash "framework/scripts/$script" "$@") >"$out" 2>&1; then
die "the framework script $script failed: $(tail -n 3 "$out" | tr '\n' ' ')"
fi
}
# install_skills DIR: install the framework's skills once. The installer is
# safe to repeat but copies everything again, so a project that already has
# them is left alone.
install_skills() {
local dir="$1"
if [[ -f $dir/.agents/skills/.framework-skills && -f $dir/.claude/skills/.framework-skills ]]; then
STATE[skills_note]="skills already installed"
return 0
fi
run_framework_script "$dir" install-skills.sh
STATE[skills_note]="skills installed"
}
# install_hooks DIR: point core.hooksPath at the framework's hooks, and turn
# on the plan gate if chosen. A different hooks path that is already set is
# only replaced after a yes.
install_hooks() {
local dir="$1" current global gate=() gate_enabled
if ((PROJECT[is_plan_gate_enabled])); then
gate=(--enable-plan-gate)
fi
# Both settings are normally unset; git config exits 1 then.
current="$(git_project "$dir" config --local --get core.hooksPath || true)"
global="$(git_project "$dir" config --global --get core.hooksPath || true)"
gate_enabled="$(git_project "$dir" config --local --get planGate.enabled || true)"
if [[ -z $current && -n $global ]]; then
warn "your global core.hooksPath is '$global'; this project sets its own, which takes precedence here"
fi
if [[ -z $current ]]; then
run_framework_script "$dir" install-git-hooks.sh "${gate[@]}"
STATE[hooks_note]="hooks installed"
elif [[ $current == framework/githooks ]]; then
STATE[hooks_note]="hooks already installed"
if ((PROJECT[is_plan_gate_enabled])) && [[ $gate_enabled != true ]]; then
run_framework_script "$dir" install-git-hooks.sh "${gate[@]}"
fi
else
prompt_yes_no "core.hooksPath is already '$current'. Replace it with framework/githooks" n
if ((REPLY)); then
run_framework_script "$dir" install-git-hooks.sh "${gate[@]}"
STATE[hooks_note]="hooks installed (replaced '$current')"
else
STATE[hooks_note]="kept the existing hooks path '$current'"
if ((PROJECT[is_plan_gate_enabled])); then
warn "the plan gate is not enabled because the framework hooks were not installed"
fi
fi
fi
}
# install_framework: skills, then hooks (and the plan gate). Each is done once.
install_framework() {
local label="Skills and hooks" dir="${PROJECT[directory]}" gate_state="plan gate off"
if is_framework_skipped; then
finish_step "$label" "skipped" "(no SSH access to Gitea)"
return 0
fi
begin_step "$label"
install_skills "$dir"
install_hooks "$dir"
if [[ $(git_project "$dir" config --local --get planGate.enabled || true) == true ]]; then
gate_state="plan gate on"
fi
finish_step "$label" "created" "(${STATE[skills_note]}; ${STATE[hooks_note]}; $gate_state)"
}
# copy_template DIR SOURCE TARGET: copy a framework template. An existing
# target is only replaced after a yes. The result goes to STATE[template_note].
copy_template() {
local dir="$1" source="$2" target="$3"
if [[ ! -f $dir/$source ]]; then
die "the framework has no $source; is the submodule complete?"
fi
if [[ -e $dir/$target ]]; then
prompt_yes_no "$target already exists. Replace it with the framework template" n
if ! ((REPLY)); then
STATE[template_note]="kept existing $target"
return 0
fi
fi
cp -- "$dir/$source" "$dir/$target"
STATE[template_note]="copied $target"
}
# copy_templates: AGENTS.md and docs/artifact-registry.md from the framework.
copy_templates() {
local label="Templates" dir="${PROJECT[directory]}" notes=""
if is_framework_skipped; then
finish_step "$label" "skipped" "(no SSH access to Gitea)"
return 0
fi
begin_step "$label"
mkdir -p -- "$dir/docs"
copy_template "$dir" framework/templates/AGENTS-template.md AGENTS.md
notes="${STATE[template_note]}"
copy_template "$dir" framework/templates/artifact-registry-template.md docs/artifact-registry.md
notes="$notes; ${STATE[template_note]}"
finish_step "$label" "created" "($notes)"
}
+48
View File
@@ -0,0 +1,48 @@
# shellcheck shell=bash
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
# git.sh - Running git for the new project: no prompts, no token on a command line.
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: git_project, fetch_origin
# git_project DIR ARGS...: run git in DIR. Prompts are switched off, so a
# missing credential or SSH key fails at once instead of waiting for input.
git_project() {
local dir="$1"
shift
GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh} -o BatchMode=yes" \
git -C "$dir" "$@"
}
# fetch_origin DIR: fetch the Gitea repository into the project. Over SSH the
# user's key is used. Over HTTPS the token reaches git through a private
# GIT_ASKPASS helper and the environment of this one command: it is never part
# of a URL, of the remote configuration or of a command line.
fetch_origin() {
local dir="$1" url askpass
url="$(git_project "$dir" config --get remote.origin.url)"
if [[ $url != https://* ]]; then
git_project "$dir" fetch -q origin
return
fi
make_temp_file
askpass="$REPLY"
# shellcheck disable=SC2016 # the helper is written out literally: it expands $1 and its environment itself
{
printf '%s\n' '#!/usr/bin/env bash'
printf '%s\n' 'case "$1" in'
printf '%s\n' ' *sername*) printf "%s\n" "$REPOFOUNDRY_ASKPASS_USER" ;;'
printf '%s\n' ' *) printf "%s\n" "$REPOFOUNDRY_ASKPASS_TOKEN" ;;'
printf '%s\n' 'esac'
} >"$askpass"
chmod 700 "$askpass"
# Not "cmd; rm": a failed fetch must still be reported to the caller.
if ! GIT_ASKPASS="$askpass" REPOFOUNDRY_ASKPASS_USER="${STATE[gitea_login]}" \
REPOFOUNDRY_ASKPASS_TOKEN="${CREDENTIALS[GITEA_TOKEN]}" \
git_project "$dir" fetch -q origin; then
rm -f -- "$askpass"
return 1
fi
rm -f -- "$askpass"
}
+38 -1
View File
@@ -4,7 +4,7 @@
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: is_reused, repo_owner, repo_url
# Provides: is_reused, repo_owner, repo_url, gitea_host, origin_protocol, origin_url, github_remote_url, framework_url
# is_reused HOST: succeed if the existing repository on HOST will be reused.
is_reused() {
@@ -26,3 +26,40 @@ repo_url() {
printf '%s/%s/%s' "${CONFIG[GITHUB_WEB_URL]}" "${PROJECT[github_owner]}" "${PROJECT[name]}"
fi
}
# gitea_host: the host name of the Gitea server, from GITEA_URL.
gitea_host() {
local host="${CONFIG[GITEA_URL]#https://}"
host="${host%%/*}"
printf '%s' "${host%%:*}"
}
# origin_protocol: SSH when the SSH test passed, otherwise HTTPS.
origin_protocol() {
if ((${STATE[is_ssh_ok]:-0})); then
printf 'SSH'
else
printf 'HTTPS'
fi
}
# origin_url: the address of the Gitea repository for the origin remote. It
# never holds a credential: "git@" is the SSH user name, not a secret.
origin_url() {
if [[ $(origin_protocol) == SSH ]]; then
printf 'ssh://git@%s:%s/%s/%s.git' "$(gitea_host)" "${CONFIG[GITEA_SSH_PORT]}" \
"${PROJECT[gitea_owner]}" "${PROJECT[name]}"
else
printf '%s/%s/%s.git' "${CONFIG[GITEA_URL]}" "${PROJECT[gitea_owner]}" "${PROJECT[name]}"
fi
}
github_remote_url() {
printf '%s/%s/%s.git' "${CONFIG[GITHUB_WEB_URL]}" "${PROJECT[github_owner]}" "${PROJECT[name]}"
}
# framework_url: where the framework submodule comes from (always SSH).
framework_url() {
printf 'ssh://git@%s:%s/%s.git' "$(gitea_host)" "${CONFIG[GITEA_SSH_PORT]}" \
"${CONFIG[FRAMEWORK_REPO]}"
}
+90
View File
@@ -0,0 +1,90 @@
# shellcheck shell=bash
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
# localproject.sh - The local project: its directory, its git repository and its remotes.
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: inspect_local_directory, confirm_local_directory, ensure_remote, checkout_gitea_history, create_local_project
# inspect_local_directory: record in STATE[local_dir] whether the project
# directory is missing, empty or not_empty. It creates nothing.
inspect_local_directory() {
local dir="${PROJECT[directory]}"
if [[ ! -e $dir ]]; then
STATE[local_dir]="missing"
elif [[ ! -d $dir ]]; then
die "$dir already exists and is not a directory"
elif [[ -z "$(find "$dir" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then
STATE[local_dir]="empty"
else
STATE[local_dir]="not_empty"
fi
}
# confirm_local_directory: an existing directory is only used after a yes.
confirm_local_directory() {
local dir="${PROJECT[directory]}" what="is empty"
if [[ ${STATE[local_dir]} == missing ]]; then
return 0
fi
if [[ ${STATE[local_dir]} == not_empty ]]; then
what="already has files"
fi
prompt_yes_no "The directory $dir already exists and $what. Use it" n
if ! ((REPLY)); then
die "stopped: choose another directory or remove this one"
fi
}
# ensure_remote DIR NAME URL: add the remote, or accept one that already has
# exactly this address. A different address is never overwritten.
ensure_remote() {
local dir="$1" name="$2" url="$3" existing
# git config exits 1 when the remote is not set; that is the normal case.
existing="$(git_project "$dir" config --get "remote.$name.url" || true)"
if [[ -z $existing ]]; then
git_project "$dir" remote add "$name" "$url"
elif [[ $existing != "$url" ]]; then
die "the remote '$name' in $dir already points to $existing; remove it or choose another directory"
fi
}
# checkout_gitea_history DIR: when the Gitea repository holds the license
# commit, fetch it and start the local branch from it, so the local history
# begins with that commit. Existing files are never overwritten: git refuses.
checkout_gitea_history() {
local dir="$1" err
if ! fetch_origin "$dir" 2>/dev/null; then
die "could not fetch the Gitea repository from $(origin_url); check your SSH key (or, over HTTPS, the token) and run the same command again"
fi
if ! git_project "$dir" rev-parse --verify -q refs/remotes/origin/main >/dev/null; then
return 0
fi
if git_project "$dir" rev-parse --verify -q HEAD >/dev/null 2>&1; then
warn "$dir already has history: the Gitea content was fetched but not checked out"
return 0
fi
make_temp_file
err="$REPLY"
if ! git_project "$dir" checkout -q -b main --track origin/main 2>"$err"; then
die "git would overwrite files in $dir with the Gitea content; move them away and run again. Git said: $(head -n 2 "$err" | tr '\n' ' ')"
fi
}
# create_local_project: the directory, the git repository on main, the
# remotes and, when GitHub is chosen, the license history. No commit is made.
create_local_project() {
local label="Local project" dir="${PROJECT[directory]}"
begin_step "$label"
mkdir -p -- "$dir"
if [[ ! -e $dir/.git ]]; then
git_project "$dir" init -q
git_project "$dir" symbolic-ref HEAD "refs/heads/$DEFAULT_BRANCH"
fi
ensure_remote "$dir" origin "$(origin_url)"
if ((PROJECT[has_github])); then
ensure_remote "$dir" github "$(github_remote_url)"
checkout_gitea_history "$dir"
fi
finish_step "$label" "created" "$dir (origin over $(origin_protocol))"
}
+20 -2
View File
@@ -4,7 +4,17 @@
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: print_plan
# Provides: local_plan_note, print_plan
# local_plan_note: what will happen to the project directory.
local_plan_note() {
local dir="${PROJECT[directory]}"
case "${STATE[local_dir]}" in
missing) printf 'create %s (new directory), git on %s, no commit' "$dir" "$DEFAULT_BRANCH" ;;
empty) printf 'use the existing empty directory %s (you will be asked)' "$dir" ;;
*) printf 'use the existing directory %s, which has files (you will be asked)' "$dir" ;;
esac
}
print_plan() {
local gitea_action github_action origin_note
@@ -35,5 +45,13 @@ print_plan() {
else
origin_note="HTTPS (SSH test: ${STATE[ssh_note]})"
fi
say "$(printf ' %-18s: %s' "Local origin" "will use $origin_note, in a later phase")"
say "$(printf ' %-18s: %s' "Local project" "$(local_plan_note)")"
say "$(printf ' %-18s: %s' "Local origin" "will use $origin_note")"
if ((STATE[is_ssh_ok])); then
say "$(printf ' %-18s: %s' "Framework" "add $(framework_url) as a submodule")"
say "$(printf ' %-18s: %s' "Skills and hooks" "install once; plan gate $(yes_no "${PROJECT[is_plan_gate_enabled]}")")"
say "$(printf ' %-18s: %s' "Templates" "AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced)")"
else
say "$(printf ' %-18s: %s' "Framework" "NOT possible without SSH to Gitea; you will be asked whether to go on without it")"
fi
}
+2 -3
View File
@@ -108,9 +108,7 @@ preflight_github() {
# or without access it is simply "not passed"; it never stops the run.
test_gitea_ssh() {
local host port output status=0
host="${CONFIG[GITEA_URL]#https://}"
host="${host%%/*}"
host="${host%%:*}"
host="$(gitea_host)"
port="${CONFIG[GITEA_SSH_PORT]}"
STATE[is_ssh_ok]=0
STATE[ssh_note]="failed (check your SSH key and that $host:$port is reachable)"
@@ -163,5 +161,6 @@ run_preflight() {
fi
test_gitea_ssh
decide_existing_repositories
inspect_local_directory
say "All checks passed."
}
+3 -2
View File
@@ -42,10 +42,11 @@ report_outcome() {
say "$(printf ' %-18s: %s' "$label" "${STEP_STATUS[$label]}${STEP_DETAIL[$label]:+ ${STEP_DETAIL[$label]}}")"
done
if ((code == 0)); then
say "The local project with the framework is created by a later phase."
say "The project is in ${PROJECT[directory]}. Nothing was committed there: review it, then work on a branch."
else
say "To continue: fix the problem named above and run the same command again with --apply."
say "A repository this run created is still empty (or holds only the license), so the next run offers to reuse it."
say "Nothing is deleted automatically. To start over, delete the repositories above in the web interface."
say "A directory, remotes and submodule created so far are used again by the next run; you are asked before an existing directory or file is touched."
say "Nothing is deleted automatically. To start over, delete the repositories above in the web interface and the project directory by hand."
fi
}
+6 -1
View File
@@ -4,7 +4,7 @@
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: is_valid_repo_name, is_valid_gitea_owner, is_valid_github_owner, is_valid_description, is_valid_directory, is_valid_base_url, is_valid_request_url, is_valid_token, is_valid_port, is_valid_interval, normalize_url
# Provides: is_valid_framework_repo, is_valid_repo_name, is_valid_gitea_owner, is_valid_github_owner, is_valid_description, is_valid_directory, is_valid_base_url, is_valid_request_url, is_valid_token, is_valid_port, is_valid_interval, normalize_url
is_valid_repo_name() {
local name="$1"
@@ -49,6 +49,11 @@ is_valid_token() {
[[ $1 =~ ^[A-Za-z0-9_.~+/=-]{8,255}$ ]]
}
# OWNER/NAME of the framework repository on Gitea.
is_valid_framework_repo() {
[[ $1 =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ && $1 != */.. && $1 != ../* && $1 != ./* && $1 != */. ]]
}
is_valid_port() {
[[ $1 =~ ^[0-9]{1,5}$ ]] && ((10#$1 >= 1 && 10#$1 <= 65535))
}