Repository navigation
adopt-project.py: _github_name reads no owner over 39 characters or repository over 100, so plan --source <50 b's>/Thing never asks gh for a name GitHub cannot hold, no remedy offers --org <50 b's>, and check and execute never clone such a record again (#1129) - #1194
brettheap wants to merge 1 commit into
Conversation
… repository over 100, so `plan --source <50 b's>/Thing` never asks `gh` for a name GitHub cannot hold, no remedy offers `--org <50 b's>`, and `check` and `execute` never clone such a record again (#1129) GitHub gives an owner at most GITHUB_OWNER_MAX (39) characters and a repository at most GITHUB_NAME_MAX (100). `_github_name` now gives no name past either, counted once the `.git` is dropped, so every reader of it answers alike: `_source_spec` reads such a spelling as a path (in place where it is one, `source-unresolvable` where it is not), `_repository_in_url` refuses a url of it, and `_recorded_repository` and `_held_repository` read a plan's record of it as naming no repository. Stacked on #1122 (fix/1003-adopt-unenterable-source): its guard on the two limits in `_source_there` is folded into this one question, and its three `github.com/` rows move to the new file, since such a url is now refused before any `stat`. New rows: tests/test_adopt_source_github_limits.py (48). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR centralizes GitHub name-length validation in Sequence diagram for rejecting over-limit GitHub sourcessequenceDiagram
actor User
participant AdoptProject
participant GitHubCLI
User->>AdoptProject: plan --source owner/repo
AdoptProject->>AdoptProject: _github_name(source)
alt owner or repo exceeds GitHub limit
AdoptProject-->>User: source-unresolvable
Note over AdoptProject,GitHubCLI: No clone request is made
else name is within limits
AdoptProject->>GitHubCLI: repo clone owner/repo
GitHubCLI-->>AdoptProject: clone result
end
Sequence diagram for recorded source handling during check and executesequenceDiagram
actor User
participant AdoptProject
participant GitHubCLI
User->>AdoptProject: check or execute plan
AdoptProject->>AdoptProject: _recorded_repository(source.repository)
alt recorded owner or repo exceeds limit
AdoptProject-->>User: source-unresolvable
Note over AdoptProject,GitHubCLI: No reclone is attempted
else recorded name is valid
AdoptProject->>GitHubCLI: repo clone recorded repository
GitHubCLI-->>AdoptProject: clone result
end
Flow diagram for GitHub source name validationflowchart TD
A["--source spelling"] --> B[_github_name]
B -->|"valid owner/repo within limits"| C["GitHub repository name"]
B -->|"over-limit or invalid spelling"| D["None"]
C --> E[_source_spec]
C --> F[_repository_in_url]
C --> G[_recorded_repository]
C --> H[_held_repository]
D --> I["Treat as local path or refuse as source-unresolvable"]
E --> J["Existing path read in place"]
F --> K["URL refused before stat"]
G --> L["Recorded repository is not cloned again"]
H --> M["No invalid --org remedy"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
RV1194: independent review of #1194 at No MUST and no SHOULD. There are three NITs, each for a follow-up. NIT-1 and NIT-2 share one cause outside Heads, read at 12:01Z and again at 12:48Z: this PR What I measuredThe limits are GitHub's. A user or an organisation gets at most 39 characters, and a repository at most 100 (github-limits, checked against GitHub's own validation messages).
Every reader.
Rows. The new file
Probe. I ran 56 commands (
On this head:
MutantsEach mutant was applied to this head's
GateThe gate ran on a scratch merge commit,
The environment:
Every run ended rc=0. FIN1129's 23 files, each run alone: 1268 passed, 1 skipped. These are the PR body's counts, file by file.
Totals: 1713 passed, 4 skipped, 0 failed, 0 errors. These are FIN1129's counts exactly. The skips belong to the host: Beyond FIN1129's list: one pytest run of 495 tests, all passed. Its 327 new tests are Merge-treesEvery
FindingsNIT-1: a local source's
Fix: a follow-up issue to apply the same two limits in NIT-2: the same plan is held, or not, depending on whether
NIT-3:
Fix, optional: when the spelling is a path here, have Lane: openreposhape-4 (openRepoShape-4) |



Lane 4's W1129 for #1129, RV1122's NIT-1 on #1122 (#1122 issuecomment-6319493639). This is a draft, and a reviewer follows.
Stacked on #1122. This PR's base is
fix/1003-adopt-unenterable-source(#1122, headc01c4d73), notmain. It lands only after the v1.2.0 tag, and only after #1122. Why it is stacked: see "Why stacked" below.What
adopt-project.py,_github_name: an owner of more thanGITHUB_OWNER_MAX(39) characters, or a repository of more thanGITHUB_NAME_MAX(100), counted once the trailing.gitis dropped, names noowner/repo. The file already imports both limits fromshape_materialize.Every reader of
_github_namenow gives the same answer for such a spelling:_source_specplan,checkandexecute--sourcegh repo cloneasked, orsource-ambiguousbeside a directory of that spellingsource-unresolvable("neither a path that exists nor anorg/reponame") where it is not_repository_in_url--source,github.com/...toogh repo cloneaskedsource-unresolvable, "names noowner/repoon github.com", before anystat_recorded_repositorycheckandexecute, a plan's clone gone (cloned: true)gh repo cloneaskedsource-unresolvable, "the plan'ssource.repository, ..., names noowner/repoon github.com"_held_repositorycheckandexecute, a source with nooriginplan-not-the-source, remedy--org <40 c's>owner/repodoes_source_there_refuse_another_repository's remedy (adopt-not-the-source) is reached only after_source_spec, so it never offers--org <a name GitHub cannot hold>for anorg/repo--sourcenow.#1122's guard in
_source_thereis redundant, and is folded in._source_thereis called only by_source_specand_repository_in_url, and both takenamefrom_github_name. So a name past the limits now arrives as None, and thename is Nonebranch gives the same refusal #1122's guard gave. The guard is gone,if name is None:is back, and the docstring says the limits are asked in_github_name. The merge withmainand with #1122 is clean (see "Merges").Why stacked. The textual merge with #1122 is clean, but the two do not hold together as they stand. A
github.com/spelling past the limits is now refused by_repository_in_urlbefore anystat, so four of #1122's rows, which expectedsource-unreadablefrom_source_there, fail on the combination:[bare-github-url];[www-github-url];[bare-github-url-owner-one-over-EIO];[bare-github-url-owner-one-over-EACCES].This PR moves those three params out of #1122's
_source_theretests, with a comment pointing to the new file. There they assert the new answer (source-unresolvable, no url), with the control at the limit kept: two readings and the url.No refusal code is new or changed, and no help text or
docs/cli.mdsentence changes:--source's help already sayssource-unresolvablerefuses "one that is neither a path that exists nor anorg/repo".Behaviour that changes besides the refusal
<40 c's>/Thing(ortestorg/<101 r's>) is read in place, and the plan is written, recording that directory and no repository. Before, it wassource-ambiguous, which named a GitHub repository that cannot exist. At the limit, it is stillsource-ambiguous.origin, and whosesource.repositoryrecords<40 c's>/Thing, is held to nothing.checksaysplan ok, andexecutegoes on. Before, it was held to that record, and the remedy said--org <40 c's>, whichexecutethen refuses asadopt-org-too-long. A record at the limit still holds the plan.github.com/<300 a's>/Thingbeside a./githubproxy.fjygbaifeng.eu.orgissource-unresolvable, "names noowner/repoon github.com". adopt-project.py: anorg/repo--sourcetoo long for the disk is refused as a path is, naming no GitHub repository and offering no url, so--source <300 a's>/Thingis no longer offeredhttps://github.com/<300 a's>/Thing, which can never clone (#1003) #1122 made itsource-unreadable, "whether a repository is there cannot be told". Both offer no url.Before and after
Probed with a
ghfirst on PATH that logs each call and refuses it (exit 4), and with:GH_TOKEN=bogus, an emptyGH_CONFIG_DIR;GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBALan empty file,GIT_CONFIG_NOSYSTEM=1;Each row is
plan --source <it> --project <repo> --org <owner>, except where--orgis said.main961640c6gave the same answers as #1122's head on every row.--sourcec01c4d73<50 b's>/Thing,--org <50 b's>(the issue's)gh repo clone <50 b's>/Thing(1 call),source-unclonablesource-unresolvable, 0 calls<50 b's>/Thing,--org testorg(the issue's)adopt-not-the-source, remedy--project Thing --org <50 b's>, 0 callssource-unresolvable, no--orgoffered, 0 calls<40 c's>/Thingsource-unresolvable, 0 callstestorg/<101 r's>, and with.gitsource-unresolvable, 0 calls<40 c's>/<101 r's>source-unresolvable, 0 callshttps://github.com/<40 c's>/Thing,github.com/<40 c's>/Thingsource-unresolvable("names noowner/repoon github.com"), 0 calls<39 c's>/Thing,testorg/<100 r's>,testorg/<100 r's>.git(controls).gitdropped)<39 c's>/Thing,--org testorg(control)adopt-not-the-source, remedy--org <39 c's>Tests
The landing gate's environment throughout:
/usr/bin/python3 -m pytest -p no:cacheprovider, Python 3.12.3;ghfirst on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR;GIT_CONFIG_GLOBALset to an empty file,GIT_CONFIG_NOSYSTEM=1;EMAIL;GIT_SSH_COMMAND,GIT_ALLOW_PROTOCOLand theLANES_*variables unset.New file
tests/test_adopt_source_github_limits.py, 48 rows, ASCII only. It skips on Windows: the loggingghis a/bin/shscript, and the moved rows need a POSIXFile name too long. Each child puts the loggingghahead of the suite's guard, and everyexecutegets--local-remote-dir.test_plan_asks_gh_for_a_name_github_can_hold(control).git, both at, a url and a bare url:ghlogged exactlyrepo clone <name> <work>/source-readonly/<repo> -- --quiet, oncetest_plan_never_asks_gh_for_a_name_github_cannot_hold.git, both over, and four url spellings: theghlog empty, exit 2, thesource-unresolvableline, no plan, no work directoryghlog (cloning ...)test_a_github_url_past_the_limits_is_refused_before_any_stat(moved from #1122)[www.]github.com/<300 a's>/Thingwith a realFile name too long;github.com/<40 a's>/Thingwithstatrefused (EIO, EACCES):source-unresolvable, no url; control at 39, both readings and the urlsource-unreadable, "cannot be told")test_no_remedy_offers_an_org_github_cannot_hold--org testorgwith owner 40, 50, both over: no--org <owner>, noadopt-not-the-source; control at 39 offers--org <39 c's>--org <owner>offeredtest_a_directory_github_cannot_name_is_read_in_place./<40 c's>/Thing,./testorg/<101 r's>repositories here: exit 0, recorded in place, no repository, 0ghcalls; control at the limit issource-ambiguoussource-ambiguous)test_a_recorded_name_github_can_hold_is_cloned_again(control)check,execute: the NOTE and 1ghcalltest_a_recorded_name_github_cannot_hold_is_never_cloned_againcheck,execute; owner 40, repository 101, both: theghlog empty, thesource-unresolvableline, no "cloned again"ghlog (the NOTE, "cloned again")test_check_and_execute_read_a_source_name_as_plan_reads_itcheck,executewith--source: 4 controls at the limits (1ghcall), 6 past them (0 calls,source-unresolvable)ghlog; 4 passtest_a_record_github_cannot_hold_offers_no_orgcheck,execute(no--yes); a source with noorigin, record owner 40 and 50: no--org <owner>, noplan-not-the-source;plan ok/adopt-unconfirmed; control at 39 keepsplan-not-the-sourceand--org <39 c's>--org <owner>offeredTotals for the 48 rows:
org/repo--sourcetoo long for the disk is refused as a path is, naming no GitHub repository and offering no url, so--source <300 a's>/Thingis no longer offeredhttps://github.com/<300 a's>/Thing, which can never clone (#1003) #1122's head: 34 failed, 14 passed;Every row that is not a control fails on both bases, at the assertion that names the defect, not at a setup step. The 14 controls pass on all three.
#1122's file
tests/test_adopt_unsearchable_source.py: 40 rows at this head, from 44. Three params moved out: the two url rows, and the url row's EIO and EACCES variants. Before the move, those four rows failed at this head (source-unresolvableagainstsource-unreadable), and the other 40 passed.Mutants
Each was applied to this head's
adopt-project.py(sha256d494ba0e...) by exact substitution of the new guard line, and run against this file,test_adopt_unsearchable_source.pyandtest_adopt_unenterable_source_and_locale.py(101 tests). After each one the file was restored from its saved bytes and its sha256 checked. Unmutated, all 101 pass. All 8 were killed.> GITHUB_OWNER_MAX - 1)--source, the recorded clone, the remedy, the directory, the held record, the moved rows' controls), and #1122's 5 owner controls at 39+ 1)--source, the recorded clone, the remedy, the directory, the held record, the 2 moved EIO/EACCES rows), and #1122's 2owner-one-overrows- 1)+ 1)repository-one-overrows.git(len(segments[1]))test_plan_asks_gh_for_a_name_github_can_hold[repository-100-dot-git]Gate
Run on the scratch merge
w1129-gateatca06ad25a: a merge ofmain961640c6and this head900f77af. Its tree,90fbea0e, is the onegit merge-tree --write-tree origin/main HEADgives, andmainhas not moved since. The environment is the one under "Tests"; targeted files only, never the full suite. Every run ended with rc=0.Test files, 23:
test_adopt_source_github_limits(new)test_adopt_unsearchable_sourcetest_adopt_unenterable_source_and_localetest_adopt_check_plan_nametest_adopt_source_clone_recordtest_adopt_template_valuestest_adopt_work_dir_refusaltest_windows_pathstest_adopt_clone_claimedtest_adopt_late_failuretest_adopt_plan_flag_refusals_firsttest_adopt_temp_work_rootstest_adopt_origin_repositorytest_adopt_plantest_adopt_project_is_the_sourcetest_adopt_relative_submodule_urltest_adopt_split_branchtest_adopt_values_as_typedtest_adopt_plan_escapestest_path_guard_hygienetest_repo_hygienetest_scaffold_leg_path_collisionstest_github_name_limitstests/test_shape_doctor.py, all 448 collected ids, in 8 shards of 56 (the shard lists together hold every collected id exactly once; everyadopt-project.pychange is gated with it):Totals: 1713 passed, 4 skipped, 0 failed, 0 errors. The skips are the host's: three
test_shape_doctor.pyrows that print the PowerShell line and needpowershell.exeon a Windows host, and onetest_adopt_plan.pyrow that needsSHAPE_MEDXEHR_CLONE, a read-only clone of MedxSoft/MedxEHR.The gate paused after d02 for disk space and ran d03 to d08 later, on the same scratch merge, whose head did not change.
Merges
git merge-tree --write-tree, this head900f77afagainst each of these; all are clean (rc=0, no conflict):main961640c690fbea0ec01c4d73e2cea9b3(this head's own tree)07486a49787024146cd96b8beff9e4dbc4beba19b79abc315208e87491156ae68162b87487e2ce3124ee133af001382d25805102b58ed4518a7828332e67c977ff33ba12070c39b27af5fabc7237b4057a2711d0c0bcaf889549006fc16ff99a5c3908f8aba16ad41c5173756e1f5cb9c55e55e3845f0ab5bba168bdab6af7c82307036a1d53a9d9504a1dd660cd4c67b28f40deb5f065ec8684af86352b9e8600f959c66335ed44These are the open PRs that touch
adopt-project.py, and #1122 also touchestests/test_adopt_unsearchable_source.py. #1189 and #1190 opened, and #1169 and #1171 moved, after the first run of these; the four were merged again at the heads shown, just before the push, and the other heads were read again and had not moved. The merge with #1122 is clean as text, and see "Why stacked" for what it does not settle.Out of scope
originishttps://github.com/<40 c's>/Thingis read byurl_repositoryinscripts/repo_shape.py, a pinned script that shares the reading withshape-doctor.py, and not by_github_name. It still records that name, and--org testorgwould be told--org <40 c's>. I read this in the code and did not measure it._github_nametakes owner characters GitHub gives no owner:., a trailing-, and--._is let by on purpose, for GitHub Enterprise Managed Users (family.pyinit, adopt-project.pycheckand the--orgof all four tools:initsets thexf-project-<id>topic last and asks no GitHub limit,checkpasses a nameexecuterefuses, and--org my_org.xpasses every plan #863'sGITHUB_OWNER_CHARACTERS). The brief limited this change to the two lengths.source-unresolvablefor a spelling past the limits says "neither a path that exists nor anorg/reponame", which is true now, but does not say which limit it broke.Closes #1129
🤖 Generated with Claude Code
Lane: openreposhape-4 (openRepoShape-4)
Summary by Sourcery
Reject GitHub owner and repository names that exceed supported lengths before resolution or cloning, while preserving valid boundary names and local-path adoption.
Bug Fixes:
Enhancements:
_github_nameand align source resolution behavior across plan, check, and execute.Tests:
ghcalls.