Skip to content

feat(projects): add project from a Git URL (#94) - #98

Merged
Lexus2016 merged 2 commits into
mainfrom
feat/94-clone-project
Sep 11, 2026
Merged

Lexus2016 merged 2 commits into
mainfrom
feat/94-clone-project

Conversation

@Lexus2016

Copy link
Copy Markdown
Owner

Closes #94.

What

POST /api/projects/clone runs git 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)

  • Transports are an allowlist: http(s)://, ssh://, git://, user@host:path. ext:: executes a command and file:// / a bare path clone anything readable — refused. The list also rides as GIT_ALLOW_PROTOCOL so a redirect/submodule cannot widen it.
  • Nothing user-supplied reaches git as 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() (a registered workdir widens that allowlist, so the gate is mandatory here too).
  • Fails instead of waiting: stdin closed, 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 into npm test): 25 pure checks + endpoint driven against a real server whose PATH holds a fake git (records argv/env, fails on demand) — gates in order, argv shape, project registration, failure cleanup, dirName/name/shallow overrides.

$ node test/git-clone.test.js      → 50 passed, 0 failed
$ npm test                         → EXIT=0 (80 files)

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

  • The HTTP request stays open for the whole clone (up to 10 min). A reverse proxy with a shorter read timeout will cut the response while git keeps running; the clone still completes and the project appears on the next list refresh.
  • A URL carrying credentials (https://user:token@host/…) ends up in the clone's .git/config exactly as it would from the CLI; the studio does not store it.

🤖 Generated with Claude Code

Lexus2016 and others added 2 commits September 11, 2026 18:07
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>
@Lexus2016
Lexus2016 merged commit b46852f into main Sep 11, 2026
2 checks passed
@Lexus2016
Lexus2016 deleted the feat/94-clone-project branch September 11, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add Project via Git Clone

1 participant