Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -956,8 +956,14 @@ options:
--work-dir, the holder in it, --local-remote-dir) and
whether <org>/<Family> 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`
Expand Down
30 changes: 27 additions & 3 deletions scripts/family.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -99,6 +99,23 @@
`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 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
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,
Expand Down Expand Up @@ -10452,8 +10469,15 @@ def build_parser() -> argparse.ArgumentParser:
"--local-remote-dir) and whether <org>/<Family> "
"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(
Expand Down
3 changes: 2 additions & 1 deletion scripts/shape_materialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions tests/test_github_limits_init_check_org.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions tests/test_scaffold_reuse_seeded_legs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 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 "
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
Expand Down
Loading