ref:37fa655b1f88eff6f643ac15303b3e27b28df263

fix(test): stop git upward-discovery escaping into the real repo

Under the full concurrent suite, integration/protocol tests run `git` in scratch/temp dirs. If such a dir isn't itself a repo (a setup step raced or failed under load), a mutating git command walks up the filesystem, finds THIS project's .git, and silently corrupts the developer's checkout — rewrites origin to a dead test server, force-updates origin/main, sets core.bare=true, and leaves stray commits. Reproduced deterministically: ( cd tmp/x/not-a-repo && git config core.bare true ) # sets it on the PROJECT repo Set GIT_CEILING_DIRECTORIES to the project root in test_helper so git's upward search stops before the project root; a stray command now fails `not in a git directory` in place instead of mutating the checkout. Adds a regression test asserting the guard is set and that git cannot discover the project repo from a scratch dir. Contains the corruption; the underlying load-flakiness of the protocol/HTTP tests is separate follow-up. Closes #74 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SHA: 37fa655b1f88eff6f643ac15303b3e27b28df263
Author: t <t@t.com>
Date: 2026-07-10 06:48
Parents: 6463a30
2 files changed +64 -0
Type
test/ex_git_objectstore/test_isolation_test.exs +50 −0
@@ -1,0 +1,50 @@
# Copyright 2026 Cole Christensen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
defmodule ExGitObjectstore.TestIsolationTest do
@moduledoc """
Guards the test-suite isolation invariant: a `git` command run in a scratch
directory must never be able to discover — and therefore mutate — this
project's real repository. See `test/test_helper.exs`.
"""
use ExUnit.Case, async: true
test "GIT_CEILING_DIRECTORIES is set to the project root for all git children" do
assert System.get_env("GIT_CEILING_DIRECTORIES") == File.cwd!()
end
@tag :tmp_dir
test "git cannot escape a scratch dir upward into the project repo", %{tmp_dir: tmp_dir} do
# `tmp_dir` is under the project tree but is NOT a git repo. Before the
# ceiling guard, a mutating git command here walked up to the project's
# real .git and corrupted it. It must now fail in place instead.
scratch = Path.join(tmp_dir, "not-a-repo")
File.mkdir_p!(scratch)
{out, code} =
System.cmd("git", ["config", "core.bare", "true"],
cd: scratch,
stderr_to_stdout: true
)
refute code == 0, "git escaped the scratch dir and mutated a real repo:\n#{out}"
# And discovery itself must not resolve to the project root.
{toplevel, _} =
System.cmd("git", ["rev-parse", "--show-toplevel"], cd: scratch, stderr_to_stdout: true)
refute String.trim(toplevel) == File.cwd!(),
"git discovered the project repo from a scratch dir: #{toplevel}"
end
end
test/test_helper.exs +14 −0
@@ -12,4 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Test isolation guard: stop git's upward repository discovery at the project
# root so no test can escape into THIS repo's real `.git`.
#
# Many integration tests run `git` in scratch/temp directories (clones, daemon
# clients). If such a directory is not itself a git repo — e.g. a setup step
# raced or failed under load — a *mutating* git command (`config`, `remote`,
# `fetch`, `commit`) would otherwise walk up the filesystem, find this
# project's `.git`, and silently corrupt the developer's checkout (rewrite
# `origin`, set `core.bare=true`, force-update `origin/main`, add commits).
# `GIT_CEILING_DIRECTORIES` makes git stop before the project root, so any such
# stray command fails loudly in its own directory instead. Belongs to every
# git child of the test process, so it is set once here, globally.
System.put_env("GIT_CEILING_DIRECTORIES", File.cwd!())
ExUnit.start(exclude: [:s3])