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
123 changes: 114 additions & 9 deletions adopt-project.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,13 @@
other than `private`, `public` or `internal` -- asking the validator itself,
loaded from the template it ships, and not a copy of its rules. A name that
cannot be a directory's (a `--project` that makes a repository name of 256
bytes with its `.git`) is refused the same way.
bytes with its `.git`) is refused the same way. Without `--local-remote-dir`
`execute` also refuses, before either leg exists, what GitHub will not take
(#838): a leg's name over GitHub's 100 characters (`adopt-name-too-long`), an
`org:` over the 39 it keeps an organisation's name to (`adopt-org-too-long`),
and an id whose `xf-project-<id>` topic is over its 50
(`adopt-topic-too-long`), which `gh repo edit --add-topic` refused only once
both legs and the split were pushed.

WHY `git filter-repo` AND NOT A VENDORED COPY. Extracting history correctly is
a solved problem with one correct implementation, and a vendored copy of it
Expand Down Expand Up @@ -261,6 +267,13 @@
MANIFEST_UNREADABLE_REMEDIATION, manifest_judged, manifest_value_problems,
refuse_manifest_values, refuse_unstorable_names,
)
# What GitHub keeps a repository's name, a topic and an organisation's name
# to, asked by `execute` before either leg exists where GitHub is (#838).
from shape_materialize import ( # noqa: E402
GITHUB_ID_MAX, GITHUB_LIMITS, GITHUB_NAME_MAX, GITHUB_OWNER_MAX,
GITHUB_PROJECT_MAX, GITHUB_TOPIC_MAX, refuse_a_topic_github_cannot_set,
refuse_an_owner_github_cannot_have,
)
# Whether a value this tool writes into a file is text a file can hold
# (#255), asked beside `one_printable_line`, the trailer's question.
from shape_materialize import unwritable_reason # noqa: E402
Expand Down Expand Up @@ -11998,14 +12011,90 @@ def _repository_urls(args, plan: Plan, names: dict,
# under `--local-remote-dir` a bare `<name>.git` for the two new legs. A
# name of 247 to 250 characters passed every check and died in
# `Path.exists` with `File name too long`, a traceback, before any leg
# existed (#261).
refuse_unstorable_names(
"adopt-name-too-long", list(names.values()),
[names["spec"], names["code"]] if local else [])
# existed (#261). Without it the two legs are GitHub repositories, which
# GitHub keeps to 100 characters, under an organisation it keeps to 39:
# either passed to the first `gh repo create` (#838).
legs = [names["spec"], names["code"]]
try:
refuse_unstorable_names(
"adopt-name-too-long", list(names.values()),
legs if local else [], [] if local else legs)
except Refusal as exc:
if local or not _a_leg_named_after_a_long_root(names):
raise
# THE SOURCE'S OWN NAME IS NOT `--project`'S TO SHORTEN (#859's
# review, SHOULD 1). Without `--local-remote-dir` the assembly root
# is the source's own repository on GitHub, which GitHub lets be
# named with up to 100 characters, and a leg named after one of 96
# to 100 is over that limit. "Use a shorter --project" planned
# another repository, which `git ls-remote` could not read. A root
# of 101 or more is no name GitHub gives a repository, so it is no
# repository's to rename, and keeps "Use a shorter --project"
# (#859's re-review, NIT 3).
raise Refusal(exc.code, exc.detail, _rename_first(
repositories["assembly"], SOURCE_NAME_TOO_LONG)) from None
if not local:
refuse_an_owner_github_cannot_have(
"adopt-org-too-long", org, f"{plan.path}: `org:`",
ORG_REMEDIATION)
return (local, repositories,
_remote_urls(org, names, args.local_remote_dir, source))


#: How `adopt-org-too-long` and `adopt-topic-too-long` are told to go on
#: (#838): the plan's own key, or `plan` again with the flag that writes it.
#: Neither is a leg: `execute` refuses both before it makes one.
ORG_REMEDIATION = (
"Remediation: no repository was created. Fix `org:` in the plan, or "
"re-run `plan` with --org, as GitHub spells the organisation, the "
"`<org>` of `https://github.com/<org>`: GitHub gives no user or "
f"organisation a name of more than {GITHUB_OWNER_MAX} characters.")
TOPIC_REMEDIATION = (
"Remediation: no repository was created. Fix `id:` in the plan, or "
f"re-run `plan` with an explicit --id, of at most {GITHUB_ID_MAX} "
"characters: the id need not be the project's name lowercased, and the "
"name, which the assembly root already has, stays as it is.")
#: What `adopt-name-too-long` says before `_rename_first`'s way out, where a
#: leg GitHub will not create is named after the source (#859's review,
#: SHOULD 1), as `adopt-bad-id` says why the source's own name will not do.
#: `GITHUB_LIMITS` says the project name's bound, 95, so this does not.
SOURCE_NAME_TOO_LONG = (
"nothing was created. The legs are named `<Project>-spec` and "
"`<Project>-code`, and --project is the source's own name: "
f"{GITHUB_LIMITS}. ")


def _a_leg_named_after_a_long_root(names: dict) -> bool:
"""Whether a leg over GitHub's `GITHUB_NAME_MAX` is named after an
assembly root GitHub can have, one of at most `GITHUB_NAME_MAX`
characters. A leg is named `<legs.assembly>-spec` or `-code`, as `plan`
names both: its length is then the root's, the source's own name (#859's
review, SHOULD 1). A leg the plan was edited to name otherwise is the
plan's own to shorten, and keeps the remediation it had; so does any leg
where the root is over `GITHUB_NAME_MAX`, which no repository on GitHub
is named, so renaming the source there shortens nothing (#859's
re-review, NIT 3)."""
return len(names["assembly"]) <= GITHUB_NAME_MAX and any(
len(names[role]) > GITHUB_NAME_MAX
and names[role] == f"{names['assembly']}-{role}"
for role in ("spec", "code"))


def _refuse_a_topic_github_cannot_set(plan: Plan, topic: str) -> None:
"""`adopt-topic-too-long`: an id whose `xf-project-<id>` topic GitHub
will not set (#838), refused before `_confirm` and before either leg
exists. `_set_the_topic` sets it on all three only once both legs and
the split are pushed, so an id over 39 characters, which a project name
of 40 or more gives by default, ended the run there, exit 2, with the
adoption pushed and the topic unset. Asked in GitHub mode alone: under
`--local-remote-dir` no topic is set. The id is named by where it came
from, `id:` or `DERIVED_ID`, as `_id_field` names it."""
named = "`id:`" if plan.get("id") is not None else DERIVED_ID
refuse_a_topic_github_cannot_set(
"adopt-topic-too-long", topic, plan.project_id,
f"{plan.path}: {named}", TOPIC_REMEDIATION)


def _remote_urls(org: str, names: dict, local_remote_dir: Path | None,
source: Source) -> dict:
"""The url of each of the three repositories, by role: what `execute`
Expand Down Expand Up @@ -12900,6 +12989,8 @@ def cmd_execute(args) -> int:
for line in plan.seeding_record_disagreements(seeded):
print(line)
topic = naming.topic_for(plan.project_id)
if not local:
_refuse_a_topic_github_cannot_set(plan, topic)
print(_topics_line(topic, local))
_confirm(args, plan, names)

Expand Down Expand Up @@ -14181,7 +14272,8 @@ def _is_the_source(repository: str, org: str, name: str) -> bool:

def _rename_first(repository: str, said: str = "") -> str:
"""The remediation where the source's own name is one the naming policy
refuses: it cannot be adopted in place under that name, and renaming it
refuses, or one too long for GitHub to create a leg named after it
(#838): it cannot be adopted in place under that name, and renaming it
is a human's decision that comes first (#499). `said` is what the
refusal says before that, a sentence or none. THE ONE TEXT of it.

Expand Down Expand Up @@ -14885,16 +14977,29 @@ def main(argv: list[str] | None = None) -> int:
"an `org/repo`.")
plan.add_argument("--project", required=True,
help="the assembly-root name; for an in-place adoption "
"it is the source repository's own name")
"it is the source repository's own name. "
f"`execute` refuses one over {GITHUB_PROJECT_MAX} "
"characters without --local-remote-dir: GitHub "
"keeps a leg's name, `<Project>-spec`, to "
f"{GITHUB_NAME_MAX}.")
plan.add_argument("--org", default=None,
help="the organisation the source repository is in, "
"which the plan records as `org:` (default: the "
"organisation of an `org/repo` --source, or the "
"one a local source's `origin` url names). A "
"source whose `origin` is a path, or that has "
"none, names no organisation: `plan` refuses "
"it as `adopt-no-org` unless --org is given.")
plan.add_argument("--id", default=None)
"it as `adopt-no-org` unless --org is given. "
f"`execute` refuses one over {GITHUB_OWNER_MAX} "
"characters without --local-remote-dir, GitHub's "
"limit on an organisation's name.")
plan.add_argument("--id", default=None,
help="lowercase project id (default: --project "
f"lowercased). `execute` refuses one over "
f"{GITHUB_ID_MAX} characters without "
"--local-remote-dir: GitHub keeps a topic to "
f"{GITHUB_TOPIC_MAX}, and the topic is "
"`xf-project-<id>`.")
plan.add_argument("--visibility", choices=VISIBILITY_CHOICES,
default="private")
plan.add_argument("--elected-by", default=None,
Expand Down
44 changes: 35 additions & 9 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@ usage: setup-project.py [<Project>] [options] [-- <extra scaffold flags>]
the same thing as --project; give one or the other.
--project <Project> the assembly-root name: ONE CamelCase token, no
hyphen, underscore, dot or space. Prompted for when
omitted and a terminal is attached.
omitted and a terminal is attached. At most 95
characters without --local-remote-dir: GitHub keeps
a repository's name to 100, and -spec adds 5.
--id <id> lowercase project id (default: project lowercased)
At most 39 characters without --local-remote-dir:
GitHub keeps the topic xf-project-<id> to 50.
--name "<Display>" display name (default: the project name)
--visibility private|public|internal (default: private)
--elected-by "<Name>" who is electing the shape (default: your gh login)
Expand All @@ -89,7 +93,9 @@ usage: setup-project.py [<Project>] [options] [-- <extra scaffold flags>]
--org <org> override the detected organisation. REQUIRED when
run outside a checkout of openRepoShape
(self-bootstrap mode): there is no origin to read it
from.
from. At most 39 characters without
--local-remote-dir, GitHub's limit on an
organisation's name.
--allow-upstream-org permit `--org opensoft` itself: opensoft is the
upstream owner of openRepoShape, and almost never
what you meant to scaffold into.
Expand Down Expand Up @@ -203,8 +209,12 @@ usage: setup-project.py [<Project>] [options] [-- <extra scaffold flags>]
the same thing as --project; give one or the other.
--project <Project> the assembly-root name: ONE CamelCase token, no
hyphen, underscore, dot or space. Prompted for when
omitted and a terminal is attached.
omitted and a terminal is attached. At most 95
characters without --local-remote-dir: GitHub keeps
a repository's name to 100, and -spec adds 5.
--id <id> lowercase project id (default: project lowercased)
At most 39 characters without --local-remote-dir:
GitHub keeps the topic xf-project-<id> to 50.
--name "<Display>" display name (default: the project name)
--visibility private|public|internal (default: private)
--elected-by "<Name>" who is electing the shape (default: your gh login)
Expand All @@ -224,7 +234,9 @@ usage: setup-project.py [<Project>] [options] [-- <extra scaffold flags>]
--org <org> override the detected organisation. REQUIRED when
run outside a checkout of openRepoShape
(self-bootstrap mode): there is no origin to read it
from.
from. At most 39 characters without
--local-remote-dir, GitHub's limit on an
organisation's name.
--allow-upstream-org permit `--org opensoft` itself: opensoft is the
upstream owner of openRepoShape, and almost never
what you meant to scaffold into.
Expand Down Expand Up @@ -274,9 +286,15 @@ Create a project's three repositories in the shape this standard describes.

options:
-h, --help show this help message and exit
--org ORG
--project PROJECT the assembly-root name, e.g. Atlas
--id ID lowercase project id (default: --project lowercased)
--org ORG the organisation the three repositories are created
in; at most 39 characters without --local-remote-dir,
GitHub's limit on an organisation's name
--project PROJECT the assembly-root name, e.g. Atlas; at most 95
characters without --local-remote-dir, as GitHub keeps
a repository's name to 100 and `-spec` adds 5
--id ID lowercase project id (default: --project lowercased);
at most 39 characters without --local-remote-dir, as
GitHub keeps the topic `xf-project-<id>` to 50
--name NAME display name
--visibility {private,public,internal}
default: private for a fresh scaffold; with --reuse-
Expand Down Expand Up @@ -428,14 +446,22 @@ options:
`source-unresolvable` refuses an empty value, and one
that is neither a path that exists nor an `org/repo`.
--project PROJECT the assembly-root name; for an in-place adoption it is
the source repository's own name
the source repository's own name. `execute` refuses
one over 95 characters without --local-remote-dir:
GitHub keeps a leg's name, `<Project>-spec`, to 100.
--org ORG the organisation the source repository is in, which
the plan records as `org:` (default: the organisation
of an `org/repo` --source, or the one a local source's
`origin` url names). A source whose `origin` is a
path, or that has none, names no organisation: `plan`
refuses it as `adopt-no-org` unless --org is given.
--id ID
`execute` refuses one over 39 characters without
--local-remote-dir, GitHub's limit on an
organisation's name.
--id ID lowercase project id (default: --project lowercased).
`execute` refuses one over 39 characters without
--local-remote-dir: GitHub keeps a topic to 50, and
the topic is `xf-project-<id>`.
--visibility {private,public,internal}
--elected-by ELECTED_BY
the human electing the shape (default: `git config
Expand Down
Loading
Loading