Repository navigation
feat(cases): clone a repository into a custom folder - #744
opticon454 wants to merge 5 commits into
Conversation
Clone Repo could only clone under ~/codeman-cases. POST /api/cases/clone now takes an optional path (parent folder plus case name) and the Clone tab gets a Clone into a custom folder option with a Browse button, mirroring Create in a custom folder. The path is judged by prepareNewCasePath, the clone is registered as a linked case, the option is admin-only in multi-user mode, and an existing target folder is refused.
The custom folder was treated as a parent, so the repository landed in <folder>/<case name>. The chosen folder is now the clone destination: an empty existing folder is cloned into directly (CloneOptions.intoEmptyDir, no temporary sibling, never removed on failure), a new folder is created, and a folder with files is refused.
|
Pushed 68bd7e4. The folder you pick is now the clone destination itself: the repository is cloned into it directly rather than into a |
|
Thanks @opticon454 for this! It lets Clone Repo clone straight into a folder you pick (empty or not yet created) and registers it as a linked case, with the same path policy and admin gate as Create in a custom folder. Reusing One thing needs fixing before merge. 1. A failed clone into an existing empty folder deletes whatever is in it, including another attempt's finished clone (
This is the regression the Asks:
Smaller things, fine to fold into the same push:
Once item 1 is in with its test, this is ready to merge. Thanks again for picking this up. |
…clone A direct clone into an empty folder emptied the folder after any failure, which deleted a concurrent attempt's finished clone when git had written nothing. Clones into one folder are now mutually exclusive in-process, the cleanup is skipped when git refused the folder, never ran, or the mount is unresponsive, and a name linked while the clone ran is a 409 that keeps the folder. Documents path for POST /api/cases/clone.
|
Pushed 086249a, thanks for the careful repro.
Full |
|
Thanks @opticon454 for the quick turnaround on the last round. This PR adds Clone into a custom folder to Clone Repo: the repository is cloned into a folder you pick (empty or not yet created) and registered as a linked case, under the same path policy and admin gate as Create in a custom folder. The in-flight Two things still need fixing before merge. 1. A direct clone whose git-slot wait times out still empties the folder ( This is the "git never ran (slot timeout)" half of item 1 from my last comment, and the gap is in my ask, not in your code: I assumed a slot timeout comes back with a I reproduced it: two clones of Asks:
2. Refuse a I missed this in the first round. Codeman never runs git in a folder a container can write to: Git status, diff, fetch and worktrees all refuse a repository at or inside a Docker case's Asks:
The default Optional, fine to leave as it is: Once both items are in with their tests, this is ready to merge. Thanks again for sticking with it. |
…rkspaces A direct clone whose git-slot wait timed out came back with no spawnError, so the cleanup emptied a folder git never touched. GitRun now says whether git started and the cleanup requires it. Cloning into a folder at or inside a Docker case workspace is refused, as git status, fetch and worktrees do, since git would run the new repository's hooks and filters on the host.
|
Pushed b549a66, thanks. Both points were right.
Docs: |
|
Thanks @opticon454 for the third round. This PR lets Clone Repo clone straight into a folder you pick (empty or not yet created) and registers it as a linked case, under the same path policy and admin gate as Create in a custom folder. Both items from my last comment are in and work as described: One thing still needs fixing before merge. 1. A failed direct clone still empties a folder git never wrote to when its path contains The cleanup is skipped only when I reproduced it with your slot-hold harness: two Asks:
Smaller things, fine to fold into the same push:
Once item 1 is in with its test, this is ready to merge. Thanks again for sticking with it through three rounds. |
…he folder git's refusal quotes the destination, so a folder named openssl or rustls classified as HOST_UNREACHABLE and the cleanup emptied it. The cleanup now tests git's own text, DESTINATION_EXISTS is matched first, and ssl/tls are matched as words. Also: a test for the Docker refusal on the default branch, the route JSDoc and constant comment, and the folder copy (empty, or new).
|
Pushed 41186f5, thanks. The repro was exactly right.
Smaller items: a route test for the Docker refusal on the default Full |
|
Thanks @opticon454 for this, and for staying with it through four rounds. This PR adds Clone into a custom folder to Clone Repo: the repository is cloned straight into a folder you pick (empty or not yet created) and registered as a linked case, under the same path policy and admin gate as Create in a custom folder, and Clone Repo now refuses a destination at or inside a Docker case workspace on both branches. Every ask from the last round is in and works as described: the cleanup is gated on git's own refusal text and on A few small things are left. None of them needs another push from you; I will apply them on top when merging:
Thanks again, this is a clean addition and the careful repro tests make it easy to trust. |
Context
Add Case has a Clone Repo tab, but it can only clone into
~/codeman-cases/<name>. Create New already lets you pick a folder instead (Create in a custom folder,POST /api/caseswith apath); Clone Repo had no equivalent, so anyone who keeps projects somewhere else (a work drive,~/projects, a mount) had to clone by hand and then use Link Existing.This adds the same option to Clone Repo: Clone into a custom folder, with a Folder field and a Browse button. The repository is cloned into the folder you pick (no subfolder is added, the case name does not affect the path) and is registered as a linked case, so it lists, resolves and deletes like any other linked case.
What changed
POST /api/cases/clonetakes an optionalpath: the folder to clone into, exactly as given. Without it nothing changes.prepareNewCasePath(), the same policy Create in a custom folder uses: absolute or~, no traversal or shell characters, not a system directory, the home folder itself, Codeman's data folder, a credentials tree (~/.ssh,~/.aws, ...), or the cases folder or one of its direct children; the parent must already exist; symlinks are resolved and judged again.400,503when the Docker case list cannot be read), as git status, fetch and worktrees do: git would run the new repository's hooks and filters on this machine.CloneOptions.intoEmptyDir): no temporary sibling and no rename, so a mount point works, and a failed or timed-out attempt empties it again but never removes the folder. A symlink, or a folder with anything in it, is refused with a message saying so.The UI mirrors Create New: a checkbox, a Folder field with Browse, and a live "Will clone into: ..." preview. The case name only names the case.
Verification
test/routes/case-clone-custom-folder.test.ts(22 tests, realgitagainst a local bare repo, real filesystem under the sandboxed HOME): clone into a new folder with scaffold, hooks and registry entry; clone straight into an existing empty folder with no<case name>subfolder, even when the name differs; the empty folder stays, and stays empty, when the clone fails;~expansion; missing parent (404, nothing created); non-empty target (409, files untouched);~/.sshrefused; name already linked; failed clone into a new folder leaves nothing and no registry entry; nopathstill clones under the cases folder unlinked; two clones into one empty folder at once (exactly one wins, its files survive); a name linked while the clone ran (409, folder kept and named); a direct clone whose git-slot wait times out leaves what was written into the folder meanwhile; a folder at or inside a Docker case workspace is refused and nothing is created; an unreadable Docker case list fails closed (503); a refused folder namedplain,openssl,rustlsordoes not existkeeps what was written during the git-slot wait and reportsDESTINATION_EXISTS; the Docker refusal on the default (nopath) branch; multi-user non-admin 403 with nothing cloned, admin allowed.npm run typecheck,lint,format:check,check:frontend-syntaxclean.npm teston the latest commit: 667 files, 13,526 tests passed, 12 skipped, 0 failed.docs/wiki/Core-Concepts.md, the Clone Repo paragraph indocs/architecture-invariants.mdandCLAUDE.md.Commits