fix(init): auto-add per-project files to .gitignore in git repos (#185) (#866)

Partially addresses #185.

`mempalace init <dir>` writes `mempalace.yaml` and `entities.json` into
the project root. When <dir> is a git repository, those files have no
default protection and risk being committed by accident — the loudest
concern in the original report.

This PR adds `_ensure_mempalace_files_gitignored()` which runs at the
end of cmd_init: if <dir>/.git exists, append the two filenames to
.gitignore (creating it if necessary) under a clearly-marked block.

The helper is conservative:
- only runs when <dir>/.git is present (no-op for non-git projects)
- skips entries already present (no duplicates)
- preserves existing .gitignore content
- handles files without trailing newlines

This does NOT relocate the files to ~/.mempalace/wings/<wing>/ as the
issue's 'Expected' section proposes — that's a behavioral change with
miner/config implications and warrants a separate design discussion.
The gitignore safeguard removes the immediate risk without breaking any
existing flow.

Tests: 5 cases in tests/test_init_gitignore_protection.py covering
no-op, fresh creation, partial append, idempotency, and missing-newline
edge case.
This commit is contained in:
Arnold Wender
2026-04-15 09:26:41 +02:00
committed by GitHub
parent 6a73eb2e20
commit 0aee6f3ed9
2 changed files with 96 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
"""Regression tests for issue #185 — gitignore protection on `mempalace init`.
Issue #185 reports that `mempalace init <dir>` writes `mempalace.yaml` and
`entities.json` into the project root, where they could be committed by
accident. The fix adds `_ensure_mempalace_files_gitignored()` which appends
the two filenames to `.gitignore` when `<dir>` is a git repository.
"""
from pathlib import Path
from mempalace.cli import _ensure_mempalace_files_gitignored
def _git_init(path: Path) -> None:
"""Mark a directory as a git repo without invoking git itself."""
(path / ".git").mkdir()
def test_no_op_when_not_a_git_repo(tmp_path):
assert _ensure_mempalace_files_gitignored(tmp_path) is False
assert not (tmp_path / ".gitignore").exists()
def test_creates_gitignore_with_both_entries(tmp_path):
_git_init(tmp_path)
assert _ensure_mempalace_files_gitignored(tmp_path) is True
contents = (tmp_path / ".gitignore").read_text()
assert "mempalace.yaml" in contents
assert "entities.json" in contents
assert "issue #185" in contents
def test_appends_only_missing_entries(tmp_path):
_git_init(tmp_path)
(tmp_path / ".gitignore").write_text("node_modules/\nmempalace.yaml\n")
assert _ensure_mempalace_files_gitignored(tmp_path) is True
contents = (tmp_path / ".gitignore").read_text()
# mempalace.yaml must not be duplicated
assert contents.count("mempalace.yaml") == 1
# entities.json was missing → must now be present
assert "entities.json" in contents
# original entries preserved
assert "node_modules/" in contents
def test_idempotent_when_both_already_present(tmp_path):
_git_init(tmp_path)
initial = "mempalace.yaml\nentities.json\n"
(tmp_path / ".gitignore").write_text(initial)
assert _ensure_mempalace_files_gitignored(tmp_path) is False
assert (tmp_path / ".gitignore").read_text() == initial
def test_handles_gitignore_without_trailing_newline(tmp_path):
_git_init(tmp_path)
(tmp_path / ".gitignore").write_text("dist") # no trailing newline
assert _ensure_mempalace_files_gitignored(tmp_path) is True
contents = (tmp_path / ".gitignore").read_text()
# Original entry preserved on its own line, not glued to the new block
assert "dist\n" in contents
assert "mempalace.yaml" in contents
assert "entities.json" in contents