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
291 changes: 177 additions & 114 deletions templates/family-root/scripts/validate-family.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion tests/test_family_add_refusals.py
Original file line number Diff line number Diff line change
Expand Up @@ -1928,7 +1928,8 @@ def test_the_validator_finds_two_sections_for_one_path(family, holder,
"path, `members/IRRS`) and take each other out by hand, `git "
"config -f .gitmodules --unset-all submodule.<name>.<key>` for "
"every key that section holds, so a submodule later given its "
"name inherits none of them, then commit .gitmodules."], extra
"name inherits none of them, then stage and commit "
".gitmodules."], extra


def test_a_gitmodules_git_cannot_read_is_a_refusal_not_a_pass(holder):
Expand Down
40 changes: 23 additions & 17 deletions tests/test_family_gitmodules_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
)
from test_family_member_path import MEMBER, OTHER, raw
from test_family_tracking_branch import on_main # noqa: F401
from test_validate_family_typed_states import ROW, removal_staged
from test_validate_family_typed_states import GONE, ROW, removal_staged
from test_validate_family_unnamed_mounts import commands, findings

#: The issue's table, `update = None` first, then `branch` with no value:
Expand Down Expand Up @@ -185,17 +185,20 @@ def said(key: str, value: str | None, where: str) -> str:


def taken_out(key: str, value: str | None, *, back: str | None = None,
where: str = HERE_AND_HEAD, put_back: bool = False) -> str:
where: str = HERE_AND_HEAD, put_back: str = "") -> str:
"""`gitmodules-value-refused` WHOLE, with the commands that take the key
out of the work tree's file, after the one that puts HEAD's back where
`put_back`, and write `back` in where it is given."""
lines = (["git checkout HEAD -- .gitmodules"] if put_back else []) + [
out of the work tree's file, after the one that puts back the file git
reads in its place where the work tree's is gone as `put_back`
(`GONE`) says, the index's, or HEAD's where the index holds none
(#1077), and write `back` in where it is given."""
whose, line = GONE[put_back] if put_back else ("", "")
lines = ([line] if put_back else []) + [
f"git config -f .gitmodules --unset-all {full(key)}"]
if back is not None:
lines.append(f"git config -f .gitmodules {full(key)} {back}")
return (said(key, value, where)
+ ("The work tree holds no .gitmodules, so the first line below "
"puts HEAD's back. " if put_back else "")
f"puts back {whose}. " if put_back else "")
+ "Take the key out"
+ (f" and write its {key} back" if back is not None else "")
+ " by hand, in a POSIX shell"
Expand Down Expand Up @@ -1094,10 +1097,11 @@ def test_a_value_git_dies_on_in_the_index_s_file_is_named_and_mended(
family, holder, offline, tmp_path):
"""`update = None` committed, and the work tree's file lost, not
committed: git reads the index's and dies all the same, and bootstrap
names it. The gate reads HEAD's, and, the work tree holding none, its
first line puts HEAD's back before the key is taken out. Typed as
printed and committed, the same run checks both members out and the
gate passes in every mode."""
names it. The gate reads the index's too, as git reads it, and HEAD's,
which both give it, and, the work tree holding none, its first line
puts the index's back before the key is taken out, not HEAD's (#1077).
Typed as printed and committed, the same run checks both members out
and the gate passes in every mode."""
env = fetching(isolated(offline, tmp_path))
clone, _ = edited_clone(holder, env, family, tmp_path,
with_line("update = None"))
Expand All @@ -1113,16 +1117,18 @@ def test_a_value_git_dies_on_in_the_index_s_file_is_named_and_mended(
failed_on(MEMBER, OTHER)], result.stderr
found_in_every_mode_and_typed(
clone, env,
taken_out("update", "None", where=HEAD_ONLY, put_back=True))
taken_out("update", "None", put_back="deleted"))
assert_a_good_holder_s_run(clone, env)


def test_two_keys_with_no_gitmodules_in_the_work_tree_put_head_s_back_once(
def test_two_keys_with_no_gitmodules_in_the_work_tree_put_it_back_once(
holder, offline, tmp_path):
"""Two values git dies on committed, and the work tree's file lost: one
finding for each key, and only the first puts HEAD's back, since a
second `git checkout` would put back the key the first took out. Typed
as printed and committed, the holder passes in every mode."""
finding for each key, and only the first puts the index's back, the
file git reads, which gives both, as HEAD's does (#1077: it put HEAD's
back), since a second `git checkout` would put back the key the first
took out. Typed as printed and committed, the holder passes in every
mode."""
env = isolated(offline, tmp_path)
data = raw(holder, env, "show", f"HEAD:{GITMODULES}").stdout
# Each line goes in right after the url, so `update` comes first.
Expand All @@ -1134,8 +1140,8 @@ def test_two_keys_with_no_gitmodules_in_the_work_tree_put_head_s_back_once(

assert result.returncode == 1, result.stdout + result.stderr
assert findings(result) == [
taken_out("update", "None", where=HEAD_ONLY, put_back=True),
taken_out("shallow", "maybe", where=HEAD_ONLY)]
taken_out("update", "None", put_back="deleted"),
taken_out("shallow", "maybe")]
for finding in findings(result):
for command in commands(finding):
type_line(holder, env, command)
Expand Down
43 changes: 28 additions & 15 deletions tests/test_family_hand_mount_git_rm_lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@
)
from test_validate_family_typed_states import GONE, ROW, gone
from test_validate_family_unnamed_mounts import (
SECTION_REMEDY, SECTION_SAID, SECTION_UNDER_GIT_RM, VARIANT, commands,
removal, with_section,
SECTION_REMEDY, SECTION_SAID, SECTION_UNDER_GIT_RM, STAGE, VARIANT,
commands, removal, with_section,
)

HAND = f"members/{SPARE}"
Expand Down Expand Up @@ -582,17 +582,18 @@ def ghost_out(how: str, *, put_back: bool) -> str:
`Members/IRRS`, a case variant of MEMBER's path, url
`/nowhere/ghost.git`, with the work tree's `.gitmodules` gone as `how`
(`GONE`) says: its keys' lines, after the line that puts the file back
where `put_back`."""
where `put_back`, then the line that stages the file, since HEAD's
holds the section (#1077, RV1069's NIT 3 on #1069)."""
line = GONE[how][1]
return (SECTION_SAID.format(path="Members/IRRS", name="ghost",
url="url /nowhere/ghost.git")
+ VARIANT.format(row_path=ROW, project="IRRS")
+ " Take the section out by hand, in a POSIX shell, every key it "
"holds, so a submodule later given its name inherits none of "
"them, " + (put_back_said(how) if put_back else "")
+ "then commit .gitmodules:"
+ "then stage and commit .gitmodules:"
+ lines_of(*([line] if put_back else []),
*removal("ghost", "path", "url")))
*removal("ghost", "path", "url"), STAGE))


