diff --git a/park b/park
index 132ce6db..65e8cac4 100755
--- a/park
+++ b/park
@@ -130,7 +130,8 @@ USAGE
# first time somebody copied only one of them. The rules themselves are NOT
# new: `
//family.yaml` carrying `kind: family-manifest` and naming
# `` is `setup-project.py`'s `family_folder_here`, doubled-name layout
-# and all (#76, RULING ruling 3).
+# and all (#76, RULING ruling 3). What "naming" accepts is widened by #236,
+# at `is_family_folder` below: the display `name:` or the repository's name.
# `$PROJECTS_DIR` names ONE directory when it is set. Otherwise both
# spellings, in that order, because people really do use both - and DEDUPED by
@@ -199,7 +200,8 @@ is_safe_folder_name() {
# One indent-zero scalar out of a small YAML file, with parameter expansion
# only: no YAML library, no python per line, no forks per line. This is the
# reader the Speckit extension's `workspace_read_scalar` is, narrowed to the
-# keys these two commands read (`kind`, `name`, `family`, `tracking_branch`).
+# keys these two commands read (`kind`, `name`, `family`, `tracking_branch`,
+# `repository`).
YAML_VALUE=""
yaml_scalar() {
local file="$1" key="$2" line
@@ -251,11 +253,19 @@ is_pinned_member() {
[ "$YAML_VALUE" = "family-manifest" ]
}
-# Is `` a FAMILY FOLDER? All three facts have to agree, exactly as
-# `family_folder_here` requires them to: the holder clone sits at
-# `/`, its `family.yaml` says `kind: family-manifest`, and it
-# names that same family. A directory called Northwind is a directory called
-# Northwind; what makes it a family folder is read, never guessed from a name.
+# Is `` a FAMILY FOLDER? All three facts have to agree: the holder clone
+# sits at `/`, its `family.yaml` says `kind: family-manifest`,
+# and the folder is named for the family. A family has TWO names and either
+# one is the folder's (#236): its display `name:`, which is the rule this
+# block began with and what an estate laid out before openRepoShape v1.1.0
+# has, or the LAST PART of its `repository:`, which is v1.1.0's rule - the
+# holder, and the folder around it, are named for the REPOSITORY, `name:`
+# being only a title (opensoft/openRepoShape#629), and the holder's `make
+# siblings` stops before cloning when the folder is not that name
+# (opensoft/openRepoShape#543). A family whose two names differ has no layout
+# that works for both unless both are accepted. A directory called Northwind
+# is a directory called Northwind; what makes it a family folder is read,
+# never guessed from a name.
is_family_folder() {
local dir="$1" name manifest
name="${dir##*/}"
@@ -263,8 +273,11 @@ is_family_folder() {
manifest="$dir/$name/family.yaml"
yaml_scalar "$manifest" kind || return 1
[ "$YAML_VALUE" = "family-manifest" ] || return 1
- yaml_scalar "$manifest" name || return 1
- [ "$YAML_VALUE" = "$name" ]
+ if yaml_scalar "$manifest" name && [ "$YAML_VALUE" = "$name" ]; then
+ return 0
+ fi
+ yaml_scalar "$manifest" repository || return 1
+ [ "${YAML_VALUE##*/}" = "$name" ]
}
is_project_root() {
diff --git a/resume b/resume
index 92fd4303..7f874004 100755
--- a/resume
+++ b/resume
@@ -113,7 +113,8 @@ USAGE
# first time somebody copied only one of them. The rules themselves are NOT
# new: `//family.yaml` carrying `kind: family-manifest` and naming
# `` is `setup-project.py`'s `family_folder_here`, doubled-name layout
-# and all (#76, RULING ruling 3).
+# and all (#76, RULING ruling 3). What "naming" accepts is widened by #236,
+# at `is_family_folder` below: the display `name:` or the repository's name.
# `$PROJECTS_DIR` names ONE directory when it is set. Otherwise both
# spellings, in that order, because people really do use both - and DEDUPED by
@@ -182,7 +183,8 @@ is_safe_folder_name() {
# One indent-zero scalar out of a small YAML file, with parameter expansion
# only: no YAML library, no python per line, no forks per line. This is the
# reader the Speckit extension's `workspace_read_scalar` is, narrowed to the
-# keys these two commands read (`kind`, `name`, `family`, `tracking_branch`).
+# keys these two commands read (`kind`, `name`, `family`, `tracking_branch`,
+# `repository`).
YAML_VALUE=""
yaml_scalar() {
local file="$1" key="$2" line
@@ -234,11 +236,19 @@ is_pinned_member() {
[ "$YAML_VALUE" = "family-manifest" ]
}
-# Is `` a FAMILY FOLDER? All three facts have to agree, exactly as
-# `family_folder_here` requires them to: the holder clone sits at
-# `/`, its `family.yaml` says `kind: family-manifest`, and it
-# names that same family. A directory called Northwind is a directory called
-# Northwind; what makes it a family folder is read, never guessed from a name.
+# Is `` a FAMILY FOLDER? All three facts have to agree: the holder clone
+# sits at `/`, its `family.yaml` says `kind: family-manifest`,
+# and the folder is named for the family. A family has TWO names and either
+# one is the folder's (#236): its display `name:`, which is the rule this
+# block began with and what an estate laid out before openRepoShape v1.1.0
+# has, or the LAST PART of its `repository:`, which is v1.1.0's rule - the
+# holder, and the folder around it, are named for the REPOSITORY, `name:`
+# being only a title (opensoft/openRepoShape#629), and the holder's `make
+# siblings` stops before cloning when the folder is not that name
+# (opensoft/openRepoShape#543). A family whose two names differ has no layout
+# that works for both unless both are accepted. A directory called Northwind
+# is a directory called Northwind; what makes it a family folder is read,
+# never guessed from a name.
is_family_folder() {
local dir="$1" name manifest
name="${dir##*/}"
@@ -246,8 +256,11 @@ is_family_folder() {
manifest="$dir/$name/family.yaml"
yaml_scalar "$manifest" kind || return 1
[ "$YAML_VALUE" = "family-manifest" ] || return 1
- yaml_scalar "$manifest" name || return 1
- [ "$YAML_VALUE" = "$name" ]
+ if yaml_scalar "$manifest" name && [ "$YAML_VALUE" = "$name" ]; then
+ return 0
+ fi
+ yaml_scalar "$manifest" repository || return 1
+ [ "${YAML_VALUE##*/}" = "$name" ]
}
is_project_root() {
@@ -1161,8 +1174,9 @@ if [ -n "$MANIFEST_FILE" ]; then
# from `family.yaml`'s `id:` — `workspace_family_name` reads that key
# — and an id is LOWERCASE by construction (`family.py`'s
# `--id ... or family.lower()`, checked against
- # `PROJECT_ID_RE = ^[a-z0-9][a-z0-9-]*$`). The FOLDER is the family's
- # `name:`, CamelCase, which is what `is_family_folder` requires to
+ # `PROJECT_ID_RE = ^[a-z0-9][a-z0-9-]*$`). The FOLDER is one of the
+ # family's CamelCase names - its repository's last part, or its
+ # display `name:` - which is what `is_family_folder` requires to
# match the directory. Deriving the folder from `family:` therefore
# produced `~/projects/inkrouter/inkrouter`, a clone of
# `/inkrouter` that 404s on any case-sensitive remote, and then a
@@ -1171,9 +1185,18 @@ if [ -n "$MANIFEST_FILE" ]; then
# `workspace_manifest_root_value` writes each MEMBER's `root:` as
# `/` with the real casing, so the
# first segment is the family folder and — the holder repository being
- # the family's own name in its org, #76 ruling 3's doubled-name layout
- # — it is the holder repository's name too. `family:` stays what it is
- # good for: the flag that says this file is a family's at all.
+ # the family's own name in its org, #76 ruling 3's doubled-name layout,
+ # and v1.1.0's rule (opensoft/openRepoShape#629) — it is the holder
+ # repository's name too. `family:` stays what it is good for: the flag
+ # that says this file is a family's at all.
+ #
+ # THE RECORD NEVER CARRIES THE HOLDER'S REPOSITORY (#236): it has the
+ # id in `family:`, each member's OWN `repository:` and `root:`, and is
+ # filed under the holder's org. So the repository is derived from that
+ # org and the folder, which is exact under v1.1.0 and is also what a
+ # fresh machine, with no `family.yaml` to read yet, has to go on. A
+ # holder this machine already has says which repository it is, and
+ # that is read below, from the estate this run resolved.
MANIFEST_DIRNAME="${MANIFEST_ROOT_VALUE%%/*}"
[ -n "$MANIFEST_ROOT_VALUE" ] &&
[ "$MANIFEST_DIRNAME" != "$MANIFEST_ROOT_VALUE" ] ||
@@ -1202,7 +1225,6 @@ if [ -n "$MANIFEST_FILE" ]; then
'$MANIFEST_DIRNAME' is not one: an estate folder is a single name under your
projects directory, with no slash and no leading dot. Nothing was cloned.
That file is written by \`park\`; a hand-edited one is fixed by hand."
- say " records a $MANIFEST_KIND estate, root repository $ROOT_REPOSITORY"
fi
# THE ESTATE ON DISK WINS over the manifest's idea of where it is: the manifest
@@ -1215,6 +1237,23 @@ if [ "$ESTATE_PRESENT" -eq 0 ] && [ -n "$MANIFEST_DIRNAME" ] &&
fi
fi
+# WHAT THE RECORD SAYS THE ESTATE IS, said once the estate on disk is known,
+# because a holder that is here knows its own repository (#236): the HOLDER
+# THIS RUN RESOLVED, whatever the record's folder is. A record goes stale the
+# moment a person moves the holder to the folder v1.1.0 names (`make
+# siblings`' own `mv`), and a lookup by the record's folder would then miss a
+# holder that is on disk and print a repository that does not exist. Nothing
+# above prints, so the order of the lines a run says is unchanged.
+if [ -n "$MANIFEST_FILE" ]; then
+ if [ "$MANIFEST_KIND" = "family" ] && [ "$ESTATE_PRESENT" -eq 1 ] &&
+ [ "$ESTATE_KIND" = "family" ] &&
+ yaml_scalar "$ESTATE_ROOT/family.yaml" repository &&
+ [ -n "$YAML_VALUE" ]; then
+ ROOT_REPOSITORY="$YAML_VALUE"
+ fi
+ say " records a $MANIFEST_KIND estate, root repository $ROOT_REPOSITORY"
+fi
+
# --- (3) rebuild or refresh the estate --------------------------------------
say ""
say "(3) the estate"
diff --git a/status b/status
index bb5c2559..c2f65796 100755
--- a/status
+++ b/status
@@ -251,7 +251,8 @@ USAGE
# first time somebody copied only one of them. The rules themselves are NOT
# new: `//family.yaml` carrying `kind: family-manifest` and naming
# `` is `setup-project.py`'s `family_folder_here`, doubled-name layout
-# and all (#76, RULING ruling 3).
+# and all (#76, RULING ruling 3). What "naming" accepts is widened by #236,
+# at `is_family_folder` below: the display `name:` or the repository's name.
# `$PROJECTS_DIR` names ONE directory when it is set. Otherwise both
# spellings, in that order, because people really do use both - and DEDUPED by
@@ -320,7 +321,8 @@ is_safe_folder_name() {
# One indent-zero scalar out of a small YAML file, with parameter expansion
# only: no YAML library, no python per line, no forks per line. This is the
# reader the Speckit extension's `workspace_read_scalar` is, narrowed to the
-# keys these two commands read (`kind`, `name`, `family`, `tracking_branch`).
+# keys these two commands read (`kind`, `name`, `family`, `tracking_branch`,
+# `repository`).
YAML_VALUE=""
yaml_scalar() {
local file="$1" key="$2" line
@@ -372,11 +374,19 @@ is_pinned_member() {
[ "$YAML_VALUE" = "family-manifest" ]
}
-# Is `` a FAMILY FOLDER? All three facts have to agree, exactly as
-# `family_folder_here` requires them to: the holder clone sits at
-# `/`, its `family.yaml` says `kind: family-manifest`, and it
-# names that same family. A directory called Northwind is a directory called
-# Northwind; what makes it a family folder is read, never guessed from a name.
+# Is `` a FAMILY FOLDER? All three facts have to agree: the holder clone
+# sits at `/`, its `family.yaml` says `kind: family-manifest`,
+# and the folder is named for the family. A family has TWO names and either
+# one is the folder's (#236): its display `name:`, which is the rule this
+# block began with and what an estate laid out before openRepoShape v1.1.0
+# has, or the LAST PART of its `repository:`, which is v1.1.0's rule - the
+# holder, and the folder around it, are named for the REPOSITORY, `name:`
+# being only a title (opensoft/openRepoShape#629), and the holder's `make
+# siblings` stops before cloning when the folder is not that name
+# (opensoft/openRepoShape#543). A family whose two names differ has no layout
+# that works for both unless both are accepted. A directory called Northwind
+# is a directory called Northwind; what makes it a family folder is read,
+# never guessed from a name.
is_family_folder() {
local dir="$1" name manifest
name="${dir##*/}"
@@ -384,8 +394,11 @@ is_family_folder() {
manifest="$dir/$name/family.yaml"
yaml_scalar "$manifest" kind || return 1
[ "$YAML_VALUE" = "family-manifest" ] || return 1
- yaml_scalar "$manifest" name || return 1
- [ "$YAML_VALUE" = "$name" ]
+ if yaml_scalar "$manifest" name && [ "$YAML_VALUE" = "$name" ]; then
+ return 0
+ fi
+ yaml_scalar "$manifest" repository || return 1
+ [ "${YAML_VALUE##*/}" = "$name" ]
}
is_project_root() {
diff --git a/tests/test_park_resume_commands.py b/tests/test_park_resume_commands.py
index 8cf874fe..5430924b 100644
--- a/tests/test_park_resume_commands.py
+++ b/tests/test_park_resume_commands.py
@@ -132,6 +132,31 @@
path: "."
"""
+#: A FAMILY WHOSE DISPLAY `name:` IS NOT ITS REPOSITORY'S NAME (#236).
+#: `family.py init --family Quill --name QuillWorks` writes exactly this:
+#: `name: "QuillWorks"` beside `repository: testorg/Quill`. `FAMILY_YAML`
+#: above always writes `repository: {org}/{name}`, so no test built on it can
+#: tell the two names apart. The holder is NAMED AFTER THE REPOSITORY under
+#: openRepoShape v1.1.0 (opensoft/openRepoShape#629) and `make siblings` stops
+#: before cloning when it is not (opensoft/openRepoShape#543).
+FAMILY_TWO_NAMES_YAML = """\
+schema_version: 1
+kind: family-manifest
+id: {id}
+name: "{name}"
+org: {org}
+repository: {repository}
+tracking_branch: main
+members_dir: members
+members:{members}
+"""
+
+#: The two names of that one family: the folder v1.1.0 prescribes, which is
+#: its repository's last part, and the display name the commands used to
+#: require instead.
+QUILL = "Quill"
+QUILL_DISPLAY = "QuillWorks"
+
# --- the environment every run gets ----------------------------------------
@@ -201,6 +226,32 @@ def probe_family(projects: Path, name: str) -> Path:
return holder
+def probe_family_two_names(projects: Path, folder: str, *, name: str,
+ repository: str | None,
+ member_repository: str | None = None) -> Path:
+ """A family folder like `probe_family`'s, but `family.yaml` carries a
+ display `name:` and a `repository:` of their own (#236), so the folder can
+ equal one, the other, neither. `repository=None` leaves the key out, and
+ `member_repository` adds one member whose own, INDENTED `repository:` is
+ that: a key that is never the holder's."""
+ holder = projects / folder / folder
+ holder.mkdir(parents=True)
+ rows = " []"
+ if member_repository is not None:
+ rows = (f"\n - project: Alpha\n id: alpha\n"
+ f" repository: {member_repository}\n"
+ f" path: members/Alpha\n")
+ text = FAMILY_TWO_NAMES_YAML.format(
+ id=folder.lower(), name=name, org=ORG, repository=repository,
+ members=rows)
+ if repository is None:
+ text = "".join(line for line in text.splitlines(keepends=True)
+ if not line.startswith("repository:"))
+ (holder / "family.yaml").write_text(text, encoding="utf-8")
+ (holder / "Makefile").write_text(PROBE_MAKEFILE, encoding="utf-8")
+ return holder
+
+
def probe_project(projects: Path, name: str, *, at: Path | None = None) -> Path:
"""A standalone project root with a probe Makefile."""
root = at if at is not None else projects / name
@@ -529,6 +580,87 @@ def estate(remotes, home) -> dict:
"siblings": siblings}
+@pytest.fixture(scope="session")
+def named_apart(tmp_path_factory, remotes) -> dict:
+ """The family of #236, whose display name is not its repository's name,
+ as bare repositories: the holder `Quill.git` and a workspace `wip.git`.
+
+ The holder is `remotes["holder"]` with ONLY `family.yaml` rewritten — `id:
+ quill`, `name: "QuillWorks"`, `repository: testorg/Quill` — so it still
+ carries the template bytes and the pinned members every other holder here
+ does. The bare is called `Quill.git` because that is what `resume` asks
+ for: `clone_url_for testorg/Quill`.
+
+ The workspace carries TWO records of it, one per folder a person might
+ have laid it out in. Each is `family_manifest`'s own, so `root:` is
+ `/` and the record says nothing about the holder's
+ repository: `family:` is the id, and the org is the directory it is filed
+ under.
+ """
+ base = tmp_path_factory.mktemp("namedapart")
+ seed = base / "seed" / QUILL
+ seed.parent.mkdir(parents=True)
+ git("-c", "protocol.file.allow=always", "clone", "-q",
+ "--recurse-submodules", str(remotes["holder"]), str(seed), cwd=base)
+ family_yaml = seed / "family.yaml"
+ text = family_yaml.read_text(encoding="utf-8")
+ rewritten = (text
+ .replace(f"id: {FAMILY.lower()}\n", f"id: {QUILL.lower()}\n")
+ .replace(f'name: "{FAMILY}"\n', f'name: "{QUILL_DISPLAY}"\n')
+ .replace(f"repository: {ORG}/{FAMILY}\n",
+ f"repository: {ORG}/{QUILL}\n"))
+ # A rewrite that matched nothing would leave `TestFam` in a fixture that
+ # claims not to be it, and every test below would be about the wrong file.
+ assert FAMILY not in rewritten, rewritten
+ assert (f'name: "{QUILL_DISPLAY}"\n' in rewritten
+ and f"repository: {ORG}/{QUILL}\n" in rewritten), rewritten
+ family_yaml.write_text(rewritten, encoding="utf-8")
+ commit_all(seed, "the family's display name is not its repository's")
+ holder = base / "remotes" / f"{QUILL}.git"
+ holder.parent.mkdir(exist_ok=True)
+ git("clone", "-q", "--bare", str(seed), str(holder), cwd=base)
+ workspace = seed_workspace(
+ base, "wip",
+ {f"workspaces/{ORG}/{QUILL.lower()}.yaml": family_manifest(QUILL),
+ f"workspaces/{ORG}/{QUILL_DISPLAY.lower()}.yaml":
+ family_manifest(QUILL_DISPLAY)})
+ return {"dir": base / "remotes", "holder": holder, "workspace": workspace}
+
+
+def clone_quill_estate(named_apart: dict, remotes: dict, home: Path,
+ folder: str) -> dict:
+ """`estate`'s own shape, for the family of #236 laid out in `folder`:
+ the holder at `/` and each member's working clone beside
+ it. `folder` is `QUILL` (v1.1.0's rule) or `QUILL_DISPLAY` (the rule
+ before it); the holder's own `family.yaml` is the same in both."""
+ projects = home / "projects"
+ where = projects / folder
+ where.mkdir(parents=True, exist_ok=True)
+ git("-c", "protocol.file.allow=always", "clone", "-q",
+ "--recurse-submodules", str(named_apart["holder"]),
+ str(where / folder), cwd=projects)
+ siblings = {}
+ for name in MEMBERS:
+ git("clone", "-q", str(remotes["members"][name]),
+ str(where / name), cwd=projects)
+ siblings[name] = where / name
+ return {"projects": projects, "folder": where, "holder": where / folder,
+ "siblings": siblings}
+
+
+def place_quill_workspace(named_apart: dict, home: Path,
+ bare: Path | None = None) -> None:
+ """The configured workspace, already cloned: what `resume` reads a record
+ out of on a machine that has resumed before (`behind`'s own setup).
+ `bare` is another workspace than `named_apart`'s own."""
+ config = home / ".agents" / "workspace.yaml"
+ config.parent.mkdir(parents=True)
+ config.write_text(f"repository: {WORKSPACE_SLUG}\npath: ~/projects/wip\n",
+ encoding="utf-8")
+ git("clone", "-q", str(bare or named_apart["workspace"]),
+ str(home / "projects" / "wip"), cwd=home)
+
+
# ===========================================================================
# estate resolution — the half both commands share
# ===========================================================================
@@ -569,6 +701,88 @@ def test_a_family_folder_wins_over_a_standalone_root_of_the_same_name(home):
assert f"PROBE park in {projects / 'Dual'} " not in result.stdout
+# --- a family folder is named for its repository OR its display name (#236) --
+#
+# `is_family_folder` required `family.yaml`'s display `name:` to equal the
+# folder, and openRepoShape v1.1.0 names the folder after the holder's
+# REPOSITORY instead (opensoft/openRepoShape#629), stopping `make siblings`
+# before it clones when it is not (opensoft/openRepoShape#543). A family whose
+# two names differ had no layout both could use. Either name now finds it.
+
+@pytest.mark.parametrize("command", [PARK, RESUME], ids=["park", "resume"])
+@pytest.mark.parametrize("folder", [QUILL, QUILL_DISPLAY],
+ ids=["repository", "name"])
+def test_a_family_folder_is_found_by_its_repository_name_or_its_display_name(
+ named_apart, remotes, home, command, folder):
+ """`Quill/Quill` is v1.1.0's layout and `QuillWorks/QuillWorks` the one the
+ commands required before it; one `family.yaml` (`name: "QuillWorks"`,
+ `repository: testorg/Quill`) is found in both, by the folder's own name."""
+ quill = clone_quill_estate(named_apart, remotes, home, folder)
+ place_quill_workspace(named_apart, home)
+ result = run(command, folder, "--dry-run", home=home,
+ env=offline(named_apart))
+ assert result.returncode == 0, result.stderr + result.stdout
+ assert "no estate named" not in result.stderr
+ assert f"{quill['holder']}" in result.stdout
+ if command == PARK:
+ assert (f"park: {folder} - the family holder at {quill['holder']}"
+ in result.stdout)
+ for name in MEMBERS:
+ assert f"--- {name}: make park ---" in result.stdout
+ else:
+ assert f"present {quill['folder']} (family)" in result.stdout
+
+
+@pytest.mark.parametrize("command", [PARK, RESUME], ids=["park", "resume"])
+@pytest.mark.parametrize("shape", [
+ ("Elsewhere", "Other", f"{ORG}/Other", None),
+ (ORG, "Other", f"{ORG}/Quill/Other", None),
+ ("Quill", "Other", None, None),
+ ("Quill", "Other", None, f"{ORG}/Quill"),
+], ids=["neither-name-nor-repository",
+ "an-earlier-part-of-the-repository", "no-repository-and-another-name",
+ "only-a-members-repository-ends-in-the-folder"])
+def test_a_family_folder_that_is_neither_name_still_refuses(
+ remotes, home, command, shape):
+ """What the second arm must NOT widen into. The folder has to equal the
+ LAST part of `repository:` — not any part, not the org, not whatever a
+ missing key reads as, and not a MEMBER's own `repository:` (indented, so
+ never the holder's) — or `name:`; anything else is a directory called
+ that, as it always was."""
+ folder, name, repository, member_repository = shape
+ projects = home / "projects"
+ probe_family_two_names(projects, folder, name=name, repository=repository,
+ member_repository=member_repository)
+ config = home / ".agents" / "workspace.yaml"
+ config.parent.mkdir(parents=True)
+ config.write_text(f"repository: {WORKSPACE_SLUG}\npath: ~/projects/wip\n",
+ encoding="utf-8")
+ result = run(command, folder, home=home, env=offline(remotes))
+ assert result.returncode == 2, result.stdout + result.stderr
+ if command == PARK:
+ assert f"no estate named '{folder}'" in result.stderr
+ assert f"PROBE park in {projects / folder / folder}" not in result.stdout
+ else:
+ assert f"no workspace manifest records '{folder}'" in result.stderr
+ assert f"present {projects / folder}" not in result.stdout
+
+
+def test_a_family_folder_named_for_its_repository_wins_over_a_standalone_root(
+ home):
+ """The same rule as above, for a family whose `name:` differs: the holder
+ drives the members, so it is still the answer where a standalone
+ `project.yaml` sits in the same folder."""
+ projects = home / "projects"
+ holder = probe_family_two_names(projects, QUILL, name=QUILL_DISPLAY,
+ repository=f"{ORG}/{QUILL}")
+ probe_project(projects, QUILL, at=projects / QUILL)
+ result = run(PARK, QUILL, home=home)
+ assert result.returncode == 0, result.stderr + result.stdout
+ assert f"park: {QUILL} - the family holder at {holder}" in result.stdout
+ assert f"PROBE park in {holder}" in result.stdout
+ assert f"PROBE park in {projects / QUILL} " not in result.stdout
+
+
def test_with_no_name_the_estate_around_the_cwd_is_used(home):
"""`park` from a subdirectory, which is how a person actually types it."""
projects = home / "projects"
@@ -1250,6 +1464,81 @@ def test_resume_on_a_fresh_machine_builds_the_whole_estate(remotes, home):
assert "0 refusal(s)" in result.stdout
+def test_resume_clones_the_holder_under_its_repositorys_name_when_the_display_name_differs(
+ named_apart, remotes, home):
+ """#236, the fresh machine. The record says `family: quill` and each
+ member's `root: Quill/Alpha`, and it is filed under `workspaces/testorg/`:
+ it NEVER carries the holder's repository, so `resume` derives it from the
+ org and the family folder — and under v1.1.0 the folder IS the
+ repository's last part (opensoft/openRepoShape#629). The clone then has to
+ be recognised as an estate although its `name:` is "QuillWorks"; before,
+ `resume` cloned the right repository and refused it as 'not an estate'."""
+ result = run(RESUME, QUILL, "--workspace", WORKSPACE_SLUG, home=home,
+ env=offline(named_apart))
+ assert result.returncode == 0, result.stderr + result.stdout
+ folder = home / "projects" / QUILL
+ holder = folder / QUILL
+ assert f"root repository {ORG}/{QUILL}" in result.stdout
+ assert f"absent cloning {ORG}/{QUILL} -> {holder}" in result.stdout
+ assert f"{ORG}/{QUILL_DISPLAY}" not in result.stdout
+ assert 'name: "QuillWorks"' in (holder / "family.yaml").read_text(
+ encoding="utf-8"), "the holder is the one whose display name differs"
+ assert not (home / "projects" / QUILL_DISPLAY).exists()
+ for name in MEMBERS:
+ assert (folder / name / ".git").is_dir(), f"{name} is not beside it"
+ assert f"{name}.sh ran with:" in result.stdout
+ assert f"holder {QUILL}" in result.stdout
+ assert "0 refusal(s)" in result.stdout
+
+
+@pytest.mark.parametrize(
+ "on_disk, asked", [(QUILL, QUILL), (QUILL_DISPLAY, "quill")],
+ ids=["the-holder-was-moved-to-the-repositorys-name",
+ "the-holder-is-still-in-the-display-names-folder"])
+def test_resume_names_the_repository_of_the_holder_it_resolved_not_the_records_folder(
+ named_apart, remotes, home, tmp_path, on_disk, asked):
+ """The review of #240 (5561239588), finding 1. The record is filed as park
+ files it: ONE per family, named for the id (`quill.yaml`, `family: quill`),
+ parked from the layout before v1.1.0 so its `root:` is `QuillWorks/Alpha`.
+ Then `make siblings`' own advice is followed, `mv QuillWorks/QuillWorks
+ Quill/Quill`, and the record is stale until the next park. The holder IS
+ on disk, found by the name typed; asking for the repository by the
+ record's folder missed it and printed `testorg/QuillWorks`, which is no
+ repository. The second case is the same record against the holder still
+ where the record says, typed as the id."""
+ clone_quill_estate(named_apart, remotes, home, on_disk)
+ record = family_manifest(QUILL_DISPLAY).replace(
+ f"family: {QUILL_DISPLAY.lower()}\n", f"family: {QUILL.lower()}\n")
+ assert "root: QuillWorks/Alpha" in record and "family: quill\n" in record
+ place_quill_workspace(named_apart, home, seed_workspace(
+ tmp_path / "stale", "wip",
+ {f"workspaces/{ORG}/{QUILL.lower()}.yaml": record}))
+ result = run(RESUME, asked, "--dry-run", home=home,
+ env=offline(named_apart))
+ assert result.returncode == 0, result.stderr + result.stdout
+ assert (f"records a family estate, root repository "
+ f"{ORG}/{QUILL}") in result.stdout
+ assert f"{ORG}/{QUILL_DISPLAY}" not in result.stdout
+ assert f"present {home / 'projects' / on_disk} (family)" \
+ in result.stdout
+
+
+def test_resume_names_the_repository_the_holders_family_yaml_gives(
+ named_apart, remotes, home):
+ """#236, the estate already here, laid out before v1.1.0: the folder is the
+ display name, so it is NOT the repository's name, and a repository derived
+ from it (`testorg/QuillWorks`) is a repository that does not exist. The
+ holder's own `family.yaml` says which one it is, and it can be read."""
+ clone_quill_estate(named_apart, remotes, home, QUILL_DISPLAY)
+ place_quill_workspace(named_apart, home)
+ result = run(RESUME, QUILL_DISPLAY, "--dry-run", home=home,
+ env=offline(named_apart))
+ assert result.returncode == 0, result.stderr + result.stdout
+ assert (f"records a family estate, root repository "
+ f"{ORG}/{QUILL}") in result.stdout
+ assert f"{ORG}/{QUILL_DISPLAY}" not in result.stdout
+
+
def test_resume_writes_the_config_only_when_asked_by_name(remotes, home):
"""One of the two files this standard ever writes outside a repository —
and it is the SAME file as the other, `~/.agents/workspace.yaml`, written
diff --git a/tests/test_status_command.py b/tests/test_status_command.py
index b5c1b5bc..89b549a3 100644
--- a/tests/test_status_command.py
+++ b/tests/test_status_command.py
@@ -74,6 +74,10 @@
ORG, MEMBERS, FAMILY, FAKE_SHA, commit_all, estate, family_manifest,
remotes, run, probe_project,
)
+from test_park_resume_commands import ( # noqa: F401 (fixtures by name)
+ QUILL, QUILL_DISPLAY, clone_quill_estate, named_apart,
+ probe_family_two_names,
+)
STATUS = REPO / "status"
@@ -558,6 +562,65 @@ def test_a_family_reads_the_holder_and_each_working_clone_and_names_a_missing_on
assert "1 finding(s) in 3 repositories" in result.stdout
+@NEEDS_UPSTREAM
+@pytest.mark.parametrize("folder", [QUILL, QUILL_DISPLAY],
+ ids=["repository", "name"])
+def test_a_family_folder_is_read_by_its_repository_name_or_its_display_name(
+ named_apart, remotes, home, folder):
+ """#236: one `family.yaml` (`name: "QuillWorks"`, `repository:
+ testorg/Quill`), laid out in the folder openRepoShape v1.1.0 prescribes —
+ its repository's last part, opensoft/openRepoShape#629 — and in the one
+ `status` required before it, the display name. Both are one estate: the
+ holder, then each member's working clone beside it."""
+ quill = clone_quill_estate(named_apart, remotes, home, folder)
+ result = run(STATUS, folder, home=home)
+ assert result.returncode == 0, result.stdout + result.stderr
+ assert "no estate named" not in result.stderr
+ assert f"holder main {quill['holder']}" in result.stdout
+ for name in MEMBERS:
+ assert f"root main {quill['siblings'][name]}" in result.stdout
+ assert "0 finding(s) in 3 repositories" in result.stdout
+
+
+@pytest.mark.parametrize("shape", [
+ ("Elsewhere", "Other", f"{ORG}/Other", None),
+ (ORG, "Other", f"{ORG}/Quill/Other", None),
+ ("Quill", "Other", None, None),
+ ("Quill", "Other", None, f"{ORG}/Quill"),
+], ids=["neither-name-nor-repository",
+ "an-earlier-part-of-the-repository", "no-repository-and-another-name",
+ "only-a-members-repository-ends-in-the-folder"])
+def test_a_family_folder_that_is_neither_name_is_still_not_an_estate(home,
+ shape):
+ """The repository arm must not widen past the LAST part of `repository:`,
+ a missing key must not read as a name, and a MEMBER's own `repository:`
+ (indented) is never the holder's: those folders are directories called
+ that, exactly as they were before."""
+ folder, name, repository, member_repository = shape
+ probe_family_two_names(home / "projects", folder, name=name,
+ repository=repository,
+ member_repository=member_repository)
+ result = run(STATUS, folder, home=home)
+ assert result.returncode == 2, result.stdout + result.stderr
+ assert f"no estate named '{folder}'" in result.stderr
+
+
+def test_a_family_folder_named_for_its_repository_wins_over_a_standalone_root(
+ home):
+ """The holder drives the members, so where a family whose `name:` differs
+ shares its folder with a `project.yaml`, `status` reads the family."""
+ projects = home / "projects"
+ holder = probe_family_two_names(projects, QUILL, name=QUILL_DISPLAY,
+ repository=f"{ORG}/{QUILL}")
+ probe_project(projects, QUILL, at=projects / QUILL)
+ result = run(STATUS, QUILL, home=home)
+ assert result.returncode != 2, result.stdout + result.stderr # 1: findings
+ assert "no estate named" not in result.stderr, result.stderr
+ assert (f"status: {QUILL} - the family holder at {holder}"
+ in result.stdout), result.stdout + result.stderr
+ assert "the assembly root" not in result.stdout
+
+
# --- every estate ------------------------------------------------------------
@pytest.mark.parametrize("flag", ["--all", "-a"])