Implement MIL-006 and MIL-007: project license, qc submodules, link start, default configuration files #54
@@ -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
|
||||||
@@ -151,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
|
||||||
|
|||||||
+1
-1
@@ -167,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=""
|
||||||
|
|||||||
+17
-16
@@ -4,7 +4,7 @@
|
|||||||
# --config and --env, else ./config.env and ./.env, else the checkout's.
|
# --config and --env, else ./config.env and ./.env, else the checkout's.
|
||||||
# Sourced by run-tests.sh.
|
# Sourced by run-tests.sh.
|
||||||
|
|
||||||
# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose
|
# 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
|
# 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.
|
# beside them, standing in for the checkout; the path is in CHECKOUT.
|
||||||
@@ -53,19 +53,19 @@ test_the_framework_checklists_are_fetched() {
|
|||||||
test_an_empty_qc_is_filled_by_a_second_run_and_a_complete_one_is_not_changed() {
|
test_an_empty_qc_is_filled_by_a_second_run_and_a_complete_one_is_not_changed() {
|
||||||
setup_hosts
|
setup_hosts
|
||||||
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
||||||
local dir="$WORK/project" answers git_in
|
local dir="$WORK/project" answers
|
||||||
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
|
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
|
||||||
run_apply "$answers"$'y\n'
|
run_apply "$answers"$'y\n'
|
||||||
assert_status "first run" 0 "$STATUS"
|
assert_status "first run" 0 "$STATUS"
|
||||||
git_in() { GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" "$@"; }
|
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
|
git_in submodule deinit -f qc >/dev/null 2>&1
|
||||||
assert_file_missing "qc emptied" "$dir/framework/qc/qc-business-case.md"
|
assert_file_missing "qc emptied" "$dir/framework/qc/qc-business-case.md"
|
||||||
run_lib "" "init_framework_submodules '$dir'"
|
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
|
||||||
assert_status "repair" 0 "$STATUS"
|
assert_status "repair" 0 "$STATUS"
|
||||||
assert_file_exists "qc filled again" "$dir/framework/qc/qc-business-case.md"
|
assert_file_exists "qc filled again" "$dir/framework/qc/qc-business-case.md"
|
||||||
local before after
|
local before after
|
||||||
before="$(git_in status --porcelain)"
|
before="$(git_in status --porcelain)"
|
||||||
run_lib "" "init_framework_submodules '$dir'"
|
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
|
||||||
after="$(git_in status --porcelain)"
|
after="$(git_in status --porcelain)"
|
||||||
assert_status "second call" 0 "$STATUS"
|
assert_status "second call" 0 "$STATUS"
|
||||||
assert_eq "nothing else changed" "$before" "$after"
|
assert_eq "nothing else changed" "$before" "$after"
|
||||||
@@ -78,14 +78,15 @@ test_a_failed_qc_fetch_names_the_command_to_run_by_hand() {
|
|||||||
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
|
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
|
||||||
run_apply "$answers"$'y\n'
|
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
|
GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" submodule deinit -f qc >/dev/null 2>&1
|
||||||
printf '[url "file://%s/nowhere/"]\n\tinsteadOf = ssh://git@git.tirsystem.com:10022/\n' "$WORK" >>"$WORK/gitconfig"
|
# Drop the cached copy of the checklists and refuse local fetches, so they
|
||||||
GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" config --unset-all submodule.qc.url >/dev/null 2>&1 || true
|
# cannot be fetched again.
|
||||||
run_lib "" "init_framework_submodules '$dir'"
|
local cache="$dir/.git/modules/framework/modules/qc"
|
||||||
if ((STATUS == 0)); then
|
if [[ -d $cache ]]; then
|
||||||
# The earlier alias still wins in this git; remove the working alias instead.
|
find "$cache" \( -type f -o -type l \) -delete
|
||||||
sed -i '/remote\/"\]/,+1d' "$WORK/gitconfig"
|
find "$cache" -depth -type d -exec rmdir {} +
|
||||||
run_lib "" "init_framework_submodules '$dir'"
|
|
||||||
fi
|
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_status "fetch fails" 1 "$STATUS"
|
||||||
assert_contains "the command" "$ERR" "git submodule update --init --recursive"
|
assert_contains "the command" "$ERR" "git submodule update --init --recursive"
|
||||||
assert_not_contains "no token" "$ERR" "$FAKE_GITEA_TOKEN"
|
assert_not_contains "no token" "$ERR" "$FAKE_GITEA_TOKEN"
|
||||||
@@ -96,7 +97,7 @@ test_a_framework_without_a_submodule_of_its_own_is_not_a_failure() {
|
|||||||
local dir="$WORK/plain"
|
local dir="$WORK/plain"
|
||||||
mkdir -p "$dir"
|
mkdir -p "$dir"
|
||||||
git init -q "$dir"
|
git init -q "$dir"
|
||||||
run_lib "" "init_framework_submodules '$dir'"
|
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
|
||||||
assert_status "nothing to fetch" 0 "$STATUS"
|
assert_status "nothing to fetch" 0 "$STATUS"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +187,7 @@ test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folde
|
|||||||
make_folder
|
make_folder
|
||||||
local bin="$WORK/linkbin"
|
local bin="$WORK/linkbin"
|
||||||
mkdir -p "$bin"
|
mkdir -p "$bin"
|
||||||
ln -s "$CHECKOUT/src/create-project.sh" "$bin/repo-foundry" 2>/dev/null || true
|
MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/repo-foundry" 2>/dev/null || true
|
||||||
if [[ ! -L $bin/repo-foundry ]]; then
|
if [[ ! -L $bin/repo-foundry ]]; then
|
||||||
printf 'skipped: this shell cannot make symbolic links\n'
|
printf 'skipped: this shell cannot make symbolic links\n'
|
||||||
return 0
|
return 0
|
||||||
@@ -196,7 +197,7 @@ test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folde
|
|||||||
assert_contains "found its files" "$OUT" "RepoFoundry"
|
assert_contains "found its files" "$OUT" "RepoFoundry"
|
||||||
run_from "$bin/repo-foundry" "$FOLDER" "$ANSWERS_GITEA_ONLY"$'y\n' --apply
|
run_from "$bin/repo-foundry" "$FOLDER" "$ANSWERS_GITEA_ONLY"$'y\n' --apply
|
||||||
assert_status "apply through the link" 0 "$STATUS"
|
assert_status "apply through the link" 0 "$STATUS"
|
||||||
assert_contains "the checkout's files" "$OUT" "Config file : $CHECKOUT/config.env (from the checkout)"
|
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_exists "the project is in the current folder" "$FOLDER/my-app/.git"
|
||||||
assert_file_missing "not in the checkout" "$CHECKOUT/my-app"
|
assert_file_missing "not in the checkout" "$CHECKOUT/my-app"
|
||||||
}
|
}
|
||||||
@@ -207,12 +208,12 @@ test_a_link_to_a_link_is_followed() {
|
|||||||
make_folder
|
make_folder
|
||||||
local bin="$WORK/linkbin"
|
local bin="$WORK/linkbin"
|
||||||
mkdir -p "$bin"
|
mkdir -p "$bin"
|
||||||
ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true
|
MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true
|
||||||
[[ -L $bin/first ]] || {
|
[[ -L $bin/first ]] || {
|
||||||
printf 'skipped: this shell cannot make symbolic links\n'
|
printf 'skipped: this shell cannot make symbolic links\n'
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
(cd "$bin" && ln -s first second)
|
(cd "$bin" && MSYS=winsymlinks:nativestrict ln -s first second)
|
||||||
run_from "$bin/second" "$FOLDER" "" --version
|
run_from "$bin/second" "$FOLDER" "" --version
|
||||||
assert_status "second link" 0 "$STATUS"
|
assert_status "second link" 0 "$STATUS"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user