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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@ one verdict, and its exit code:
| `COMPLIANT` | 0 | no row is a `FINDING`, and the pin names this standard's commit |
| `COMPLIANT, SHAPE BEHIND (n upstream-changed, m upstream-added)`, or `(pin <a> -> <b>, no copied file differs)` | 1 | valid, and the standard has moved on |
| `DRIFTED (…)` | 1 | a copy was edited, deleted or no longer maps onto the standard, or a leg (a member, in a holder) is off its pin |
| `MISPLACED (n paths)` | 1 | a tracked path sits in a leg the path policy puts elsewhere, or a submodule in a leg other than the one its `submodules:` record names |
| `MISPLACED (n paths)` | 1 | a tracked path sits in a leg the path policy puts elsewhere (unless `answered_paths:` answers it into that leg), or a submodule in a leg other than the one its `submodules:` record names |
| `INVALID (<rows>)` | 1 | a validator went red, or (`merge`) the index holds an unmerged path |
| `NOT A SHAPE ROOT` | 2 | neither manifest is there — adopt, or scaffold |

Expand Down
34 changes: 27 additions & 7 deletions adopt-project.py
Original file line number Diff line number Diff line change
Expand Up @@ -7115,13 +7115,26 @@ def _answered_legs(entries: list, paths: list[str]) -> dict:
yet cannot have been answered inconsistently. `execute` refuses all four
too: the null one in `_refuse_an_unrunnable_plan`, the other three in
#168's `_refuse_what_check_finds`.

