Merge pull request 'Implement MIL-006: project license in config.env; AGPL-3.0 default only for a public GitHub project' (#52) from mil-006-license into mil-006-plan

Reviewed-on: #52
This commit was merged in pull request #52.
This commit is contained in:
2026-10-07 08:18:47 +02:00
23 changed files with 682 additions and 62 deletions
+82 -10
View File
@@ -59,13 +59,73 @@ src/create-project.sh --help
Nothing has to be installed system-wide: the script runs from the checkout and Nothing has to be installed system-wide: the script runs from the checkout and
loads its own files from `src/lib/`. loads its own files from `src/lib/`.
### Run it from the folder where the project is to be created
The new project is created under the folder you start the script in: the
default directory is `./<repository name>`. Go to the folder that should hold
the project, then start the script from there, by its path or by a global
command (below):
```bash
cd ~/work # the folder that will hold my-app
~/src/RepoFoundry/src/create-project.sh # creates ~/work/my-app
```
### Make it a global command
Put a link to the script in a folder that is on your `PATH`. The script
follows the link to the checkout, so it still finds its own files there.
Linux, macOS and Git Bash on Windows (run this once, from the checkout):
```bash
mkdir -p ~/bin
ln -s "$PWD/src/create-project.sh" ~/bin/repo-foundry
```
If `~/bin` is not on your `PATH` yet, add it and open a new shell:
```bash
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc # ~/.zshrc on macOS
```
Check that it works, from any folder:
```bash
cd ~/work
repo-foundry --version # prints the name and version
repo-foundry # a dry run that creates nothing
```
On Windows, Git Bash makes a *copy* instead of a link unless symbolic links
are allowed (Developer Mode, or an administrator shell). Either allow them and
run `export MSYS=winsymlinks:nativestrict` before the `ln -s`, or use an alias
in `~/.bashrc`, which works because the script finds its own folder:
```bash
alias repo-foundry='bash /c/Users/me/RepoFoundry/src/create-project.sh'
```
## Configuration ## Configuration
The script reads two plain files from the project root. They are **parsed, The script reads two plain files. They are **parsed,
never executed** (`source` is not used): only `KEY=VALUE` lines with known keys never executed** (`source` is not used): only `KEY=VALUE` lines with known keys
are accepted, and anything else stops the run with a message that names the key are accepted, and anything else stops the run with a message that names the key
and the line, never the value. and the line, never the value.
Each file is chosen on its own, in this order:
1. the file named with `--config` or `--env`;
2. `./config.env` or `./.env` in the folder you start the script in;
3. `config.env` or `.env` in the checkout (next to `src/`).
The script names the files it uses before it contacts any host. A file taken
from the folder you started in is also confirmed: the script shows the file
names and the Gitea address and asks for a yes (default no) before the first
request, because a `config.env` in a folder you do not control could point
Gitea at another host and so send your token there. A file you name with
`--config` or `--env`, or the checkout's own, is not asked about.
```bash ```bash
cp config.env.example config.env # service addresses, not secret: set GITEA_URL (and GITEA_API_URL) cp config.env.example config.env # service addresses, not secret: set GITEA_URL (and GITEA_API_URL)
cp .env.example .env # credentials: keep private cp .env.example .env # credentials: keep private
@@ -103,6 +163,7 @@ A detail that is set is used and not asked; the summary marks it with
| `GITHUB_OWNER` | GitHub user or organization | letters, digits, `-`; used only when GitHub is used | | `GITHUB_OWNER` | GitHub user or organization | letters, digits, `-`; used only when GitHub is used |
| `PROJECT_DIRECTORY` | local directory | not empty, not starting with `-` | | `PROJECT_DIRECTORY` | local directory | not empty, not starting with `-` |
| `ENABLE_PLAN_GATE` | enable the plan gate | `yes` or `no` | | `ENABLE_PLAN_GATE` | enable the plan gate | `yes` or `no` |
| `PROJECT_LICENSE` | license of the project | a Gitea license key (letters, digits, `.`, `+`, `-`; at most 64), such as `AGPL-3.0` or `MIT`, or `none` |
- A key that is present counts as set, even when its value is empty. Only - A key that is present counts as set, even when its value is empty. Only
`PROJECT_DESCRIPTION` may be empty (no description); an empty value for any `PROJECT_DESCRIPTION` may be empty (no description); an empty value for any
@@ -111,7 +172,12 @@ A detail that is set is used and not asked; the summary marks it with
key. The script never falls back to asking for it. key. The script never falls back to asking for it.
- `USE_GITHUB=no` skips the GitHub owner and every GitHub step; a - `USE_GITHUB=no` skips the GitHub owner and every GitHub step; a
`GITHUB_OWNER` set at the same time is ignored, with a warning. `GITHUB_OWNER` set at the same time is ignored, with a warning.
- Only these eight details can be set. The confirmations stay questions that - `PROJECT_LICENSE` is never asked. When set, that license is put on the Gitea
repository with or without GitHub, and `none` means no license. When absent,
AGPL-3.0 is applied only if GitHub is used **and** the project is public;
a private project, or one without GitHub, gets no license. The Gitea server
must offer the license, or the run stops before anything is created.
- Only these nine details can be set. The confirmations stay questions that
default to no: create now, reusing an existing repository, an existing default to no: create now, reusing an existing repository, an existing
directory, `core.hooksPath` and replacing a template file. directory, `core.hooksPath` and replacing a template file.
- These keys are accepted in `config.env` only, never in `.env`. - These keys are accepted in `config.env` only, never in `.env`.
@@ -119,7 +185,7 @@ A detail that is set is used and not asked; the summary marks it with
value there. Put a description that contains ` #` in double quotes, for value there. Put a description that contains ` #` in double quotes, for
example `PROJECT_DESCRIPTION="Tool for #mirrors"`. example `PROJECT_DESCRIPTION="Tool for #mirrors"`.
With all eight set, a run asks only the confirmations: With all of them set, a run asks only the confirmations:
```bash ```bash
src/create-project.sh --apply # asks only "Create these now (y/n) [n]" src/create-project.sh --apply # asks only "Create these now (y/n) [n]"
@@ -145,6 +211,11 @@ src/create-project.sh --apply # creates everything after a final yes
src/create-project.sh --config /path/to/config.env --env /path/to/.env src/create-project.sh --config /path/to/config.env --env /path/to/.env
``` ```
With a global command (see [Installation](#installation)) the same commands are
`repo-foundry`, `repo-foundry --apply` and so on, started from the folder that
should hold the project. Without `--config` and `--env` the files are looked
for as described under [Configuration](#configuration).
The script asks for, in this order: repository name, description, visibility, The script asks for, in this order: repository name, description, visibility,
Gitea owner, whether to also create a GitHub repository (and its owner), the Gitea owner, whether to also create a GitHub repository (and its owner), the
local directory and whether to enable the plan gate (a detail set in local directory and whether to enable the plan gate (a detail set in
@@ -153,8 +224,8 @@ with read-only requests and prints a plan:
```text ```text
Plan: Plan:
Gitea repository : create (private) with the AGPL-3.0 license https://git.example.org/Team/my-app Gitea repository : create (public) with the AGPL-3.0 license (default: GitHub and a public project) https://git.example.org/Team/my-app
GitHub repository : create (private), empty https://github.com/acme/my-app GitHub repository : create (public), empty https://github.com/acme/my-app
Push mirror : Gitea -> GitHub every 10m0s Push mirror : Gitea -> GitHub every 10m0s
Local project : create ./my-app (new directory), git on main, no commit Local project : create ./my-app (new directory), git on main, no commit
Local origin : will use SSH (the SSH test passed) Local origin : will use SSH (the SSH test passed)
@@ -167,7 +238,7 @@ Without `--apply` that is all that happens. With `--apply` the script asks
"Create these now" (default no) and then creates, in this order: "Create these now" (default no) and then creates, in this order:
1. the GitHub repository (empty), if chosen; 1. the GitHub repository (empty), if chosen;
2. the Gitea repository (with the AGPL-3.0 license if GitHub was chosen); 2. the Gitea repository (with the license that applies: `PROJECT_LICENSE`, or AGPL-3.0 for a public project with GitHub);
3. the push mirror Gitea -> GitHub, and a request for its first sync; 3. the push mirror Gitea -> GitHub, and a request for its first sync;
4. the local directory, `git init` on `main`, the `origin` remote (and `github` 4. the local directory, `git init` on `main`, the `origin` remote (and `github`
if chosen), and, if the Gitea repository holds the license commit, that if chosen), and, if the Gitea repository holds the license commit, that
@@ -181,10 +252,11 @@ hooks refuse commits on `main`.
### Choices ### Choices
- **GitHub or not.** Choosing GitHub also applies the AGPL-3.0 license to the - **GitHub or not.** Choosing GitHub sets up the mirror. For a public project it
Gitea repository (so it is not empty) and sets up the mirror. Without GitHub also applies the AGPL-3.0 license to the Gitea repository (so it is not
the Gitea repository is empty and has no license, and `GITHUB_PAT` is not empty), unless `PROJECT_LICENSE` says otherwise. A private project with
needed. GitHub gets no license by default. Without GitHub the Gitea repository has
no license unless `PROJECT_LICENSE` sets one, and `GITHUB_PAT` is not needed.
- **Owners.** The Gitea owner and the GitHub owner are chosen separately and - **Owners.** The Gitea owner and the GitHub owner are chosen separately and
may be a user or an organization. `GITHUB_USER` is only the suggested default may be a user or an organization. `GITHUB_USER` is only the suggested default
for the GitHub owner prompt; it identifies who authenticates. for the GitHub owner prompt; it identifies who authenticates.
+6
View File
@@ -46,6 +46,12 @@ GITEA_API_URL=https://<your gitea instance>/api/v1
#PROJECT_DIRECTORY=./my-project #PROJECT_DIRECTORY=./my-project
#ENABLE_PLAN_GATE=no # yes or no #ENABLE_PLAN_GATE=no # yes or no
# Optional. The license of the project, as a Gitea license key (AGPL-3.0, MIT,
# ...), or none for no license. It is never asked. When absent, AGPL-3.0 is
# applied only if GitHub is used and the project is public; otherwise there
# is no license. The Gitea server must offer the license.
#PROJECT_LICENSE=AGPL-3.0
# Optional. OWNER/NAME of the SQA-QC-Framework repository on the Gitea server; # Optional. OWNER/NAME of the SQA-QC-Framework repository on the Gitea server;
# it is added to the new project as a submodule over SSH. # it is added to the new project as a submodule over SSH.
# Default: TirSystem/SQA-QC-Framework. # Default: TirSystem/SQA-QC-Framework.
+2 -2
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | AGPL-3.0 default only when GitHub is chosen and the project is public (purpose, resolution order, criterion 3, tasks 1 and 4) | [1cd27f7] | | 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | AGPL-3.0 default only when GitHub is chosen and the project is public (purpose, resolution order, criterion 3, tasks 1 and 4) | [1cd27f7] |
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | | 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] |
--- ---
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | | 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] |
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | [0ab5006] | | 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | [0ab5006] |
--- ---
+34 -11
View File
@@ -10,8 +10,9 @@
# repositories and the mirror, then the local project: its directory, git # repositories and the mirror, then the local project: its directory, git
# repository, remotes (no credential in any address), the framework as a # repository, remotes (no credential in any address), the framework as a
# submodule, the framework's skills and git hooks (and the plan gate if # 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 # chosen) and its templates. The license of the Gitea repository is
# license to the Gitea repository. No commit is made in the new project. # PROJECT_LICENSE in config.env (none means no license); without it AGPL-3.0
# applies only when GitHub is chosen and the project is public. No commit is made in the new project.
# #
# Dry run by default # Dry run by default
# Without --apply the script only reads from GitHub and Gitea (GET # Without --apply the script only reads from GitHub and Gitea (GET
@@ -85,18 +86,39 @@ if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4)));
fi fi
# Where this script lives; the library files and the project root are found # Where this script lives; the library files and the project root are found
# from here, never from the current directory. # from here, never from the current directory. A link to the script (a
readonly SCRIPT_FILE="${BASH_SOURCE[0]}" # command in a folder on PATH) is followed to the real file, however many
case "${BASH_SOURCE[0]}" in # links lie on the way; readlink without -f exists on Linux, macOS and Git
*/*) script_path_dir="${BASH_SOURCE[0]%/*}" ;; # Bash alike.
script_path="${BASH_SOURCE[0]}"
script_link_count=0
while [[ -L $script_path ]]; do
((++script_link_count <= 40)) || {
printf 'error: too many links when following %s\n' "${BASH_SOURCE[0]}" >&2
exit 1
}
script_link_target="$(readlink -- "$script_path")"
case "$script_link_target" in
/*) script_path="$script_link_target" ;;
*)
case "$script_path" in
*/*) script_path="${script_path%/*}/$script_link_target" ;;
*) script_path="$script_link_target" ;;
esac
;;
esac
done
readonly SCRIPT_FILE="$script_path"
case "$script_path" in
*/*) script_path_dir="${script_path%/*}" ;;
*) script_path_dir="." ;; *) script_path_dir="." ;;
esac esac
SCRIPT_DIR="$(cd "$script_path_dir" && pwd)" SCRIPT_DIR="$(cd -P "$script_path_dir" && pwd -P)"
readonly SCRIPT_DIR readonly SCRIPT_DIR
unset script_path_dir unset script_path script_path_dir script_link_count script_link_target
# The script lives in src/; the configuration files live one level up, in # The script lives in src/; the checkout is one level up, next to
# the project root, next to config.env.example and .env.example. # config.env.example and .env.example.
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd -P "$SCRIPT_DIR/.." && pwd -P)"
readonly PROJECT_ROOT readonly PROJECT_ROOT
# shellcheck source=lib/constants.sh # shellcheck source=lib/constants.sh
@@ -162,6 +184,7 @@ main() {
trap finish EXIT trap finish EXIT
is_valid_repo_name "$PROJECT_NAME" || is_valid_repo_name "$PROJECT_NAME" ||
die "REPOFOUNDRY_NAME is not a valid project name" die "REPOFOUNDRY_NAME is not a valid project name"
WORKING_FOLDER="$(pwd -P)"
parse_args "$@" parse_args "$@"
check_tools check_tools
setup_temp_dir setup_temp_dir
+2 -2
View File
@@ -47,9 +47,9 @@ confirm_reuse() {
die "stopped: choose another name or remove the existing repository" die "stopped: choose another name or remove the existing repository"
fi fi
done done
if ((${STATE[reuse_gitea]:-0})) && ((PROJECT[has_github])) && if ((${STATE[reuse_gitea]:-0})) && [[ -n ${PROJECT[license]} ]] &&
[[ ${STATE[gitea_repo]} == empty ]]; then [[ ${STATE[gitea_repo]} == empty ]]; then
warn "the empty Gitea repository is reused as it is: the $AGPL_LICENSE_KEY license is not added to it" warn "the empty Gitea repository is reused as it is: the ${PROJECT[license]} license is not added to it"
fi fi
} }
+2
View File
@@ -9,6 +9,8 @@
usage() { usage() {
cat <<EOF cat <<EOF
Usage: ${0##*/} [--apply] [--config FILE] [--env FILE] Usage: ${0##*/} [--apply] [--config FILE] [--env FILE]
Without --config and --env, ./config.env and ./.env in the current folder
are read, then the ones in the checkout.
${0##*/} --help | --version ${0##*/} --help | --version
EOF EOF
} }
+48
View File
@@ -142,6 +142,7 @@ validate_project_presets() {
check_preset GITHUB_OWNER is_valid_github_owner "$HINT_GITHUB_OWNER" check_preset GITHUB_OWNER is_valid_github_owner "$HINT_GITHUB_OWNER"
check_preset PROJECT_DIRECTORY is_valid_directory "$HINT_DIRECTORY" check_preset PROJECT_DIRECTORY is_valid_directory "$HINT_DIRECTORY"
check_preset_choice ENABLE_PLAN_GATE yes no check_preset_choice ENABLE_PLAN_GATE yes no
check_preset PROJECT_LICENSE is_valid_license "$HINT_LICENSE"
} }
validate_credentials() { validate_credentials() {
@@ -197,9 +198,56 @@ warn_if_env_unsafe() {
fi fi
} }
# locate_config_file NAME FLAG FILE_VAR ORIGIN_VAR: the file named with FLAG;
# otherwise ./NAME in the working folder; otherwise NAME in the checkout.
# The origin is "named", "folder" or "checkout".
locate_config_file() {
local name="$1" flag="$2"
local -n file_ref="$3" origin_ref="$4"
if [[ -n $file_ref ]]; then
origin_ref="named"
elif [[ $WORKING_FOLDER != "$PROJECT_ROOT" && -f $WORKING_FOLDER/$name ]]; then
file_ref="$WORKING_FOLDER/$name"
origin_ref="folder"
elif [[ -f $PROJECT_ROOT/$name ]]; then
file_ref="$PROJECT_ROOT/$name"
origin_ref="checkout"
else
die "$name is not named with $flag and is in neither the working folder ($WORKING_FOLDER) nor the checkout ($PROJECT_ROOT); name it with $flag FILE"
fi
}
# origin_words ORIGIN: how the origin of a configuration file reads.
origin_words() {
case "$1" in
named) printf 'named on the command line' ;;
folder) printf 'from the working folder' ;;
*) printf 'from the checkout' ;;
esac
}
# confirm_folder_files: a file taken from the working folder may point the
# Gitea address elsewhere and so send the token there, so it is named with
# the address and needs a yes (default no) before the first request.
confirm_folder_files() {
local files=()
[[ $CONFIG_ORIGIN == folder ]] && files+=("$CONFIG_FILE")
[[ $ENV_ORIGIN == folder ]] && files+=("$ENV_FILE")
((${#files[@]} > 0)) || return 0
say "The working folder supplies: ${files[*]}"
say "Gitea would be ${CONFIG[GITEA_URL]}; the token from the credentials file is sent there."
prompt_yes_no "Use ${files[*]}" n
((REPLY)) || die "stopped before any request: choose the files with --config and --env, or run from the checkout"
}
load_configuration() { load_configuration() {
locate_config_file config.env --config CONFIG_FILE CONFIG_ORIGIN
locate_config_file .env --env ENV_FILE ENV_ORIGIN
say "Config file : $CONFIG_FILE ($(origin_words "$CONFIG_ORIGIN"))"
say "Credentials file: $ENV_FILE ($(origin_words "$ENV_ORIGIN"))"
parse_env_file "$CONFIG_FILE" CONFIG_KEYS CONFIG parse_env_file "$CONFIG_FILE" CONFIG_KEYS CONFIG
validate_config validate_config
confirm_folder_files
parse_env_file "$ENV_FILE" CREDENTIAL_KEYS CREDENTIALS parse_env_file "$ENV_FILE" CREDENTIAL_KEYS CREDENTIALS
validate_credentials validate_credentials
warn_if_env_unsafe "$ENV_FILE" warn_if_env_unsafe "$ENV_FILE"
+11 -3
View File
@@ -18,6 +18,7 @@ readonly DEFAULT_MIRROR_INTERVAL="10m0s"
readonly DEFAULT_SSH_PORT=10022 readonly DEFAULT_SSH_PORT=10022
readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework" readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework"
readonly AGPL_LICENSE_KEY="AGPL-3.0" readonly AGPL_LICENSE_KEY="AGPL-3.0"
readonly NO_LICENSE_WORD="none"
readonly DEFAULT_BRANCH="main" readonly DEFAULT_BRANCH="main"
# What the prompts and the preset keys in config.env both tell the Maintainer # What the prompts and the preset keys in config.env both tell the Maintainer
# when a value is refused. # when a value is refused.
@@ -25,6 +26,7 @@ readonly HINT_REPO_NAME="use letters, digits, '.', '_' or '-' (at most 100), not
readonly HINT_DESCRIPTION="at most $MAX_DESCRIPTION_LENGTH characters and no control characters" readonly HINT_DESCRIPTION="at most $MAX_DESCRIPTION_LENGTH characters and no control characters"
readonly HINT_GITEA_OWNER="use letters, digits, '.', '_' or '-' (at most 39)" readonly HINT_GITEA_OWNER="use letters, digits, '.', '_' or '-' (at most 39)"
readonly HINT_GITHUB_OWNER="use letters, digits or '-' (at most 39)" readonly HINT_GITHUB_OWNER="use letters, digits or '-' (at most 39)"
readonly HINT_LICENSE="use a Gitea license key (letters, digits, '.', '+' or '-', at most 64), such as AGPL-3.0 or MIT, or none"
readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters" readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters"
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") "Local project" "Framework" "Skills and hooks" "Templates")
@@ -32,11 +34,17 @@ readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror"
readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL
GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO
PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB
GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE) GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE PROJECT_LICENSE)
readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN) readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN)
CONFIG_FILE="$PROJECT_ROOT/config.env" # The configuration files: named by --config and --env, or chosen by
ENV_FILE="$PROJECT_ROOT/.env" # locate_config_file. The origin is named, folder or checkout.
CONFIG_FILE=""
ENV_FILE=""
CONFIG_ORIGIN=""
ENV_ORIGIN=""
# The folder the Maintainer started the script in.
WORKING_FOLDER=""
TMP_DIR="" TMP_DIR=""
HAS_JQ=0 HAS_JQ=0
HTTP_STATUS=0 HTTP_STATUS=0
+15 -1
View File
@@ -4,7 +4,7 @@
# #
# Part of create-project.sh: sourced by it, never run on its own. # 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 # Provides: is_framework_skipped, init_framework_submodules, 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 # is_framework_skipped: succeed when the framework steps are left out because
# SSH to Gitea is not available (the Maintainer agreed to that). # SSH to Gitea is not available (the Maintainer agreed to that).
@@ -12,6 +12,18 @@ is_framework_skipped() {
[[ ${STATE[skip_framework]:-0} == 1 ]] [[ ${STATE[skip_framework]:-0} == 1 ]]
} }
# init_framework_submodules DIR: fetch the submodules the framework holds
# itself (the qc checklists). Without a submodule of its own this changes
# nothing. A failure names the command to run by hand.
init_framework_submodules() {
local dir="$1" err
make_temp_file
err="$REPLY"
if ! git_project "$dir" submodule update -q --init --recursive 2>"$err"; then
die "the framework was added but git could not fetch its own submodules (the qc checklists). Run in $dir: git submodule update --init --recursive. Git said: $(head -n 2 "$err" | tr '\n' ' ')"
fi
}
# add_framework: git submodule add of the framework as "framework". SSH is # add_framework: git submodule add of the framework as "framework". SSH is
# needed for it; a failure says how to test the access. # needed for it; a failure says how to test the access.
add_framework() { add_framework() {
@@ -27,6 +39,7 @@ add_framework() {
if [[ $existing != "$url" ]]; then if [[ $existing != "$url" ]]; then
die "'framework' already exists in $dir and is not the framework submodule ($url)" die "'framework' already exists in $dir and is not the framework submodule ($url)"
fi fi
init_framework_submodules "$dir"
finish_step "$label" "reused" "$url (already a submodule)" finish_step "$label" "reused" "$url (already a submodule)"
return 0 return 0
fi fi
@@ -35,6 +48,7 @@ add_framework() {
if ! git_project "$dir" submodule add -q "$url" framework 2>"$err"; then 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' ' ')" 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 fi
init_framework_submodules "$dir"
finish_step "$label" "created" "$url" finish_step "$label" "created" "$url"
} }
+3 -1
View File
@@ -72,7 +72,7 @@ checkout_gitea_history() {
} }
# create_local_project: the directory, the git repository on main, the # create_local_project: the directory, the git repository on main, the
# remotes and, when GitHub is chosen, the license history. No commit is made. # remotes and, when a license applies, the license history. No commit is made.
create_local_project() { create_local_project() {
local label="Local project" dir="${PROJECT[directory]}" local label="Local project" dir="${PROJECT[directory]}"
begin_step "$label" begin_step "$label"
@@ -84,6 +84,8 @@ create_local_project() {
ensure_remote "$dir" origin "$(origin_url)" ensure_remote "$dir" origin "$(origin_url)"
if ((PROJECT[has_github])); then if ((PROJECT[has_github])); then
ensure_remote "$dir" github "$(github_remote_url)" ensure_remote "$dir" github "$(github_remote_url)"
fi
if [[ -n ${PROJECT[license]} ]]; then
checkout_gitea_history "$dir" checkout_gitea_history "$dir"
fi fi
finish_step "$label" "created" "$dir (origin over $(origin_protocol))" finish_step "$label" "created" "$dir (origin over $(origin_protocol))"
+2 -2
View File
@@ -22,8 +22,8 @@ print_plan() {
say "Plan:" say "Plan:"
if ((STATE[reuse_gitea])); then if ((STATE[reuse_gitea])); then
gitea_action="reuse the existing repository (you will be asked to confirm)" gitea_action="reuse the existing repository (you will be asked to confirm)"
elif ((PROJECT[has_github])); then elif [[ -n ${PROJECT[license]} ]]; then
gitea_action="create (${PROJECT[visibility]}) with the $AGPL_LICENSE_KEY license" gitea_action="create (${PROJECT[visibility]}) with the ${PROJECT[license]} license$(license_note)"
else else
gitea_action="create (${PROJECT[visibility]}), empty" gitea_action="create (${PROJECT[visibility]}), empty"
fi fi
+4 -4
View File
@@ -23,8 +23,8 @@ check_gitea_organization() {
check_gitea_license() { check_gitea_license() {
api_call gitea GET /licenses api_call gitea GET /licenses
expect_status "cannot list the licenses of the Gitea server" 200 expect_status "cannot list the licenses of the Gitea server" 200
json_has_value "$HTTP_BODY_FILE" key "$AGPL_LICENSE_KEY" || json_has_value "$HTTP_BODY_FILE" key "${PROJECT[license]}" ||
die "the Gitea server does not offer the $AGPL_LICENSE_KEY license" die "the Gitea server does not offer the ${PROJECT[license]} license"
} }
# inspect_repository HOST: record in STATE[HOST_repo] whether the repository # inspect_repository HOST: record in STATE[HOST_repo] whether the repository
@@ -70,7 +70,7 @@ preflight_gitea() {
check_gitea_organization "$owner" "$login" check_gitea_organization "$owner" "$login"
STATE[gitea_owner_kind]="organization" STATE[gitea_owner_kind]="organization"
fi fi
if ((PROJECT[has_github])); then if [[ -n ${PROJECT[license]} ]]; then
check_gitea_license check_gitea_license
fi fi
inspect_repository gitea inspect_repository gitea
@@ -143,7 +143,7 @@ decide_existing_repositories() {
free) ;; free) ;;
empty) STATE[reuse_$host]=1 ;; empty) STATE[reuse_$host]=1 ;;
initial_only) initial_only)
if [[ $host == gitea ]] && ((PROJECT[has_github])); then if [[ $host == gitea && -n ${PROJECT[license]} ]]; then
STATE[reuse_$host]=1 STATE[reuse_$host]=1
else else
die "the $(host_label "$host") repository $owner/${PROJECT[name]} already exists and has content; choose another name or remove it first" die "the $(host_label "$host") repository $owner/${PROJECT[name]} already exists and has content; choose another name or remove it first"
+32 -3
View File
@@ -4,7 +4,7 @@
# #
# Part of create-project.sh: sourced by it, never run on its own. # Part of create-project.sh: sourced by it, never run on its own.
# #
# Provides: preset_detail, collect_project_details, collect_github_details, source_note, yes_no, credential_state, print_summary # Provides: preset_detail, resolve_license, license_note, collect_project_details, collect_github_details, source_note, yes_no, credential_state, print_summary
# preset_detail KEY NAME: a detail set in config.env is used and not asked; # preset_detail KEY NAME: a detail set in config.env is used and not asked;
# the value is returned in REPLY and marked in PRESET[NAME]. # the value is returned in REPLY and marked in PRESET[NAME].
@@ -14,6 +14,33 @@ preset_detail() {
PRESET[$2]=1 PRESET[$2]=1
} }
# resolve_license: the license that applies, in PROJECT[license] (empty means
# none). PROJECT_LICENSE in config.env decides, and "none" means no license;
# without it AGPL-3.0 applies only when GitHub is chosen and the project is
# public. The license is never asked.
resolve_license() {
PROJECT[license]=""
if [[ -n ${CONFIG[PROJECT_LICENSE]+set} ]]; then
PRESET[license]=1
if [[ ${CONFIG[PROJECT_LICENSE],,} != "$NO_LICENSE_WORD" ]]; then
PROJECT[license]="${CONFIG[PROJECT_LICENSE]}"
fi
elif ((PROJECT[has_github])) && [[ ${PROJECT[visibility]} == public ]]; then
PROJECT[license]="$AGPL_LICENSE_KEY"
fi
}
# license_note: where the license on the Gitea repository comes from.
license_note() {
if [[ -n ${PRESET[license]:-} ]]; then
source_note license
elif [[ -n ${PROJECT[license]} ]]; then
printf ' (default: GitHub and a public project)'
elif ((PROJECT[has_github])); then
printf ' (no default for a private project)'
fi
}
# Ask for each project detail, except those set in config.env. # Ask for each project detail, except those set in config.env.
collect_project_details() { collect_project_details() {
preset_detail PROJECT_NAME name || preset_detail PROJECT_NAME name ||
@@ -29,6 +56,7 @@ collect_project_details() {
prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner "$HINT_GITEA_OWNER" prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner "$HINT_GITEA_OWNER"
PROJECT[gitea_owner]="$REPLY" PROJECT[gitea_owner]="$REPLY"
collect_github_details collect_github_details
resolve_license
preset_detail PROJECT_DIRECTORY directory || preset_detail PROJECT_DIRECTORY directory ||
prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory "$HINT_DIRECTORY" prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory "$HINT_DIRECTORY"
PROJECT[directory]="$REPLY" PROJECT[directory]="$REPLY"
@@ -46,7 +74,7 @@ collect_github_details() {
if preset_detail USE_GITHUB has_github; then if preset_detail USE_GITHUB has_github; then
[[ $REPLY == yes ]] && REPLY=1 || REPLY=0 [[ $REPLY == yes ]] && REPLY=1 || REPLY=0
else else
prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y prompt_yes_no "Also create a GitHub repository" y
fi fi
PROJECT[has_github]="$REPLY" PROJECT[has_github]="$REPLY"
PROJECT[github_owner]="" PROJECT[github_owner]=""
@@ -90,10 +118,11 @@ print_summary() {
say " Description : ${PROJECT[description]:-(none)}$(source_note description)" say " Description : ${PROJECT[description]:-(none)}$(source_note description)"
say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}$(source_note gitea_owner)" say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}$(source_note gitea_owner)"
if ((PROJECT[has_github])); then if ((PROJECT[has_github])); then
say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)$(source_note github_owner)" say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]}$(source_note github_owner)"
else else
say " GitHub : not used$(source_note has_github)" say " GitHub : not used$(source_note has_github)"
fi fi
say " License : ${PROJECT[license]:-none}$(license_note)"
say " Directory : ${PROJECT[directory]}$(source_note directory)" say " Directory : ${PROJECT[directory]}$(source_note directory)"
say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")$(source_note is_plan_gate_enabled)" say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")$(source_note is_plan_gate_enabled)"
say "Credentials : GITEA_TOKEN $(credential_state GITEA_TOKEN)," \ say "Credentials : GITEA_TOKEN $(credential_state GITEA_TOKEN)," \
+2 -2
View File
@@ -18,8 +18,8 @@ repo_body() {
"${PROJECT[name]}" "$description" "$private" "${PROJECT[name]}" "$description" "$private"
return 0 return 0
fi fi
if ((PROJECT[has_github])); then if [[ -n ${PROJECT[license]} ]]; then
extra=',"auto_init":true,"license":"'"$AGPL_LICENSE_KEY"'"' extra=',"auto_init":true,"license":"'"${PROJECT[license]}"'"'
else else
extra=',"auto_init":false' extra=',"auto_init":false'
fi fi
+5
View File
@@ -58,6 +58,11 @@ is_valid_port() {
[[ $1 =~ ^[0-9]{1,5}$ ]] && ((10#$1 >= 1 && 10#$1 <= 65535)) [[ $1 =~ ^[0-9]{1,5}$ ]] && ((10#$1 >= 1 && 10#$1 <= 65535))
} }
# A Gitea license key such as AGPL-3.0 or MIT, or the word none.
is_valid_license() {
[[ -n $1 && ${#1} -le 64 && $1 =~ ^[A-Za-z0-9.+-]+$ ]]
}
# A Go duration such as 10m0s or 8h0m0s, the form Gitea expects. # A Go duration such as 10m0s or 8h0m0s, the form Gitea expects.
is_valid_interval() { is_valid_interval() {
[[ -n $1 && $1 =~ ^([0-9]+h)?([0-9]+m)?([0-9]+s)?$ ]] [[ -n $1 && $1 =~ ^([0-9]+h)?([0-9]+m)?([0-9]+s)?$ ]]
+16 -2
View File
@@ -21,6 +21,16 @@ readonly FAKE_GITHUB_PAT="ghpFAKEtoken1234567890"
# Answers to the prompts: name, description, visibility, Gitea owner, GitHub # Answers to the prompts: name, description, visibility, Gitea owner, GitHub
# yes or no, GitHub owner, directory, plan gate. # yes or no, GitHub owner, directory, plan gate.
readonly ANSWERS_GITHUB=$'my-app\nA test app\n\nTirSystem\ny\nacme-org\n\nn\n' readonly ANSWERS_GITHUB=$'my-app\nA test app\n\nTirSystem\ny\nacme-org\n\nn\n'
# The same, for a public project (the AGPL-3.0 default applies with GitHub).
readonly ANSWERS_GITHUB_PUBLIC=$'my-app
A test app
public
TirSystem
y
acme-org
n
'
readonly ANSWERS_GITEA_ONLY=$'my-app\n\n\nTirSystem\nn\n\nn\n' readonly ANSWERS_GITEA_ONLY=$'my-app\n\n\nTirSystem\nn\n\nn\n'
SHARED_REMOTES="" SHARED_REMOTES=""
@@ -110,12 +120,15 @@ write_gitconfig() {
insteadOf = https://git.example.test/ insteadOf = https://git.example.test/
[url "file://$WORK/remote/"] [url "file://$WORK/remote/"]
insteadOf = ssh://git@git.example.test:10022/ insteadOf = ssh://git@git.example.test:10022/
[url "file://$WORK/remote/"]
insteadOf = ssh://git@git.tirsystem.com:10022/
EOF EOF
} }
# ensure_shared_remotes: build, once per run of the suite, the local bare # ensure_shared_remotes: build, once per run of the suite, the local bare
# repositories that stand in for Gitea (TirSystem/my-app.git, holding the # repositories that stand in for Gitea (TirSystem/my-app.git, holding the
# license commit) and for the framework (a copy of the real one). # license commit), for the framework and for the checklists the framework
# holds as its own submodule (copies of the real ones).
ensure_shared_remotes() { ensure_shared_remotes() {
if [[ -n $SHARED_REMOTES && -d $SHARED_REMOTES ]]; then if [[ -n $SHARED_REMOTES && -d $SHARED_REMOTES ]]; then
return 0 return 0
@@ -123,6 +136,7 @@ ensure_shared_remotes() {
SHARED_REMOTES="$(mktemp -d "${TMPDIR:-/tmp}/repofoundry-remotes.XXXXXX")" SHARED_REMOTES="$(mktemp -d "${TMPDIR:-/tmp}/repofoundry-remotes.XXXXXX")"
mkdir -p "$SHARED_REMOTES/TirSystem" mkdir -p "$SHARED_REMOTES/TirSystem"
git clone -q --bare "$REPO_ROOT/framework" "$SHARED_REMOTES/TirSystem/SQA-QC-Framework.git" git clone -q --bare "$REPO_ROOT/framework" "$SHARED_REMOTES/TirSystem/SQA-QC-Framework.git"
git clone -q --bare "$REPO_ROOT/framework/qc" "$SHARED_REMOTES/TirSystem/SQA-QC-Checklists.git"
git init -q --bare "$SHARED_REMOTES/TirSystem/my-app.git" git init -q --bare "$SHARED_REMOTES/TirSystem/my-app.git"
git init -q "$SHARED_REMOTES/seed" git init -q "$SHARED_REMOTES/seed"
git -C "$SHARED_REMOTES/seed" symbolic-ref HEAD refs/heads/main git -C "$SHARED_REMOTES/seed" symbolic-ref HEAD refs/heads/main
@@ -153,7 +167,7 @@ setup_local_remotes() {
# first, then the now empty directories from the bottom up. # first, then the now empty directories from the bottom up.
remove_workdir() { remove_workdir() {
if [[ -n $WORK && -d $WORK ]]; then if [[ -n $WORK && -d $WORK ]]; then
find "$WORK" \( -type f -o -type p \) -delete find "$WORK" \( -type f -o -type p -o -type l \) -delete
find "$WORK" -depth -type d -exec rmdir {} + find "$WORK" -depth -type d -exec rmdir {} +
fi fi
WORK="" WORK=""
+23 -9
View File
@@ -31,7 +31,7 @@ test_dry_run_prints_the_plan_and_only_reads() {
setup_hosts setup_hosts
run_dry "$ANSWERS_GITHUB" run_dry "$ANSWERS_GITHUB"
assert_status "dry run" 0 "$STATUS" assert_status "dry run" 0 "$STATUS"
assert_contains "Gitea plan" "$OUT" "Gitea repository : create (private) with the AGPL-3.0 license https://git.example.test/TirSystem/my-app" assert_contains "Gitea plan" "$OUT" "Gitea repository : create (private), empty https://git.example.test/TirSystem/my-app"
assert_contains "GitHub plan" "$OUT" "GitHub repository : create (private), empty https://github.com/acme-org/my-app" assert_contains "GitHub plan" "$OUT" "GitHub repository : create (private), empty https://github.com/acme-org/my-app"
assert_contains "mirror plan" "$OUT" "Push mirror : Gitea -> GitHub every 10m0s" assert_contains "mirror plan" "$OUT" "Push mirror : Gitea -> GitHub every 10m0s"
assert_contains "origin plan" "$OUT" "Local origin : will use SSH (the SSH test passed)" assert_contains "origin plan" "$OUT" "Local origin : will use SSH (the SSH test passed)"
@@ -148,14 +148,18 @@ test_github_owner_must_be_a_member() {
assert_contains "pending message" "$ERR" "membership of the GitHub organization 'acme-org' is not active" assert_contains "pending message" "$ERR" "membership of the GitHub organization 'acme-org' is not active"
} }
test_license_must_be_offered_when_github_is_chosen() { test_license_must_be_offered_when_a_public_project_has_github() {
setup_hosts setup_hosts
prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]' prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]'
run_apply "$ANSWERS_GITHUB" run_apply "$ANSWERS_GITHUB_PUBLIC"
assert_status "no AGPL" 1 "$STATUS" assert_status "no AGPL" 1 "$STATUS"
assert_contains "message" "$ERR" "does not offer the AGPL-3.0 license" assert_contains "message" "$ERR" "does not offer the AGPL-3.0 license"
assert_not_contains "nothing created" "$(calls)" "POST" assert_not_contains "nothing created" "$(calls)" "POST"
# Without GitHub no license is needed, so the same server is fine. # Without GitHub, or for a private project, no license is needed, so the same server is fine.
setup_hosts
prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]'
run_dry "$ANSWERS_GITHUB"
assert_status "private with GitHub" 0 "$STATUS"
setup_hosts setup_hosts
prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]' prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]'
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env" printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
@@ -215,8 +219,7 @@ test_apply_sends_the_right_request_bodies() {
bodies="$(cat "$WORK/curl.bodies")" bodies="$(cat "$WORK/curl.bodies")"
assert_contains "GitHub name" "$bodies" '"name":"my-app"' assert_contains "GitHub name" "$bodies" '"name":"my-app"'
assert_contains "GitHub is created empty" "$bodies" '"private":true,"auto_init":false}' assert_contains "GitHub is created empty" "$bodies" '"private":true,"auto_init":false}'
assert_contains "Gitea gets the license" "$bodies" '"license":"AGPL-3.0"' assert_not_contains "a private project gets no license" "$bodies" '"license"'
assert_contains "Gitea is initialised with it" "$bodies" '"auto_init":true'
assert_contains "default branch" "$bodies" '"default_branch":"main"' assert_contains "default branch" "$bodies" '"default_branch":"main"'
assert_contains "description" "$bodies" '"description":"A test app"' assert_contains "description" "$bodies" '"description":"A test app"'
assert_contains "mirror target without credentials" "$bodies" '"remote_address":"https://github.com/acme-org/my-app.git"' assert_contains "mirror target without credentials" "$bodies" '"remote_address":"https://github.com/acme-org/my-app.git"'
@@ -239,6 +242,17 @@ test_apply_never_prints_or_passes_a_token() {
assert_contains "GitHub token in the private config" "$(cat "$WORK/curl.config")" "Authorization: Bearer $FAKE_GITHUB_PAT" assert_contains "GitHub token in the private config" "$(cat "$WORK/curl.config")" "Authorization: Bearer $FAKE_GITHUB_PAT"
} }
test_apply_sends_the_license_for_a_public_project_with_github() {
setup_hosts
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\n'
local bodies
bodies="$(cat "$WORK/curl.bodies")"
assert_contains "Gitea gets the license" "$bodies" '"license":"AGPL-3.0"'
assert_contains "Gitea is initialised with it" "$bodies" '"auto_init":true'
assert_contains "public" "$bodies" '"private":false'
assert_contains "plan names the rule" "$OUT" "with the AGPL-3.0 license (default: GitHub and a public project)"
}
test_apply_with_gitea_only() { test_apply_with_gitea_only() {
setup_hosts setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env" printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
@@ -337,7 +351,7 @@ test_gitea_repository_with_only_the_license_can_be_reused() {
setup_hosts setup_hosts
prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}' prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}'
prepend_route 'GET|/api/v1/repos/TirSystem/my-app/contents|200|[{"name":"LICENSE","type":"file","path":"LICENSE"}]' prepend_route 'GET|/api/v1/repos/TirSystem/my-app/contents|200|[{"name":"LICENSE","type":"file","path":"LICENSE"}]'
run_apply "$ANSWERS_GITHUB"$'y\ny\n' run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\ny\n'
assert_status "license only" 0 "$STATUS" assert_status "license only" 0 "$STATUS"
assert_not_contains "Gitea repository not created again" "$(calls)" "$GITEA_REPO_CALL" assert_not_contains "Gitea repository not created again" "$(calls)" "$GITEA_REPO_CALL"
assert_eq "mirror created once" "1" "$(calls | grep -c -x -F "$MIRROR_CALL")" assert_eq "mirror created once" "1" "$(calls | grep -c -x -F "$MIRROR_CALL")"
@@ -355,7 +369,7 @@ test_gitea_repository_with_only_the_license_can_be_reused() {
test_reusing_an_empty_gitea_repository_warns_about_the_license() { test_reusing_an_empty_gitea_repository_warns_about_the_license() {
setup_hosts setup_hosts
prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":true}' prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":true}'
run_apply "$ANSWERS_GITHUB"$'y\ny\n' run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\ny\n'
assert_status "reuse empty Gitea repository" 0 "$STATUS" assert_status "reuse empty Gitea repository" 0 "$STATUS"
assert_contains "warning" "$ERR" "the AGPL-3.0 license is not added to it" assert_contains "warning" "$ERR" "the AGPL-3.0 license is not added to it"
} }
@@ -485,7 +499,7 @@ test_a_repository_this_script_created_earlier_can_be_reused() {
setup_hosts setup_hosts
prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}' prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}'
prepend_route 'GET|/api/v1/repos/TirSystem/my-app/contents|200|[{"name":"README.md","type":"file"},{"name":"LICENSE","type":"file"}]' prepend_route 'GET|/api/v1/repos/TirSystem/my-app/contents|200|[{"name":"README.md","type":"file"},{"name":"LICENSE","type":"file"}]'
run_apply "$ANSWERS_GITHUB"$'y\ny\n' run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\ny\n'
assert_status "LICENSE and README.md" 0 "$STATUS" assert_status "LICENSE and README.md" 0 "$STATUS"
assert_not_contains "not created again" "$(calls)" "$GITEA_REPO_CALL" assert_not_contains "not created again" "$(calls)" "$GITEA_REPO_CALL"
assert_contains "reported" "$OUT" "Gitea repository : reused" assert_contains "reported" "$OUT" "Gitea repository : reused"
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/env bash
# test-launch.sh - tests for MIL-007: the framework's own submodules (qc) are
# fetched, the script starts through a link, and the configuration files are
# --config and --env, else ./config.env and ./.env, else the checkout's.
# Sourced by run-tests.sh.
# shellcheck disable=SC2016,SC2153 # snippet and fixture text is literal on purpose; SCRIPT comes from lib.sh
# make_checkout: a copy of the script's own files with config.env and .env
# beside them, standing in for the checkout; the path is in CHECKOUT.
make_checkout() {
CHECKOUT="$(cd "$WORK" && pwd -P)/checkout"
mkdir -p "$CHECKOUT"
cp -R "$SRC_DIR" "$CHECKOUT/src"
cp "$WORK/config.env" "$CHECKOUT/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$CHECKOUT/.env"
}
# make_folder: an empty working folder; the path is in FOLDER.
make_folder() {
FOLDER="$(cd "$WORK" && pwd -P)/folder"
mkdir -p "$FOLDER"
}
# run_from SCRIPT_PATH DIR INPUT ARGS...: run the script with DIR as the
# current folder.
run_from() {
local script="$1" dir="$2" input="$3"
shift 3
STATUS=0
(cd "$dir" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" \
REPOFOUNDRY_SYNC_WAIT=0 GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 \
"$BASH" "$script" "$@" <<<"$input" >"$WORK/out.txt" 2>"$WORK/err.txt") ||
STATUS=$?
OUT="$(cat "$WORK/out.txt")"
ERR="$(cat "$WORK/err.txt")"
}
# --------------------------------------------------- the framework's qc
test_the_framework_checklists_are_fetched() {
setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
local dir="$WORK/project" answers
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
run_apply "$answers"$'y\n'
assert_status "apply" 0 "$STATUS"
assert_file_exists "the framework" "$dir/framework/README.md"
assert_file_exists "qc is filled" "$dir/framework/qc/qc-business-case.md"
assert_eq "no submodule is left uninitialised" "" "$(GIT_CONFIG_GLOBAL="$WORK/gitconfig" git -C "$dir" submodule status --recursive | grep '^-' || true)"
}
test_an_empty_qc_is_filled_by_a_second_run_and_a_complete_one_is_not_changed() {
setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
local dir="$WORK/project" answers
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
run_apply "$answers"$'y\n'
assert_status "first run" 0 "$STATUS"
git_in() { GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" "$@"; }
git_in submodule deinit -f qc >/dev/null 2>&1
assert_file_missing "qc emptied" "$dir/framework/qc/qc-business-case.md"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
assert_status "repair" 0 "$STATUS"
assert_file_exists "qc filled again" "$dir/framework/qc/qc-business-case.md"
local before after
before="$(git_in status --porcelain)"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
after="$(git_in status --porcelain)"
assert_status "second call" 0 "$STATUS"
assert_eq "nothing else changed" "$before" "$after"
}
test_a_failed_qc_fetch_names_the_command_to_run_by_hand() {
setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
local dir="$WORK/project" answers
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
run_apply "$answers"$'y\n'
GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" submodule deinit -f qc >/dev/null 2>&1
# Drop the cached copy of the checklists and refuse local fetches, so they
# cannot be fetched again.
local cache="$dir/.git/modules/framework/modules/qc"
if [[ -d $cache ]]; then
find "$cache" \( -type f -o -type l \) -delete
find "$cache" -depth -type d -exec rmdir {} +
fi
printf '[protocol "file"]\n\tallow = never\n' >>"$WORK/gitconfig"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
assert_status "fetch fails" 1 "$STATUS"
assert_contains "the command" "$ERR" "git submodule update --init --recursive"
assert_not_contains "no token" "$ERR" "$FAKE_GITEA_TOKEN"
}
test_a_framework_without_a_submodule_of_its_own_is_not_a_failure() {
setup_hosts
local dir="$WORK/plain"
mkdir -p "$dir"
git init -q "$dir"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
assert_status "nothing to fetch" 0 "$STATUS"
}
# ------------------------------------------------ the configuration files
test_files_in_the_working_folder_are_used_after_a_yes() {
setup_hosts
make_checkout
make_folder
cp "$WORK/config.env" "$FOLDER/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
run_from "$SCRIPT" "$FOLDER" $'y\n'"$ANSWERS_GITEA_ONLY"
assert_status "folder files" 0 "$STATUS"
assert_contains "config named" "$OUT" "Config file : $FOLDER/config.env (from the working folder)"
assert_contains "credentials named" "$OUT" "Credentials file: $FOLDER/.env (from the working folder)"
assert_contains "the address is named" "$OUT" "Gitea would be https://git.example.test"
assert_contains "asked" "$ERR" "Use $FOLDER/config.env $FOLDER/.env (y/n) [n]"
}
test_files_in_the_working_folder_are_not_used_without_a_yes() {
setup_hosts
make_checkout
make_folder
cp "$WORK/config.env" "$FOLDER/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
rm -f "$WORK/curl.calls"
run_from "$SCRIPT" "$FOLDER" $'\n'"$ANSWERS_GITEA_ONLY"
assert_status "default no" 1 "$STATUS"
assert_contains "stopped" "$ERR" "stopped before any request"
assert_eq "no request to any host" "" "$(calls)"
}
test_named_files_win_and_are_not_confirmed() {
setup_hosts
make_checkout
make_folder
cp "$WORK/config.env" "$FOLDER/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
run_from "$SCRIPT" "$FOLDER" "$ANSWERS_GITEA_ONLY" --config "$WORK/config.env" --env "$WORK/.env"
assert_status "named files" 0 "$STATUS"
assert_contains "named" "$OUT" "Config file : $WORK/config.env (named on the command line)"
assert_not_contains "no confirmation" "$OUT" "The working folder supplies"
}
test_the_checkouts_files_are_used_when_the_folder_has_none() {
setup_hosts
make_checkout
make_folder
run_from "$CHECKOUT/src/create-project.sh" "$FOLDER" "$ANSWERS_GITEA_ONLY"
assert_status "checkout files" 0 "$STATUS"
assert_contains "config named" "$OUT" "Config file : $CHECKOUT/config.env (from the checkout)"
assert_contains "credentials named" "$OUT" "Credentials file: $CHECKOUT/.env (from the checkout)"
assert_not_contains "no confirmation" "$OUT" "The working folder supplies"
}
test_each_file_is_chosen_on_its_own() {
setup_hosts
make_checkout
make_folder
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
run_from "$CHECKOUT/src/create-project.sh" "$FOLDER" $'y\n'"$ANSWERS_GITEA_ONLY"
assert_status "mixed" 0 "$STATUS"
assert_contains "config from the checkout" "$OUT" "Config file : $CHECKOUT/config.env (from the checkout)"
assert_contains "credentials from the folder" "$OUT" "Credentials file: $FOLDER/.env (from the working folder)"
assert_contains "asked about the folder file only" "$ERR" "Use $FOLDER/.env (y/n) [n]"
}
test_files_found_nowhere_stop_before_any_request_and_name_both_places() {
setup_hosts
make_checkout
make_folder
rm -f "$CHECKOUT/.env" "$WORK/curl.calls"
run_from "$CHECKOUT/src/create-project.sh" "$FOLDER" "$ANSWERS_GITEA_ONLY"
assert_status "no .env anywhere" 1 "$STATUS"
assert_contains "working folder named" "$ERR" "working folder ($FOLDER)"
assert_contains "checkout named" "$ERR" "checkout ($CHECKOUT)"
assert_contains "the option" "$ERR" "--env FILE"
assert_eq "no request to any host" "" "$(calls)"
}
# ----------------------------------------------------------- a link to it
test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folder() {
setup_hosts
make_checkout
make_folder
local bin="$WORK/linkbin"
mkdir -p "$bin"
MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/repo-foundry" 2>/dev/null || true
if [[ ! -L $bin/repo-foundry ]]; then
printf 'skipped: this shell cannot make symbolic links\n'
return 0
fi
run_from "$bin/repo-foundry" "$FOLDER" "" --version
assert_status "version through the link" 0 "$STATUS"
assert_contains "found its files" "$OUT" "RepoFoundry"
run_from "$bin/repo-foundry" "$FOLDER" "$ANSWERS_GITEA_ONLY"$'y\n' --apply
assert_status "apply through the link" 0 "$STATUS"
assert_contains "the checkout's files" "$OUT" "/checkout/config.env (from the checkout)"
assert_file_exists "the project is in the current folder" "$FOLDER/my-app/.git"
assert_file_missing "not in the checkout" "$CHECKOUT/my-app"
}
test_a_link_to_a_link_is_followed() {
setup_hosts
make_checkout
make_folder
local bin="$WORK/linkbin"
mkdir -p "$bin"
MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true
[[ -L $bin/first ]] || {
printf 'skipped: this shell cannot make symbolic links\n'
return 0
}
(cd "$bin" && MSYS=winsymlinks:nativestrict ln -s first second)
run_from "$bin/second" "$FOLDER" "" --version
assert_status "second link" 0 "$STATUS"
}
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env bash
# test-license.sh - tests for the project license (MIL-006): PROJECT_LICENSE
# in config.env, the default (AGPL-3.0 only for a public project with GitHub),
# "none", invalid values and a license the server does not offer. Sourced by
# run-tests.sh.
# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose
readonly ANSWERS_GITEA_ONLY_PUBLIC=$'my-app\n\npublic\nTirSystem\nn\n\nn\n'
readonly MIT_ROUTE='GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"},{"key":"AGPL-3.0","name":"AGPL-3.0"}]'
# use_license LINE: add a line to config.env and make the server offer MIT.
use_license() {
printf '%s\n' "$1" >>"$WORK/config.env"
prepend_route "$MIT_ROUTE"
}
# gitea_only_env: a Gitea-only run needs no GitHub credentials.
gitea_only_env() {
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
}
# ------------------------------------------------------------- key is set
test_a_license_in_config_is_used_with_github_for_a_private_project() {
setup_hosts
use_license "PROJECT_LICENSE=MIT"
run_apply "$ANSWERS_GITHUB"$'y\n'
assert_status "apply" 0 "$STATUS"
assert_contains "Gitea gets MIT" "$(cat "$WORK/curl.bodies")" '"license":"MIT"'
assert_not_contains "no AGPL" "$(cat "$WORK/curl.bodies")" "AGPL"
assert_contains "plan" "$OUT" "create (private) with the MIT license (from config.env)"
assert_contains "summary" "$OUT" "License : MIT (from config.env)"
}
test_a_license_in_config_is_used_without_github() {
setup_hosts
gitea_only_env
use_license "PROJECT_LICENSE=MIT"
run_apply "$ANSWERS_GITEA_ONLY"$'y\n'
assert_status "apply" 0 "$STATUS"
assert_contains "Gitea gets MIT" "$(cat "$WORK/curl.bodies")" '"license":"MIT"'
assert_contains "initialised with it" "$(cat "$WORK/curl.bodies")" '"auto_init":true'
assert_not_contains "no GitHub call" "$(calls)" "api.github.com"
assert_contains "the server is asked" "$(calls)" "/licenses"
}
test_the_license_key_is_taken_in_any_case_for_none() {
setup_hosts
use_license "PROJECT_LICENSE=NONE"
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\n'
assert_status "apply" 0 "$STATUS"
assert_not_contains "no license" "$(cat "$WORK/curl.bodies")" '"license"'
}
# ------------------------------------------------------------------- none
test_none_gives_no_license_even_for_a_public_project_with_github() {
setup_hosts
use_license "PROJECT_LICENSE=none"
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\n'
assert_status "apply" 0 "$STATUS"
assert_not_contains "no license" "$(cat "$WORK/curl.bodies")" '"license"'
assert_not_contains "no license lookup" "$(calls)" "/licenses"
assert_contains "plan" "$OUT" "create (public), empty"
assert_contains "summary" "$OUT" "License : none (from config.env)"
}
# ----------------------------------------------------------------- absent
test_without_the_key_a_public_project_with_github_gets_agpl() {
setup_hosts
run_dry "$ANSWERS_GITHUB_PUBLIC"
assert_status "dry run" 0 "$STATUS"
assert_contains "plan" "$OUT" "with the AGPL-3.0 license (default: GitHub and a public project)"
assert_contains "summary" "$OUT" "License : AGPL-3.0 (default: GitHub and a public project)"
}
test_without_the_key_a_private_project_with_github_gets_no_license() {
setup_hosts
run_dry "$ANSWERS_GITHUB"
assert_status "dry run" 0 "$STATUS"
assert_contains "summary" "$OUT" "License : none (no default for a private project)"
assert_not_contains "no license lookup" "$(calls)" "/licenses"
}
test_without_the_key_a_project_without_github_gets_no_license() {
setup_hosts
gitea_only_env
run_dry "$ANSWERS_GITEA_ONLY_PUBLIC"
assert_status "dry run" 0 "$STATUS"
assert_contains "summary" "$OUT" "License : none"
assert_not_contains "no marker" "$OUT" "License : none ("
assert_not_contains "no license lookup" "$(calls)" "/licenses"
}
# ---------------------------------------------------- empty and invalid
test_an_empty_license_stops_before_any_request() {
setup_hosts
printf 'PROJECT_LICENSE=\n' >>"$WORK/config.env"
run_dry "$ANSWERS_GITHUB"
assert_status "empty" 1 "$STATUS"
assert_contains "key named" "$ERR" "PROJECT_LICENSE in"
assert_contains "says empty" "$ERR" "is empty"
assert_eq "no request to any host" "" "$(calls)"
}
test_an_invalid_license_stops_before_any_request_and_is_never_asked() {
local value
for value in 'bad license' 'MIT/2' 'MIT;rm' "$(printf 'a%.0s' {1..65})"; do
remove_workdir
new_workdir
setup_hosts
printf 'PROJECT_LICENSE=%s\n' "$value" >>"$WORK/config.env"
run_dry "$ANSWERS_GITHUB"
assert_status "invalid '$value'" 1 "$STATUS"
assert_contains "key named" "$ERR" "PROJECT_LICENSE in"
assert_eq "no request to any host" "" "$(calls)"
assert_not_contains "never asked" "$ERR" "License ("
done
}
# ------------------------------------------------------ not offered
test_a_license_the_server_does_not_offer_stops_before_anything_is_created() {
setup_hosts
use_license "PROJECT_LICENSE=Zlib"
run_apply "$ANSWERS_GITHUB"$'y\n'
assert_status "not offered" 1 "$STATUS"
assert_contains "license named" "$ERR" "does not offer the Zlib license"
assert_not_contains "nothing created" "$(calls)" "POST"
}
# ------------------------------------------------------- never asked
test_the_license_is_never_asked() {
setup_hosts
run_dry "$ANSWERS_GITHUB_PUBLIC"
assert_not_contains "no license prompt" "$ERR" "icense ("
assert_not_contains "no license prompt, any case" "$ERR" "License:"
remove_workdir
new_workdir
setup_hosts
use_license "PROJECT_LICENSE=MIT"
run_dry "$ANSWERS_GITHUB"
assert_not_contains "no license prompt with the key" "$ERR" "icense ("
}
# ------------------------------------------- mirror and local history
test_the_license_file_reaches_the_local_project_without_github() {
setup_hosts
gitea_only_env
use_license "PROJECT_LICENSE=MIT"
local dir="$WORK/project" answers
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
run_apply "$answers"$'y\n'
assert_status "apply" 0 "$STATUS"
assert_eq "the Gitea license commit is the whole history" "1" "$(GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir" rev-list --count HEAD)"
assert_file_exists "LICENSE from Gitea" "$dir/LICENSE"
}
+1 -1
View File
@@ -12,7 +12,7 @@
# LOCAL_ANSWERS (kept in a variable because $(...) would drop the last newline). # LOCAL_ANSWERS (kept in a variable because $(...) would drop the last newline).
local_answers() { local_answers() {
if [[ $2 == y ]]; then if [[ $2 == y ]]; then
printf -v LOCAL_ANSWERS 'my-app\nA test app\n\nTirSystem\ny\nacme-org\n%s\n%s\n' "$1" "$3" printf -v LOCAL_ANSWERS 'my-app\nA test app\npublic\nTirSystem\ny\nacme-org\n%s\n%s\n' "$1" "$3"
else else
printf -v LOCAL_ANSWERS 'my-app\n\n\nTirSystem\nn\n%s\n%s\n' "$1" "$3" printf -v LOCAL_ANSWERS 'my-app\n\n\nTirSystem\nn\n%s\n%s\n' "$1" "$3"
fi fi
+1 -1
View File
@@ -205,7 +205,7 @@ test_summary_marks_the_values_from_config_env() {
assert_contains "name" "$OUT" "Repository : my-app (from config.env) (private (from config.env))" assert_contains "name" "$OUT" "Repository : my-app (from config.env) (private (from config.env))"
assert_contains "description" "$OUT" "Description : A test app (from config.env)" assert_contains "description" "$OUT" "Description : A test app (from config.env)"
assert_contains "Gitea" "$OUT" "/TirSystem/my-app (from config.env)" assert_contains "Gitea" "$OUT" "/TirSystem/my-app (from config.env)"
assert_contains "GitHub" "$OUT" "(AGPL license applied) (from config.env)" assert_contains "GitHub" "$OUT" "/acme-org/my-app (from config.env)"
assert_contains "directory" "$OUT" "Directory : ./my-app (from config.env)" assert_contains "directory" "$OUT" "Directory : ./my-app (from config.env)"
assert_contains "plan gate" "$OUT" "Plan gate : no (from config.env)" assert_contains "plan gate" "$OUT" "Plan gate : no (from config.env)"
} }
+8 -6
View File
@@ -19,10 +19,10 @@ test_full_run_with_github() {
after="$(listing)" after="$(listing)"
assert_status "full run" 0 "$STATUS" assert_status "full run" 0 "$STATUS"
assert_contains "dry run" "$OUT" "Dry run: nothing was created" assert_contains "dry run" "$OUT" "Dry run: nothing was created"
assert_contains "plan" "$OUT" "create (private) with the AGPL-3.0 license" assert_contains "plan" "$OUT" "create (private), empty"
assert_contains "Gitea link derived from config" "$OUT" "https://git.example.test/TirSystem/my-app" assert_contains "Gitea link derived from config" "$OUT" "https://git.example.test/TirSystem/my-app"
assert_contains "GitHub link uses the chosen organization" "$OUT" "https://github.com/acme-org/my-app" assert_contains "GitHub link uses the chosen organization" "$OUT" "https://github.com/acme-org/my-app"
assert_contains "AGPL noted" "$OUT" "AGPL license applied" assert_contains "no default license for a private project" "$OUT" "License : none (no default for a private project)"
assert_contains "credential state" "$OUT" "GITEA_TOKEN set, GITHUB_PAT set" assert_contains "credential state" "$OUT" "GITEA_TOKEN set, GITHUB_PAT set"
assert_not_contains "no Gitea token in output" "$OUT$ERR" "$FAKE_GITEA_TOKEN" assert_not_contains "no Gitea token in output" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
assert_not_contains "no GitHub token in output" "$OUT$ERR" "$FAKE_GITHUB_PAT" assert_not_contains "no GitHub token in output" "$OUT$ERR" "$FAKE_GITHUB_PAT"
@@ -201,13 +201,15 @@ test_default_files_are_in_the_project_root() {
cp "$WORK/config.env" "$WORK/project/config.env" cp "$WORK/config.env" "$WORK/project/config.env"
cp "$WORK/.env" "$WORK/project/.env" cp "$WORK/.env" "$WORK/project/.env"
STATUS=0 STATUS=0
PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ (cd "$WORK/project" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \
<<<"$ANSWERS_GITHUB" >"$WORK/out.txt" 2>"$WORK/err.txt" || STATUS=$? <<<"$ANSWERS_GITHUB" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$?
assert_status "run with the default files" 0 "$STATUS" assert_status "run with the default files" 0 "$STATUS"
assert_contains "found config.env in the project root" "$(cat "$WORK/out.txt")" "https://git.example.test/TirSystem/my-app" assert_contains "found config.env in the project root" "$(cat "$WORK/out.txt")" "https://git.example.test/TirSystem/my-app"
# Run from another directory: the defaults follow the script, not the cwd. # Run from another folder that holds no files of its own: the checkout's
# files are used; the defaults follow the script, not the current folder.
mkdir -p "$WORK/elsewhere"
STATUS=0 STATUS=0
(cd "$WORK" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ (cd "$WORK/elsewhere" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \
<<<"$ANSWERS_GITEA_ONLY" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$? <<<"$ANSWERS_GITEA_ONLY" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$?
assert_status "run from another directory" 0 "$STATUS" assert_status "run from another directory" 0 "$STATUS"
} }