feat(projects): add project from a Git URL (#94) - #98
Merged
Merged
Conversation
POST /api/projects/clone runs `git clone <url>` into <parentDir>/<repo> and registers the result as a local project through registerLocalProject(), which the plain POST /api/projects now shares. The New-project modal gains one row: a URL (and optional branch) that turns "Add project" into a clone into the folder the user browsed to. git-clone.js holds the pure half: - transports are an allowlist (http(s), ssh, git, user@host:path); ext:: runs a command and file:// clones anything readable, so both are refused, and the list also rides as GIT_ALLOW_PROTOCOL; - nothing user-supplied may look like an option: URL and target follow `--`, a branch starting with `-` is refused, the directory name is one path segment joined onto a parent that passed isPathAllowed(); - it fails instead of waiting: stdin closed, GIT_TERMINAL_PROMPT=0, no TTY, CCS_GIT_CLONE_TIMEOUT_MS (10 min) backstop, target removed on failure. test/git-clone.test.js pins the parser and drives the endpoint against a real server whose PATH holds a fake git (argv/env recorded, failure on demand). Verified live: a shallow clone of this repository over https in 8 s, a nonexistent repo refused in 0.4 s with git's own message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #94.
What
POST /api/projects/clonerunsgit clone <url>into<parentDir>/<repo>and registers the result as a local project. The New-project modal gets one extra row — a Git URL and an optional branch. The folder the user browsed to is the parent; a non-empty URL turns "Add project" into a clone. Name defaults to the repository name.Covers the acceptance criteria in the issue: HTTPS and SSH URLs, automatic clone, workspace configured automatically, clear error on failure (git's own last stderr line), existing creation flow untouched. Not built: progress display (the request stays open for the clone; the button is locked with "Cloning…"), remote-host clones (the remote flow already takes an existing path).
How (
git-clone.js— pure,server.js— endpoint,public/index.html— UI + i18n ×5)http(s)://,ssh://,git://,user@host:path.ext::executes a command andfile:/// a bare path clone anything readable — refused. The list also rides asGIT_ALLOW_PROTOCOLso a redirect/submodule cannot widen it.--; a branch starting with-is refused; the directory name is one path segment joined onto a parent that passedisPathAllowed()(a registered workdir widens that allowlist, so the gate is mandatory here too).GIT_TERMINAL_PROMPT=0, no TTY;CCS_GIT_CLONE_TIMEOUT_MS(10 min) backstop; target removed on any non-zero exit (a SIGKILLed clone does not clean up after itself).registerLocalProject()extracted so the plain create and the clone share one definition of a project row.Tests
test/git-clone.test.js(wired intonpm test): 25 pure checks + endpoint driven against a real server whosePATHholds a fakegit(records argv/env, fails on demand) — gates in order, argv shape, project registration, failure cleanup,dirName/name/shallowoverrides.Live check with real git: shallow clone of this repository over https in 8 s; a nonexistent/private GitHub URL refused in 0.4 s with
fatal: could not read Username for 'https://github.com': terminal prompts disabled— no hang.Cost / risk
https://user:token@host/…) ends up in the clone's.git/configexactly as it would from the CLI; the studio does not store it.🤖 Generated with Claude Code