def test_1036_a_section_the_index_alone_gives_comes_out_of_the_file_put_back(
Expand Down Expand Up @@ -631,10 +632,9 @@ def test_1036_a_section_the_index_alone_gives_comes_out_of_the_file_put_back(
put_back = GONE["deleted"][1]
assert found[1] == (
SECTION_SAID.format(path=path, name=path, url=f"url {url}")
+ SECTION_REMEDY.replace("then commit .gitmodules:",
+ SECTION_REMEDY.replace("then stage and commit .gitmodules:",
put_back_said("deleted") + THEN_STAGE + ":")
+ lines_of(put_back, *removal(path, "path", "url"),
"git add -- .gitmodules"))
+ lines_of(put_back, *removal(path, "path", "url"), STAGE))


def test_1036_outside_members_the_gitlink_goes_first_then_the_put_back(
Expand Down Expand Up @@ -719,7 +719,8 @@ def test_1036_one_put_back_serves_two_sections(family, holder, offline,
ghost_out("deleted", put_back=True),
SECTION_SAID.format(path="members/Old", name="members/Old",
url="url /nowhere/old.git")
+ SECTION_REMEDY + lines_of(*removal("members/Old", "path", "url"))]
+ SECTION_REMEDY + lines_of(*removal("members/Old", "path", "url"),
STAGE)]
typed_in_order(holder, env, validate(holder, "--no-members"))
staged_and_committed(holder, env, "Typed in order")
assert raw(holder, env, "status", "--porcelain").stdout == b""
Expand Down Expand Up @@ -765,13 +766,16 @@ def test_1036_control_a_value_git_dies_on_keeps_what_it_read(
"""#1036's control: `update = None` committed in IRSS's section, a value
git dies on, then IRQS's gitlink staged over a directory holding a file,
its section staged in the index's `.gitmodules` alone, and the work
tree's file deleted. The value's lines put HEAD's file back and stage
it, which drops that section, so `gitmodules-path-unnamed` reads nothing
from the file git reads there, as before: its `--unset-all` of a key the
file put back does not hold exited 5. Typed in order, what is on disk
tree's file deleted. Git reads the index's file, which gives the value,
so `gitmodules-path-unnamed` reads nothing from it, as git reads
nothing, and as with the file on disk. The value's lines put back the
index's file now, the one git reads, not HEAD's, which dropped IRQS's
section (#1077), and take the key out. Typed in order, what is on disk
deleted as the text says, then staged and committed as the value's
finding says, nothing is left, the gate passes in every mode and a
plain clone checks both out."""
finding says, HEAD holds IRQS's section, as with the file on disk, and
the next run names it in every mode. Its lines typed, then a plain `git
commit`, nothing is left, the gate passes in every mode and a plain
clone checks both out."""
env = cloning(offline, tmp_path, family)
value_committed(holder, env, OTHER_ROW)
path = staged_by_hand(holding_a_file)(holder, env, family)
Expand All @@ -788,8 +792,17 @@ def test_1036_control_a_value_git_dies_on_keeps_what_it_read(

assert codes_of(found) == ["gitmodules-value-refused",
"member-gitlink-unnamed"]
assert commands(found[0])[0] == GONE["deleted"][1]
typed_around(holder, env, found, lambda: deleted(holder / path))
staged_and_committed(holder, env, "Typed in order")
assert head_paths(holder, env) == {ROW, OTHER_ROW, path}
assert found_alike(holder, *MODES) == [
SECTION_SAID.format(
path=path, name=path,
url=f"url https://github.com/InkRouter/{SPARE}.git")
+ SECTION_REMEDY + lines_of(*removal(path, "path", "url"), STAGE)]
typed_in_order(holder, env, validate(holder, "--no-members"))
what_is_staged_committed(holder, env)
assert raw(holder, env, "status", "--porcelain").stdout == b""
assert head_paths(holder, env) == {ROW, OTHER_ROW}
ends_well(holder, env, family, tmp_path)
15 changes: 8 additions & 7 deletions tests/test_family_holder_bootstrap_texts.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,14 +991,16 @@ def test_two_findings_that_write_the_file_put_it_back_once(
assert_passes_in_every_mode(holder, 2)


def test_a_value_finding_that_put_head_s_back_is_not_put_back_over(
def test_a_value_finding_that_put_the_file_back_is_not_put_back_over(
holder, offline, tmp_path):
"""No second put back after `gitmodules-value-refused`'s: MEMBER's
section given `update = None`, OTHER's taken out, committed, and the
work tree's file deleted. The value's finding puts HEAD's back first
(#768) and takes the key out; OTHER's `member-gitmodules-absent` then
writes into that file, and puts nothing back over the key it took
out. Typed as printed and committed, the gate passes in every mode."""
work tree's file deleted. The value's finding puts the index's back
first, the file git reads, which gives the value as HEAD's does (#768;
HEAD's until #1077), and takes the key out; OTHER's
`member-gitmodules-absent` then writes into that file, and puts nothing
back over the key it took out. Typed as printed and committed, the gate
passes in every mode."""
env = isolated(offline, tmp_path)
data = raw(holder, env, "show", "HEAD:.gitmodules").stdout
line = f"\tpath = members/{MEMBER}\n".encode()
Expand All @@ -1012,8 +1014,7 @@ def test_a_value_finding_that_put_head_s_back_is_not_put_back_over(
assert result.returncode == 1, result.stdout + result.stderr
assert codes(result) == ["gitmodules-value-refused",
"member-gitmodules-absent"]
assert commands(findings(result)[0])[0] == (
"git checkout HEAD -- .gitmodules")
assert commands(findings(result)[0])[0] == "git checkout -- .gitmodules"
assert findings(result)[1] == absent(OTHER, pinned(holder, OTHER),
held=True)
typed_and_committed(holder, env, result)
Expand Down
18 changes: 10 additions & 8 deletions tests/test_family_holder_remedies_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,13 +1334,15 @@ def test_round1_nit_6_the_value_s_put_back_serves_the_removal_after_it(
family, holder, offline, tmp_path):
"""Lane 1's NIT 6 on #889 (its mutant MX1): `update = None` committed
in IRSS's section, `git rm --cached members/IRRS`, and the work tree's
`.gitmodules` deleted. The value finding puts back HEAD's file and
records that, so the removal after it prints no line for the file,
which HEAD's gives MEMBER's section in. Put back again from the
index's, which holds the value, it undid the fix, and the commit died
(`invalid value for 'submodule.members/IRSS.update'`, exit 128).
Typed in order and committed, the gate passes in every mode and a
plain clone checks both out."""
`.gitmodules` deleted. The value finding puts back the index's file,
the one git reads, which gives the value as HEAD's does (#1077: it put
back HEAD's), and records that, so the removal after it prints no line
for the file, which gives MEMBER's section. Put back again from the
index's after the value's lines, which hold the value, it undid the
fix, and the commit died (`invalid value for
'submodule.members/IRSS.update'`, exit 128). Typed in order and
committed, the gate passes in every mode and a plain clone checks both
out."""
env = cloning(offline, tmp_path, family)
value_committed(holder, env, OTHER_ROW)
raw(holder, env, "rm", "-q", "--cached", "--", ROW)
Expand All @@ -1350,7 +1352,7 @@ def test_round1_nit_6_the_value_s_put_back_serves_the_removal_after_it(

assert codes_of(found) == ["gitmodules-value-refused",
"member-gitlink-removal-staged"]
assert commands(found[0])[0] == "git checkout HEAD -- .gitmodules"
assert commands(found[0])[0] == "git checkout -- .gitmodules"
assert commands(found[1]) == [f"git reset -q -- {ROW}"]
typed_in_order(holder, env, validate(holder, "--no-members"))
staged_and_committed(holder, env, "Typed in order")
Expand Down
23 changes: 13 additions & 10 deletions tests/test_family_unnamed_gitlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
from test_family_member_path import MEMBER, OTHER, raw
from test_validate_family_remedies_typed import STAGED
from test_validate_family_unnamed_mounts import (
SECTION_REMEDY, SECTION_SAID, codes, commands, findings, removal,
SECTION_REMEDY, SECTION_SAID, STAGE, codes, commands, findings, removal,
spare_commits, stage_gitlink, with_section,
)

Expand Down Expand Up @@ -133,9 +133,11 @@
"hand, in a POSIX shell, every key it holds, so a submodule "
"later given its name inherits none of them, {then}:")

#: How the section's text ends: the commit, or, where HEAD's `.gitmodules`
#: holds no section of its name, the file staged and nothing to commit.
THEN_COMMIT = "then commit .gitmodules"
#: How the section's text ends: the file staged and committed, or, where
#: HEAD's `.gitmodules` holds no section of its name, staged and nothing to
#: commit. Its lines end `git add -- .gitmodules` either way (#1077,
#: RV1069's NIT 3 on #1069).
THEN_COMMIT = "then stage and commit .gitmodules"
THEN_STAGE = ("then stage .gitmodules: HEAD's holds no section of that name, "
"so nothing of it is left to commit")

Expand All @@ -159,7 +161,8 @@ def section_first(path: str, name: str, url: str, said: str, then: str,
"""`gitmodules-path-unnamed` whole for the section `name`, giving `path`
and `url`, at a gitlink outside `members/`: its `git rm --cached`,
carrying `flag`, after `git add -- .gitmodules` where `stage`, of which
it says `said`, then its own lines, and `then`."""
it says `said`, then its own lines, and `then`, the lines ending `git
add -- .gitmodules` (#1077)."""
nth = "the second line" if stage else "the first line"
return (SECTION_SAID.format(path=path, name=name, url=f"url {url}")
+ ROW_BACK + GITLINK_FIRST + (STAGE_SAID if stage else "")
Expand All @@ -168,7 +171,7 @@ def section_first(path: str, name: str, url: str, said: str, then: str,
+ f"\n git rm --cached{flag} -- {path}"
+ "".join(f"\n {line}" for line in removal(name, "path",
"url"))
+ (STAGE_LINE if then.endswith(THEN_STAGE) else ""))
+ STAGE_LINE)


def found_in_every_mode(root: Path, code: str) -> str:
Expand Down Expand Up @@ -501,8 +504,8 @@ def test_two_sections_for_one_gitlink_outside_members_print_one_git_rm(
"gitmodules-path-unnamed"], result.stderr
first, second = findings(result)[1:]
assert commands(first) == [f"git rm --cached -- {TOOLS}",
*removal(TOOLS, "path", "url")]
assert commands(second) == removal("second", "path", "url")
*removal(TOOLS, "path", "url"), STAGE]
assert commands(second) == [*removal("second", "path", "url"), STAGE]
env = isolated(offline, tmp_path)
for finding in (first, second):
typed(holder, env, finding)
Expand Down Expand Up @@ -541,8 +544,8 @@ def test_a_section_with_no_gitlink_in_the_index_prints_its_lines_alone(

assert finding == (
SECTION_SAID.format(path=TOOLS, name=TOOLS, url="url /nowhere/x.git")
+ SECTION_REMEDY + "".join(f"\n {line}" for line in removal(
TOOLS, "path", "url")))
+ SECTION_REMEDY + "".join(f"\n {line}" for line in [*removal(
TOOLS, "path", "url"), STAGE]))
typed(holder, env, finding)
staged_and_committed(holder, env, "Typed as printed")
assert_passes_in_every_mode(holder, 2)
Loading
Loading