EACH PATH LOOKS UP ITS OWN NAMES (#914), as the doctor's
`answered_questions` does: the entries `_covering` could find covering
it are the names `_covering_names` lists, and one entry covers it where
exactly one of those names is a plan entry's, spelled by one entry. So a
path costs its depth and not the plan's length, with the answer holding
every entry against every path gave.
"""
entry_paths = [str(e.get("path")) for e in entries]
legs = {str(e.get("path")): str(e.get("leg")) for e in entries}
spelled: dict[str, int] = {}
legs: dict[str, str] = {}
for entry in entries:
name = str(entry.get("path"))
spelled[name] = spelled.get(name, 0) + 1
legs[name] = str(entry.get("leg"))
out: dict[str, tuple[str, str]] = {}
for path in paths:
covering = _covering(entry_paths, path)
if len(covering) == 1 and legs[covering[0]] in LEG_VALUES:
covering = [name for name in dict.fromkeys(_covering_names(path))
if name in spelled]
if (len(covering) == 1 and spelled[covering[0]] == 1
and legs[covering[0]] in LEG_VALUES):
out[path] = (legs[covering[0]], covering[0])
return out

Expand Down Expand Up @@ -15118,20 +15131,27 @@ def _account_for(before: dict, after: dict, drops: list[str]) -> tuple[dict, lis

A path in TWO places is as much a finding as a path in none — the second
is data loss and the first is two repositories owning one file.

THE DROP IS ASKED BEFORE THE ROOT IS (#914). A path the plan dropped is
gone from the split before the shape writes, so a blob the root holds
at it is the shape's, which happens to be the same bytes: the path is
counted `drop`, as the plan says, and not `root`. Only the count moves;
a dropped path in a leg is counted, or found, as before.
"""
dropped = {p.rstrip("/") for p in drops}
counts = {"spec": 0, "code": 0, "root": 0, "drop": 0}
findings: list[str] = []
for path, oid in sorted(before.items()):
landings = [where for where in after.get(path, [])
if where.split(":", 1)[1] == oid]
if len(landings) == 1:
drop = any(path == d or path.startswith(d + "/") for d in dropped)
if drop and landings in ([], [f"root:{oid}"]):
counts["drop"] += 1
elif len(landings) == 1:
counts[landings[0].split(":", 1)[0]] += 1
elif landings:
findings.append(f"FINDING adopt-duplicated: {_spelled(path)} is "
"in " + ", ".join(landings))
elif any(path == d or path.startswith(d + "/") for d in dropped):
counts["drop"] += 1
else:
findings.append(f"FINDING adopt-lost: {_spelled(path)} "
f"({oid[:12]}) is in no leg, not in the root "
Expand Down
6 changes: 3 additions & 3 deletions docs/handbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -1552,7 +1552,7 @@ <h2>Bootstrap is COPIED into the project, not fetched</h2>

<section id="current">

<!-- reads README.md § Keeping a project's shape current: blob 5edbfec7e824a4064e9ff5331da208f21cab7423 -->
<!-- reads README.md § Keeping a project's shape current: blob 355e197f5b3e9b283a9cdb13d71ff81e99a120f8 -->

<p class="eyebrow">Reference</p>
<h2>Keeping a project&rsquo;s shape current</h2>
Expand Down Expand Up @@ -1614,7 +1614,7 @@ <h2>Keeping a project&rsquo;s shape current</h2>

<section id="doctor">

<!-- reads README.md § Keeping a project's shape current: blob 5edbfec7e824a4064e9ff5331da208f21cab7423 -->
<!-- reads README.md § Keeping a project's shape current: blob 355e197f5b3e9b283a9cdb13d71ff81e99a120f8 -->

<p class="eyebrow">Reference</p>
<h2>Is this repository compliant? <code>openRepoShape --doctor</code></h2>
Expand Down Expand Up @@ -1669,7 +1669,7 @@ <h2>Is this repository compliant? <code>openRepoShape --doctor</code></h2>
<tr><td><code>COMPLIANT</code></td><td>0</td><td>no row is a <code>FINDING</code>, and the pin names this standard&rsquo;s commit</td></tr>
<tr><td><code>COMPLIANT, SHAPE BEHIND (n upstream-changed, m upstream-added)</code>, or <code>(pin &lt;a&gt; -&gt; &lt;b&gt;, no copied file differs)</code></td><td>1</td><td>valid, and the standard has moved on</td></tr>
<tr><td><code>DRIFTED (&hellip;)</code></td><td>1</td><td>a copy was edited, deleted or no longer maps onto the standard, or a leg (a member, in a holder) is off its pin</td></tr>
<tr><td><code>MISPLACED (n paths)</code></td><td>1</td><td>a tracked path sits in a leg the path policy puts elsewhere, or a submodule in a leg other than the one its <code>submodules:</code> record names</td></tr>
<tr><td><code>MISPLACED (n paths)</code></td><td>1</td><td>a tracked path sits in a leg the path policy puts elsewhere (unless <code>answered_paths:</code> answers it into that leg), or a submodule in a leg other than the one its <code>submodules:</code> record names</td></tr>
<tr><td><code>INVALID (&lt;rows&gt;)</code></td><td>1</td><td>a validator went red, or (<code>merge</code>) the index holds an unmerged path</td></tr>
<tr><td><code>NOT A SHAPE ROOT</code></td><td>2</td><td>neither manifest is there &mdash; adopt, or scaffold</td></tr>
</tbody>
Expand Down
11 changes: 11 additions & 0 deletions scripts/shape_materialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -5018,6 +5018,17 @@ def _placeholders_of(line: str) -> list[str]:
#: Which placeholder carries the value a finding of the validator is about,
#: for the ones a tool is handed a value for. A finding with no row is still
#: refused, in the validator's words and without a flag to point at.
#:
#: THE TWO RECORDS' CODES HAVE NO ROW, ON PURPOSE (#914): #189's
#: `submodules:` (`manifest-submodules`, `manifest-submodule-*`) and #525's
#: `answered_paths:` (`manifest-answered-paths`, `manifest-answered-*`),
#: which the validator spells from its `_Record`s. No placeholder carries a row
#: of either: `adopt-project.py execute` appends both to `project.yaml` after
#: the render its gate asks, so no tool is handed a value of theirs and no
#: such finding can arise there today. Should a caller render a record into
#: that question, a finding about one is about no placeholder, and
#: `manifest_judged` answers None: every value is left to the validator, the
#: safe direction, and none is named as one a flag carries.
FINDING_PLACEHOLDER = {
"manifest-id": "PROJECT_ID", "manifest-topic": "PROJECT_ID",
"manifest-name": "PROJECT_NAME", "manifest-elected-by": "ELECTED_BY",
Expand Down
Loading
Loading