Repository navigation
adopt-project.py: --source <org/repo> reads a directory of that name under the current one, a trailing / clones into source-readonly/ itself, and a recorded clone that is gone or emptied is refused for the wrong reason #652
Description
Lane: openreposhape-1 (openRepoShape-1)
plan --source <org/repo> should not read a directory of that name under the current one as the source. A trailing / should not make the clone land in source-readonly/ itself. A recorded clone that is gone or emptied should be refused for what it is. The plan should record whether plan made the clone (cloned:), and a clone no plan records should be removed.
From lane 1's triage of 2026-10-15, D3, approved by Brett Heap at 2026-10-16T11:21:17Z ("Approve all (Recommended)"). One pull request covers the lines below.
Parent: #457. Milestone: v1.1.0.
Lands after: #594, and D2's issue (#650, the --work-dir refusal and help text), which edits the same functions: _work_root, Plan.open_source, the head of cmd_check and the finally in main, as #594 leaves them. It is authored on D2's branch, or rebased once D2 lands.
Effort: M. Platform: all.
Lane openRepoShape-1 claims it at filing; see its CLAIMED comment.
Why this is P2.
- L1-24, probed on 396c0ff. With
./acme/Atlaspresent,plan --source acme/Atlasplanned that directory, cloned nothing and exited 0:Source.openasksPath(spec).exists()first. So a person working in a~/src/<org>/<repo>tree adopts a local copy they never named. Read again on 7ba64f0:Source.openstill askscandidate.exists()first. - L1-43, probed on 396c0ff.
--source acme/Atlas/cloned into<work>/source-readonlyitself, and recordedacme/Atlas/. Read again on 7ba64f0:Source.openstill names the clone byspec.split("/")[-1], which is empty here. - L1-22 and L1-26, on adopt-project.py and tests/conftest.py: a run without
--work-dirremoves the empty work directory it made, and a recorded clone that is gone is cloned again, so no emptyopenreposhape-adopt-*is left and nosource-unclonablefollows (#526) #594's head. A recorded clone with norepository:that is gone (L1-22) is refusedsource-unclonablewith agh auth statushint. One a temp cleaner emptied (L1-26) is refusedgit-failed. In both cases, passing--sourceagain works.
The fix. The plan records whether plan made the clone (cloned:, L1-27's narrowing). That answers L1-22, L1-26 and L1-27 together. A clone no plan records is removed (L1-23, #526's own subject).
Lines
- L1-22 (P2): a vanished
local_pathwith no recordedrepositorygoes togh repo cloneon POSIX and is refusedsource-unclonable, with agh auth statushint; on Windows it is refusedsource-unresolvable, naming a--sourcenobody passed._where_the_clone_is_gonecould say the path is gone. On be808a7, adopt-project.py and tests/conftest.py: a run without--work-dirremoves the empty work directory it made, and a recorded clone that is gone is cloned again, so no emptyopenreposhape-adopt-*is left and nosource-unclonablefollows (#526) #594's head, read. The line measured it on 652b686 (POSIX) and read it from the code (Windows). (source) - L1-24 (P2):
Source.opentestsPath(spec).exists()against the current directory, soorg/repois read as a local path when a directory of that name exists there, even for the re-clone whose NOTE says "is cloned again". On 396c0ff, probed: with./acme/Atlaspresent,plan --source acme/Atlasplanned that directory, cloned nothing and exited 0. (source) - L1-26 (P2): a recorded clone that a cleaner emptied or half-cleaned, directory still there, is not treated as gone:
checkrefusesgit-failedrather than cloning again. macOS's temp cleaner can leave clones that way. On be808a7, adopt-project.py and tests/conftest.py: a run without--work-dirremoves the empty work directory it made, and a recorded clone that is gone is cloned again, so no emptyopenreposhape-adopt-*is left and nosource-unclonablefollows (#526) #594's head, read:exists()at :4075. The line measured it on 652b686. (source)
Rides along (backlog; tick it here if the same change covers it)
- L1-23: a clone no plan records stays in its temp directory: the fresh clone
checkorexecutemakes, each--source <org/repo>run, and a refusedplan --source. Eachcheckafter the recorded clone is gone clones again and keeps 31 files where main left an emptysource-readonly/. On be808a7, adopt-project.py and tests/conftest.py: a run without--work-dirremoves the empty work directory it made, and a recorded clone that is gone is cloned again, so no emptyopenreposhape-adopt-*is left and nosource-unclonablefollows (#526) #594's head, read. The line measured it on 652b686. (source) - L1-27:
source.repositoryis read fromoriginon any host, so a moved non-GitHub local clone is re-cloned from github.com under that name;plan-stalekeeps it safe. Narrowing: re-clone only alocal_paththis tool made, or recordcloned:in the plan. Not re-checked since the line measured it on 652b686. (source) - L1-43:
--source acme/Atlas/with a trailing slash is compared asacme/Atlas/, andspec.split("/")[-1]is empty, so the clone target becomessource-readonly/itself. On 396c0ff, probed:--source acme/Atlas/cloned into<work>/source-readonlyitself, and recordedacme/Atlas/. (source)
Each line above is a line of #457's checklist comment (issuecomment-6200110061). Tick it there too when this closes.
The lines as written on the tracking issue
[L1-22] A vanished
local_pathwith no recordedrepositorygoes togh repo cloneon POSIX and is refusedsource-unclonable, with agh auth statushint; on Windows it is refusedsource-unresolvable, naming a--sourcenobody passed._where_the_clone_is_gonecould say the path is gone. Measured on 652b686 (POSIX); from the code (Windows). (Found in #594's writer and review.)
[L1-24]
Source.opentestsPath(spec).exists()against the current directory, soorg/repois read as a local path when a directory of that name exists there, even for the re-clone whose NOTE says "is cloned again". From the code at 652b686. (Found in #594's writer and review.)
[L1-26] A recorded clone that a cleaner emptied or half-cleaned, directory still there, is not treated as gone:
checkrefusesgit-failedrather than cloning again. macOS's temp cleaner can leave clones that way. Measured on 652b686. (Found in #594's review.)
[L1-23] A clone no plan records stays in its temp directory: the fresh clone
checkorexecutemakes, each--source <org/repo>run, and a refusedplan --source. Eachcheckafter the recorded clone is gone clones again and keeps 31 files where main left an emptysource-readonly/. Measured on 652b686. (Found in #594's writer and review.)
[L1-27]
source.repositoryis read fromoriginon any host, so a moved non-GitHub local clone is re-cloned from github.com under that name;plan-stalekeeps it safe. Narrowing: re-clone only alocal_paththis tool made, or recordcloned:in the plan. Measured on 652b686. (Found in #594's review.)
[L1-43]
--source acme/Atlas/with a trailing slash is compared asacme/Atlas/, andspec.split("/")[-1]is empty, so the clone target becomessource-readonly/itself. From the code at 182389e; gh is needed to probe it. (Found in #591's writer and review.)
Activity
brettheap commentedon Oct 16, 2026
CLAIMED — lane openRepoShape-1, session 300f8f46-004a-4e72-8587-4f192be2e108@Eagle, 2026-10-16T11:39:10Z, for #652
Logged in
lanes/log/openRepoShape-1.mdat89cde1d2db549e96d4d18c52b36e0843f7a97aa7.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 16, 2026
(lane openRepoShape-4, 2026-10-16T14:10:49Z) For lane openRepoShape-1, which holds #652 (CLAIMED at 2026-10-16T11:39:10Z).
One more line for #652, in the code its body already names. It comes from lane 4's checklist triage of 2026-10-16, approved by Brett Heap at 2026-10-16T13:36:07Z ("approve all triage recommendations"), and it is on the same
Source.openpath as L1-22, L1-24, L1-26 and L1-43..gitmodulesgives ismember-gitmodules-absent,removerefuses a holder with no.gitmodulesbeforedeinit, and two gitlink remedies name what they missed (#611) #639):Source.openclones before_checked_plan_inputs(adopt-project.py:3972,:3977) reaches_refuse_another_repository(:3450). adopt-project.py:--source <org/repo>reads a directory of that name under the current one, a trailing/clones intosource-readonly/itself, and a recorded clone that is gone or emptied is refused for the wrong reason #652's body does not hold it.Source.open(args.source, work_root)atadopt-project.py:3973,_checked_plan_inputsat:3978,_refuse_another_repositoryat:3451, andgh repo cloneat:1465insideSource.open(:1428).--source <org/repo>reads a directory of that name under the current one, a trailing/clones intosource-readonly/itself, and a recorded clone that is gone or emptied is refused for the wrong reason #652 already editsSource.open(thecandidate.exists()first, the trailing/and the namespec.split("/")[-1]). Theorg/repoit receives is the name L4-37 compares: asking_refuse_another_repository's question of the spec beforegh repo clonemakes a--projector--orgtypo refuse before a clone is made, as it does for a local source./in the spec) changes what that comparison reads, so the two are best taken together.If #652's pull request reorders
Source.open, tick L4-37 on #457 (issuecomment-6157842287) when it lands. If it does not, L4-37 stays a line on #457.Lane: openreposhape-4 (openRepoShape-4)
adopt-project.py: a clone is removed only at the real path it was mad…
adopt-project.py: `--source` is read by its spelling, an `org/repo` i…
tests/test_adopt_source_clone_record.py: `.` and `..` as the reposito…