diff --git a/src/lib/apply.sh b/src/lib/apply.sh index e823b56..3d21b0b 100644 --- a/src/lib/apply.sh +++ b/src/lib/apply.sh @@ -19,6 +19,7 @@ create_all() { add_framework install_framework copy_templates + exclude_framework_files create_env_file } diff --git a/src/lib/constants.sh b/src/lib/constants.sh index d5775dd..5fc95aa 100644 --- a/src/lib/constants.sh +++ b/src/lib/constants.sh @@ -30,8 +30,12 @@ readonly HINT_LICENSE="use a Gitea license key (letters, digits, '.', '+' or '-' readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters" readonly HINT_TOKEN="8 to 255 letters, digits or _ . ~ + / = -" readonly ENV_FILE_NAME=".env" +# What the framework installs into the new project that git must not list: the +# folders of the skills and the copy of AGENTS.md, anchored to the project +# root. They go into .git/info/exclude of the new project. +readonly FRAMEWORK_EXCLUDES=(/.claude /.agents /AGENTS.md) readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror" - "Local project" "Framework" "Skills and hooks" "Templates" "Project .env") + "Local project" "Framework" "Skills and hooks" "Templates" "Git excludes" "Project .env") # 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 FRAMEWORK_REPO diff --git a/src/lib/envfile.sh b/src/lib/envfile.sh index f2b67eb..d67c40e 100644 --- a/src/lib/envfile.sh +++ b/src/lib/envfile.sh @@ -26,19 +26,8 @@ env_file_key_list() { # file: the entry goes into .git/info/exclude, which is never committed. It # does nothing when .env is already ignored. exclude_env_file() { - local dir="$1" gitdir exclude - if git_project "$dir" check-ignore -q -- "$ENV_FILE_NAME"; then - return 0 - fi - gitdir="$(git_project "$dir" rev-parse --absolute-git-dir)" - exclude="$gitdir/info/exclude" - mkdir -p -- "$gitdir/info" - # Start on a fresh line when the file does not end with one. - if [[ -s $exclude && -n "$(tail -c 1 -- "$exclude")" ]]; then - printf '\n' >>"$exclude" - fi - printf '%s\n' "# RepoFoundry: the credentials file of this project" "$ENV_FILE_NAME" >>"$exclude" - git_project "$dir" check-ignore -q -- "$ENV_FILE_NAME" || + local dir="$1" + exclude_from_git "$dir" "RepoFoundry: the credentials file of this project" "$ENV_FILE_NAME" || die "could not make git ignore $ENV_FILE_NAME in $dir; nothing was written to it" } diff --git a/src/lib/framework.sh b/src/lib/framework.sh index ee25452..f0d1d56 100644 --- a/src/lib/framework.sh +++ b/src/lib/framework.sh @@ -4,7 +4,7 @@ # # Part of create-project.sh: sourced by it, never run on its own. # -# Provides: is_framework_skipped, init_framework_submodules, 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, exclude_framework_files # is_framework_skipped: succeed when the framework steps are left out because # SSH to Gitea is not available (the Maintainer agreed to that). @@ -165,3 +165,39 @@ copy_templates() { notes="$notes; ${STATE[template_note]}" finish_step "$label" "created" "($notes)" } + +# exclude_framework_files: make git ignore .claude, .agents and AGENTS.md in +# the new project, through its .git/info/exclude (never .gitignore, so no +# tracked file changes and nothing is committed). Only when the framework +# steps ran. A path git already tracks stays tracked: an exclusion does not +# apply to it, so the step names it. +exclude_framework_files() { + local label="Git excludes" dir="${PROJECT[directory]}" entry path tracked=() note written + if is_framework_skipped; then + finish_step "$label" "skipped" "(no SSH access to Gitea)" + return 0 + fi + begin_step "$label" + exclude_from_git "$dir" "RepoFoundry: the files installed from the framework" "${FRAMEWORK_EXCLUDES[@]}" || + die "could not make git ignore the framework files in $dir; check $dir/.git/info/exclude" + written="$REPLY" + for entry in "${FRAMEWORK_EXCLUDES[@]}"; do + path="${entry#/}" + if [[ -n "$(git_project "$dir" ls-files -- "$path")" ]]; then + tracked+=("$path") + fi + done + if ((written == 0)); then + note="${FRAMEWORK_EXCLUDES[*]} already excluded" + else + note="${FRAMEWORK_EXCLUDES[*]} added to .git/info/exclude" + fi + if ((${#tracked[@]})); then + note="$note; git tracks ${tracked[*]}, so it is not ignored" + fi + if ((written == 0)); then + finish_step "$label" "reused" "($note)" + else + finish_step "$label" "created" "($note)" + fi +} diff --git a/src/lib/git.sh b/src/lib/git.sh index e952674..176bd30 100644 --- a/src/lib/git.sh +++ b/src/lib/git.sh @@ -4,7 +4,7 @@ # # Part of create-project.sh: sourced by it, never run on its own. # -# Provides: git_project, fetch_origin +# Provides: git_project, exclude_from_git, fetch_origin # git_project DIR ARGS...: run git in DIR. Prompts are switched off and stdin # is closed (git must not eat the answers meant for later prompts), so a @@ -16,6 +16,40 @@ git_project() { git -C "$dir" "$@" >"$exclude" + fi + printf '%s\n' "# $comment" "${missing[@]}" >>"$exclude" + for entry in "${missing[@]}"; do + path="${entry#/}" + git_project "$dir" check-ignore --no-index -q -- "${path%/}" || return 1 + done +} + # 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 diff --git a/src/lib/plan.sh b/src/lib/plan.sh index 0c2d81b..f8fffad 100644 --- a/src/lib/plan.sh +++ b/src/lib/plan.sh @@ -51,6 +51,7 @@ print_plan() { 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)")" + say "$(printf ' %-18s: %s' "Git excludes" "${FRAMEWORK_EXCLUDES[*]} go into .git/info/exclude (no tracked file changes)")" else say "$(printf ' %-18s: %s' "Framework" "NOT possible without SSH to Gitea; you will be asked whether to go on without it")" fi