8Integration tests for the F1.4 workspace-aware ``oct git ignore``
9``--scope`` flag and the matching ``oct git init`` workspace-mode
10behaviour (which augments the workspace-root ``.gitignore`` with the
11manifest's ``ignore_at_workspace_scope[]`` patterns).
15- ``oct git ignore --scope=workspace`` writes to the workspace
16 root's ``.gitignore``, even when invoked from inside a subproject.
17- ``oct git ignore --scope=project`` writes to the
18 ``project_root``'s ``.gitignore`` (subproject if invoked there;
19 workspace root if invoked there).
20- ``oct git ignore --scope=nearest`` (the default) targets the
21 workspace root when CWD is the workspace root, otherwise the
22 subproject's ``.gitignore``.
23- ``oct git init`` at workspace scope merges the manifest's
24 ``ignore_at_workspace_scope[]`` patterns into the workspace
32 L3 -- assertion details
36- All tests use subprocess invocation against ``oct.cli``.
37- All tests use tmp_path fixtures so they are automatically cleaned up.
44from pathlib
import Path
50 return shutil.which(
"git")
is not None
53def _git(repo: Path, *args: str, check: bool =
True):
54 return subprocess.run(
55 [
"git", *args], cwd=repo, check=check,
56 capture_output=
True, text=
True,
61 cwd: Path, *extra_args: str,
62) -> subprocess.CompletedProcess:
63 return subprocess.run(
64 [sys.executable,
"-m",
"oct.cli",
"git",
"ignore", *extra_args],
65 cwd=cwd, capture_output=
True, text=
True,
70 cwd: Path, *extra_args: str,
71) -> subprocess.CompletedProcess:
72 return subprocess.run(
73 [sys.executable,
"-m",
"oct.cli",
"git",
"init", *extra_args],
74 cwd=cwd, capture_output=
True, text=
True,
80 sub.mkdir(parents=
True, exist_ok=
True)
81 (sub /
".option_c").mkdir()
83 (sub /
"tests").mkdir()
84 (sub /
"docs").mkdir()
85 (sub /
"logs").mkdir()
86 (sub /
"oc_diagnostics").mkdir()
87 (sub /
".octrc.json").write_text(
88 json.dumps({
"linter": {
"profile":
"compact"}}),
91 (sub /
"pyproject.toml").write_text(
92 f
'[project]\nname = "{name}"\n', encoding=
"utf-8",
94 (sub /
"debug_config.json").write_text(
95 json.dumps({
"domains": {
"GIT": {
"level": 1,
"color":
"cyan"}}}),
98 (sub /
"docs" /
"ARCHITECTURE.md").write_text(
99 "# Architecture\n", encoding=
"utf-8",
106 """Build a workspace with one subproject for scope tests."""
108 pytest.skip(
"git binary not available on PATH")
110 ws_root = tmp_path /
"ws"
112 _git(ws_root,
"init",
"--quiet",
"-b",
"feature/test")
113 _git(ws_root,
"config",
"user.email",
"test@example.com")
114 _git(ws_root,
"config",
"user.name",
"Test")
115 _git(ws_root,
"config",
"commit.gpgsign",
"false")
119 "name":
"Scope Test Workspace",
120 "ignore_at_workspace_scope": [
121 ".claude/settings.local.json",
122 ".claude/worktrees/**",
125 {
"path":
"alpha",
"name":
"alpha",
"profile":
"compact"},
128 (ws_root /
".option_c_workspace.json").write_text(
129 json.dumps(manifest, indent=2), encoding=
"utf-8",
133 _git(ws_root,
"add",
"-A")
134 _git(ws_root,
"commit",
"-m",
"initial",
"--quiet")
136 return ws_root.resolve(), alpha.resolve()
147 self, workspace_with_subproject,
149 ws_root, _alpha = workspace_with_subproject
151 ws_root,
"*.tmp",
"--scope=workspace",
153 assert result.returncode == 0, result.stderr
154 ws_gitignore = (ws_root /
".gitignore").read_text(encoding=
"utf-8")
155 assert "*.tmp" in ws_gitignore
158 self, workspace_with_subproject,
160 ws_root, alpha = workspace_with_subproject
162 alpha,
"*.scratch",
"--scope=workspace",
164 assert result.returncode == 0, result.stderr
165 ws_gitignore = (ws_root /
".gitignore").read_text(encoding=
"utf-8")
166 assert "*.scratch" in ws_gitignore
168 assert not (alpha /
".gitignore").is_file()
171 self, tmp_path: Path,
174 pytest.skip(
"git not on PATH")
176 proj = tmp_path /
"proj"
178 _git(proj,
"init",
"--quiet",
"-b",
"main")
179 _git(proj,
"config",
"user.email",
"t@e.com")
180 _git(proj,
"config",
"user.name",
"T")
181 _git(proj,
"config",
"commit.gpgsign",
"false")
183 _git(proj,
"add",
"-A")
184 _git(proj,
"commit",
"-m",
"init",
"--quiet")
186 result =
_run_ignore(proj,
"*.tmp",
"--scope=workspace")
187 assert result.returncode == 1
188 assert "workspace manifest" in result.stderr.lower()
199 self, workspace_with_subproject,
201 ws_root, alpha = workspace_with_subproject
203 alpha,
"build/",
"--scope=project",
205 assert result.returncode == 0, result.stderr
207 assert (alpha /
".gitignore").is_file()
208 content = (alpha /
".gitignore").read_text(encoding=
"utf-8")
209 assert "build/" in content
211 ws_content = (ws_root /
".gitignore").read_text(
213 )
if (ws_root /
".gitignore").is_file()
else ""
214 assert "build/" not in ws_content
225 self, workspace_with_subproject,
227 ws_root, _alpha = workspace_with_subproject
231 assert result.returncode == 0, result.stderr
232 ws_content = (ws_root /
".gitignore").read_text(encoding=
"utf-8")
233 assert "*.bak" in ws_content
236 self, workspace_with_subproject,
238 ws_root, alpha = workspace_with_subproject
240 assert result.returncode == 0, result.stderr
242 assert (alpha /
".gitignore").is_file()
243 sp_content = (alpha /
".gitignore").read_text(encoding=
"utf-8")
244 assert "*.cache" in sp_content
246 ws_path = ws_root /
".gitignore"
248 ws_path.read_text(encoding=
"utf-8")
249 if ws_path.is_file()
else ""
251 assert "*.cache" not in ws_content
262 self, workspace_with_subproject,
264 ws_root, _alpha = workspace_with_subproject
267 gi = ws_root /
".gitignore"
272 assert result.returncode == 0, result.stderr
273 assert gi.is_file(),
"init should have created .gitignore"
274 content = gi.read_text(encoding=
"utf-8")
276 assert "__pycache__/" in content
278 assert ".claude/settings.local.json" in content
279 assert ".claude/worktrees/**" in content
282 self, workspace_with_subproject,
284 _ws_root, alpha = workspace_with_subproject
286 gi = alpha /
".gitignore"
291 assert result.returncode == 0, result.stderr
293 content = gi.read_text(encoding=
"utf-8")
296 assert ".claude/settings.local.json" not in content
297 assert ".claude/worktrees/**" not in content
308 self, tmp_path: Path,
311 pytest.skip(
"git not on PATH")
312 proj = tmp_path /
"single"
314 _git(proj,
"init",
"--quiet",
"-b",
"main")
315 _git(proj,
"config",
"user.email",
"t@e.com")
316 _git(proj,
"config",
"user.name",
"T")
317 _git(proj,
"config",
"commit.gpgsign",
"false")
318 _git(proj,
"add",
"-A")
319 _git(proj,
"commit",
"-m",
"init",
"--quiet")
324 assert result.returncode == 0, result.stderr
325 assert (proj /
".gitignore").is_file()
326 content = (proj /
".gitignore").read_text(encoding=
"utf-8")
327 assert "*.tmp" in content
None test_no_workspace_scope_nearest_returns_project(self, Path tmp_path)
None test_init_at_workspace_root_merges_ignore_patterns(self, workspace_with_subproject)
None test_init_at_subproject_does_not_use_workspace_patterns(self, workspace_with_subproject)
None test_default_at_workspace_root_targets_workspace(self, workspace_with_subproject)
None test_default_at_subproject_targets_subproject(self, workspace_with_subproject)
None test_writes_to_subproject_gitignore(self, workspace_with_subproject)
None test_scope_workspace_outside_workspace_errors(self, Path tmp_path)
None test_writes_to_workspace_gitignore_from_workspace_root(self, workspace_with_subproject)
None test_writes_to_workspace_gitignore_from_subproject(self, workspace_with_subproject)
subprocess.CompletedProcess _run_init(Path cwd, *str extra_args)
Path _make_subproject(Path parent, str name)
_git(Path repo, *str args, bool check=True)
workspace_with_subproject(Path tmp_path)
subprocess.CompletedProcess _run_ignore(Path cwd, *str extra_args)