From 83af669dc1d263333f73a6a825e48a18869bf0d7 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 20 Oct 2026 16:02:41 +0000 Subject: [PATCH 1/2] scripts/family.py and tests: `family.py init --dry-run`'s help and docstring say its folder answer is `os.access`'s, which on Windows says yes for every folder, and that it leaves nothing that stays, where they said it makes no folder; and a unit test pins the seed NOTE's GitHub clause for a GitHub leg (#923, #921) Co-Authored-By: Claude Sonnet 5.5 --- docs/cli.md | 10 ++++- scripts/family.py | 29 +++++++++++++-- tests/test_github_limits_init_check_org.py | 24 ++++++++++++ tests/test_scaffold_reuse_seeded_legs.py | 43 ++++++++++++++++++++++ 4 files changed, 101 insertions(+), 5 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 3ca5f67e..d9fd7318 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -956,8 +956,14 @@ options: --work-dir, the holder in it, --local-remote-dir) and whether / already exists (`gh repo view` and its commit listing; with --local-remote-dir, the - bare repository), refusing what it refuses; make no - folder, remote or repository + bare repository), refusing what it refuses. Whether + each folder can be written in is asked of `os.access`, + which reads the mode bits and on Windows says yes for + every folder, so a quota, a Windows ACL or a read-only + drive on Windows can pass here and be refused by the + real run. It leaves nothing that stays: it makes and + removes temporary folders, creates no remote or + repository and pushes nothing ``` #### `scripts/family.py add` diff --git a/scripts/family.py b/scripts/family.py index 82270793..629e2f9c 100755 --- a/scripts/family.py +++ b/scripts/family.py @@ -90,7 +90,7 @@ question the real run asks before it creates the holder (#537): `gh repo view` and the commit listing, or the bare repository with `--local-remote-dir`. It refuses `family-remote-exists` as the real run does, -and makes no folder, remote or repository. Both runs ask it after whether +and creates no remote or repository. Both runs ask it after whether each folder `init` makes can be made and the holder written (#609), and before the real run makes the family folder, so a refusal leaves none: a file where a folder must go, or a folder this user cannot write in, is @@ -99,6 +99,22 @@ `gh repo view` failure (a bad login, a rate limit, no network) is refused as `remote-unreadable`. +`init --dry-run` LEAVES NOTHING THAT STAYS, which is not the same as making +no folder (#921). It makes and removes temporary folders, as the real run +does: `openreposhape-branch-*`, where git is asked whether the tracking +branch is a name it will create, and `openreposhape-ident-*`, one each for +the author and the committer when a GIT_AUTHOR_* or GIT_COMMITTER_* variable +is set and not empty. Each is empty, and a removal that fails is not +reported. It creates no remote or repository and pushes nothing: the only +`gh` calls it makes read (`gh repo view`, the commit listing). + +WHETHER EACH FOLDER CAN BE WRITTEN IN IS THE MODE BITS' ANSWER (#923). The +family folder or `--work-dir`, the holder in it and `--local-remote-dir` are +asked of `os.access`, which reads the mode bits and on Windows says yes for +every folder, and a dry run makes none of them to ask. So a quota, a Windows +ACL or a read-only drive on Windows can pass the dry run and be refused by the +real run. + `init` ASKS GITHUB'S LIMITS FIRST, WHERE GITHUB IS (#863), as the scaffold and `adopt-project.py` ask theirs (#838), before `_creator_name` and before any `gh` call, so `--dry-run` refuses the same. The holder's topic, @@ -10452,8 +10468,15 @@ def build_parser() -> argparse.ArgumentParser: "--local-remote-dir) and whether / " "already exists (`gh repo view` and its commit " "listing; with --local-remote-dir, the bare " - "repository), refusing what it refuses; make no " - "folder, remote or repository") + "repository), refusing what it refuses. Whether " + "each folder can be written in is asked of " + "`os.access`, which reads the mode bits and on " + "Windows says yes for every folder, so a quota, " + "a Windows ACL or a read-only drive on Windows " + "can pass here and be refused by the real run. " + "It leaves nothing that stays: it makes and " + "removes temporary folders, creates no remote or " + "repository and pushes nothing") init.set_defaults(func=cmd_init) add = subparsers.add_parser( diff --git a/tests/test_github_limits_init_check_org.py b/tests/test_github_limits_init_check_org.py index 8fd811f8..225033de 100644 --- a/tests/test_github_limits_init_check_org.py +++ b/tests/test_github_limits_init_check_org.py @@ -409,6 +409,30 @@ def test_init_help_says_each_bound_where_the_value_is_typed(): assert sentence in entry, (flag, entry) +def test_init_dry_run_help_says_the_mode_bits_and_nothing_that_stays(): + """#923 U063 and #921 U088, the family's half, as the scaffold's + `--dry-run` says it: the write question is asked of `os.access`, which on + Windows says yes for every folder, so a quota, a Windows ACL or a + read-only drive there can pass it, and the run leaves "nothing that + stays" where it once said "make no folder": it makes and removes + `openreposhape-branch-*` (and `openreposhape-ident-*` where an identity + variable is set), creates no remote or repository and pushes nothing. The + wording is `docs/cli.md`'s too, which `test_cli_reference.py` holds.""" + result = run_script(FAMILY, "init", "--help") + assert result.returncode == 0, result.stderr + said = help_entry(result.stdout, "--dry-run") + assert ("Whether each folder can be written in is asked of `os.access`, " + "which reads the mode bits and on Windows says yes for every " + "folder, so a quota, a Windows ACL or a read-only drive on " + "Windows can pass here and be refused by the real run. It leaves " + "nothing that stays: it makes and removes temporary folders, " + "creates no remote or repository and pushes nothing") in said, said + assert "make no folder" not in said + assert "its mode bits'" not in said, ( + "only the write question is `os.access`'s: a file in the way and a " + "folder's contents are not the mode bits'") + + # --- family.py init end to end: GitHub's refusal, by its documentation ------ #: A `gh` with no network that answers as GitHub's documentation says: a diff --git a/tests/test_scaffold_reuse_seeded_legs.py b/tests/test_scaffold_reuse_seeded_legs.py index 91301a31..7713ad32 100644 --- a/tests/test_scaffold_reuse_seeded_legs.py +++ b/tests/test_scaffold_reuse_seeded_legs.py @@ -378,6 +378,49 @@ def test_the_seed_note_says_github_only_on_github(scaffold_module, local, "T", branches, "main", local) == said +@pytest.mark.parametrize("local,ending", [ + pytest.param(False, "so that branch remains, and on GitHub it may be " + "the leg's default branch.", id="github-leg"), + pytest.param(True, "so that branch remains.", id="bare-repository-leg"), +]) +def test_the_note_a_re_run_prints_says_github_only_for_a_github_leg( + scaffold_module, monkeypatch, capsys, local, ending): + """RV1052d's NIT-A: `_reusable_remotes` hands `_seed_branch_note` the + `local` it was called with. Every run in this file that finds a seed only + on `seed/scaffold` is a `--local-remote-dir` run, and the unit test above + calls `_seed_branch_note` directly, so a call that always passed `True` + for `local` (RV1052d's R2c) printed no GitHub clause for a GitHub leg and + no test failed. Here `remote_is_empty` says the leg exists with commits + and `_held_seed` says its one commit is the seed, found on + `seed/scaffold` alone, and the NOTE that `_reusable_remotes` itself + prints is what is read: GitHub's ends with the default-branch clause, and + the bare repository's, which has no GitHub, does not. In-process, so it + runs on every platform.""" + seed = "0123456789abcdef0123456789abcdef01234567" + monkeypatch.setattr(scaffold_module, "remote_is_empty", + lambda repository: False) + monkeypatch.setattr(scaffold_module, "local_remote_is_empty", + lambda target: False) + monkeypatch.setattr(scaffold_module, "_held_seed", + lambda role, url, values, tracking, scratch: ( + seed, None, ("seed/scaffold",))) + reused = scaffold_module._reusable_remotes( + {"spec": "Atlas-spec"}, {"spec": "/remotes/Atlas-spec.git"}, + {"spec": "o/Atlas-spec"}, local=local, reuse_flag=True, values={}, + tracking="main") + assert reused == {"spec": seed} + notes = [line for line in capsys.readouterr().out.splitlines() + if line.startswith("NOTE the seed in")] + target = "/remotes/Atlas-spec.git" if local else "o/Atlas-spec" + assert notes == [ + f"NOTE the seed in {target} was found only on its branch " + "`seed/scaffold`: this run creates `main` at it and does not touch " + f"`seed/scaffold`, {ending}"], notes + assert notes[0].endswith(", and on GitHub it may be the leg's default " + "branch.") is (not local) + assert ("GitHub" in notes[0]) is (not local) + + def test_dry_run_help_says_the_mode_bits_and_nothing_that_stays(tmp_path): """#923 U063 and #921 U088, the scaffold's half: `--dry-run`'s help says the write question is asked of `os.access`, which on Windows says yes for From d996ebf1ed71daad26538ab3b8d893221299b6c2 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 20 Oct 2026 16:19:52 +0000 Subject: [PATCH 2/2] scripts/family.py, scripts/shape_materialize.py and tests/test_scaffold_reuse_seeded_legs.py: the family's `--dry-run` words name one `openreposhape-ident-*` folder per role (author for GIT_AUTHOR_NAME, _EMAIL or _DATE, committer for a GIT_COMMITTER_ one), `folder_in_the_way`'s docstring says the real `mkdir` can still be refused on Windows where `os.access` says yes, and the bare-repository seed NOTE test compares the path as `str(Path(...))` prints it on every platform (#923, #921) Review round on #1063 (RV1063): SHOULD-1, NIT-1 and NIT-2. Co-Authored-By: Claude Sonnet 5.5 --- scripts/family.py | 11 ++++++----- scripts/shape_materialize.py | 3 ++- tests/test_scaffold_reuse_seeded_legs.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/family.py b/scripts/family.py index 629e2f9c..6aebe7c2 100755 --- a/scripts/family.py +++ b/scripts/family.py @@ -102,11 +102,12 @@ `init --dry-run` LEAVES NOTHING THAT STAYS, which is not the same as making no folder (#921). It makes and removes temporary folders, as the real run does: `openreposhape-branch-*`, where git is asked whether the tracking -branch is a name it will create, and `openreposhape-ident-*`, one each for -the author and the committer when a GIT_AUTHOR_* or GIT_COMMITTER_* variable -is set and not empty. Each is empty, and a removal that fails is not -reported. It creates no remote or repository and pushes nothing: the only -`gh` calls it makes read (`gh repo view`, the commit listing). +branch is a name it will create, and `openreposhape-ident-*`, one for the +author when GIT_AUTHOR_NAME, _EMAIL or _DATE is set and not empty, and one +for the committer when a GIT_COMMITTER_ one is. Each is empty, and a removal +that fails is not reported. It creates no remote or repository and pushes +nothing: the only `gh` calls it makes read (`gh repo view`, the commit +listing). WHETHER EACH FOLDER CAN BE WRITTEN IN IS THE MODE BITS' ANSWER (#923). The family folder or `--work-dir`, the holder in it and `--local-remote-dir` are diff --git a/scripts/shape_materialize.py b/scripts/shape_materialize.py index d7f897cb..fef3dbd4 100644 --- a/scripts/shape_materialize.py +++ b/scripts/shape_materialize.py @@ -1618,7 +1618,8 @@ def folder_in_the_way(path: Path) -> tuple[Path, str] | None: `path` and the folders above it that exists is the one that answers. A file there, or above it, is named, and so is a folder this user cannot write in (the system's own answer, `os.access`, where it has one; it - says yes for root, and for any folder on Windows, as `mkdir` would). A + says yes for root, and for any folder on Windows, where the real `mkdir` + can still be refused: an ACL, a quota). A `path` nothing stands at is asked of the folder above it. `folder_cannot_be_made` says it as one sentence, naming the folder as diff --git a/tests/test_scaffold_reuse_seeded_legs.py b/tests/test_scaffold_reuse_seeded_legs.py index 7713ad32..83e7c73c 100644 --- a/tests/test_scaffold_reuse_seeded_legs.py +++ b/tests/test_scaffold_reuse_seeded_legs.py @@ -411,7 +411,7 @@ def test_the_note_a_re_run_prints_says_github_only_for_a_github_leg( assert reused == {"spec": seed} notes = [line for line in capsys.readouterr().out.splitlines() if line.startswith("NOTE the seed in")] - target = "/remotes/Atlas-spec.git" if local else "o/Atlas-spec" + target = str(Path("/remotes/Atlas-spec.git")) if local else "o/Atlas-spec" assert notes == [ f"NOTE the seed in {target} was found only on its branch " "`seed/scaffold`: this run creates `main` at it and does not touch "