From ebfe991a15c344f15afb1ab965b146d8d889efd5 Mon Sep 17 00:00:00 2001 From: marxlaml Date: Fri, 22 May 2026 18:38:21 +0200 Subject: [PATCH] fix(web): support cloning existing repositories - Make the add repository dialog explicitly support cloning from a remote git server - Keep blank repo creation as a fallback option - Add validation and tests for the clone-first flow Quality gates: tsc --noEmit, vitest run src/components/repositories-settings-tab.test.tsx --- .../repositories-settings-tab.test.tsx | 26 ++++++++++-- .../components/repositories-settings-tab.tsx | 41 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/repositories-settings-tab.test.tsx b/apps/web/src/components/repositories-settings-tab.test.tsx index c223e4b..69e5304 100644 --- a/apps/web/src/components/repositories-settings-tab.test.tsx +++ b/apps/web/src/components/repositories-settings-tab.test.tsx @@ -32,7 +32,7 @@ afterEach(() => { }); describe("RepositoriesSettingsTab", () => { - it("opens create dialog and submits a repository", async () => { + it("opens create dialog and clones an existing repository", async () => { const listMock = vi.spyOn(gitRepositoriesApi, "listRepositories").mockResolvedValue(mockRepositories); const createMock = vi.spyOn(gitRepositoriesApi, "createRepository").mockResolvedValue(mockRepositories[0]); @@ -46,14 +46,34 @@ describe("RepositoriesSettingsTab", () => { fireEvent.change(screen.getByPlaceholderText(/repository-name/i), { target: { value: "New Repo" }, }); - fireEvent.click(screen.getByRole("button", { name: /create/i })); + fireEvent.change(screen.getByPlaceholderText(/github.com\/user\/repo.git/i), { + target: { value: "https://github.com/user/repo.git" }, + }); + fireEvent.click(screen.getByRole("button", { name: /clone repository/i })); await waitFor(() => { expect(createMock).toHaveBeenCalledWith("proj-1", { name: "New Repo", - remote_url: undefined, + remote_url: "https://github.com/user/repo.git", }); }); expect(listMock).toHaveBeenCalledTimes(2); }); + + it("shows validation when cloning without a remote url", async () => { + vi.spyOn(gitRepositoriesApi, "listRepositories").mockResolvedValue(mockRepositories); + render(); + + await waitFor(() => { + expect(screen.getByText("Main Repo")).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole("button", { name: /add repository/i })); + fireEvent.change(screen.getByPlaceholderText(/repository-name/i), { + target: { value: "New Repo" }, + }); + fireEvent.click(screen.getByRole("button", { name: /clone repository/i })); + + expect(screen.getByText(/remote url is required/i)).toBeInTheDocument(); + }); }); diff --git a/apps/web/src/components/repositories-settings-tab.tsx b/apps/web/src/components/repositories-settings-tab.tsx index 7ff9a62..c7545ee 100644 --- a/apps/web/src/components/repositories-settings-tab.tsx +++ b/apps/web/src/components/repositories-settings-tab.tsx @@ -15,6 +15,7 @@ export const RepositoriesSettingsTab: React.FC = () => { const [repositories, setRepositories] = useState([]); const [loading, setLoading] = useState(true); const [showCreate, setShowCreate] = useState(false); + const [createMode, setCreateMode] = useState<"clone" | "blank">("clone"); const [formName, setFormName] = useState(""); const [formRemoteUrl, setFormRemoteUrl] = useState(""); const [error, setError] = useState(""); @@ -49,6 +50,10 @@ export const RepositoriesSettingsTab: React.FC = () => { setError("Repository name is required"); return; } + if (createMode === "clone" && !formRemoteUrl.trim()) { + setError("Remote URL is required to clone an existing repository"); + return; + } try { const input: GitRepositoryCreate = { @@ -57,6 +62,7 @@ export const RepositoriesSettingsTab: React.FC = () => { }; await createRepository(projectId, input); setShowCreate(false); + setCreateMode("clone"); setFormName(""); setFormRemoteUrl(""); await loadRepositories(); @@ -82,7 +88,11 @@ export const RepositoriesSettingsTab: React.FC = () => {

Repositories

- @@ -117,9 +127,32 @@ export const RepositoriesSettingsTab: React.FC = () => {

Add Repository

+

+ Clone an existing repository from a git server, or create a blank bare repo here. +

+
+ + +