diff --git a/.changeset/git-clone-depth-option.md b/.changeset/git-clone-depth-option.md new file mode 100644 index 00000000..8c187873 --- /dev/null +++ b/.changeset/git-clone-depth-option.md @@ -0,0 +1,5 @@ +--- +"@upstash/box": patch +--- + +adds depth option to git.clone for shallow clones diff --git a/packages/python-sdk/CHANGELOG.md b/packages/python-sdk/CHANGELOG.md index 96ad9b13..03693f53 100644 --- a/packages/python-sdk/CHANGELOG.md +++ b/packages/python-sdk/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to `upstash-box` (Python) are documented here. ## Unreleased +- `git.clone(depth=...)` — shallow clone support (`git clone --depth N`). + `depth=1` fetches only the latest commit; omitting it keeps the current + full-clone behavior. Mirrors `depth` in `@upstash/box` `git.clone`. - Add Claude Opus 5 model constants for Claude Code, OpenRouter, Vercel AI Gateway, OpenCode/Zen, and Cursor, mirroring `@upstash/box`. - `schedule.update(id, ...)` — partial schedule updates (PATCH). Omitted diff --git a/packages/python-sdk/README.md b/packages/python-sdk/README.md index fde89c72..42642555 100644 --- a/packages/python-sdk/README.md +++ b/packages/python-sdk/README.md @@ -176,6 +176,7 @@ await box.files.download(folder="output/") ```python await box.git.clone(repo="https://github.com/user/repo", branch="main") +await box.git.clone(repo="https://github.com/user/repo", depth=1) # shallow clone diff = await box.git.diff() await box.git.commit(message="feat: add feature") await box.git.push(branch="main") diff --git a/packages/python-sdk/tests/_async/test_box_git.py b/packages/python-sdk/tests/_async/test_box_git.py index b5e2bb2f..8eafd179 100644 --- a/packages/python-sdk/tests/_async/test_box_git.py +++ b/packages/python-sdk/tests/_async/test_box_git.py @@ -18,6 +18,17 @@ async def test_clone_includes_git_token(): assert body["repo"] == "https://github.com/u/r" assert body["branch"] == "main" assert body["github_token"] == "ght" + assert "depth" not in body + await box.aclose() + + +@respx.mock +async def test_clone_with_depth(): + box = await make_async_box(respx.mock) + route = respx.post(f"{BASE}/git/clone").mock(return_value=httpx.Response(200, json={})) + await box.git.clone(repo="https://github.com/u/r", depth=1) + body = last_json_body(route) + assert body["depth"] == 1 await box.aclose() diff --git a/packages/python-sdk/upstash_box/_async/client.py b/packages/python-sdk/upstash_box/_async/client.py index 98817ecc..91d2e2d1 100644 --- a/packages/python-sdk/upstash_box/_async/client.py +++ b/packages/python-sdk/upstash_box/_async/client.py @@ -319,8 +319,10 @@ class AsyncGitNamespace: def __init__(self, box: "AsyncBox") -> None: self._box = box - async def clone(self, *, repo: str, branch: Optional[str] = None) -> None: - await self._box._git_clone(repo, branch) + async def clone( + self, *, repo: str, branch: Optional[str] = None, depth: Optional[int] = None + ) -> None: + await self._box._git_clone(repo, branch, depth) async def diff(self) -> str: return await self._box._git_diff() @@ -1522,9 +1524,11 @@ async def _schedule_delete(self, id: str) -> None: # ==================== Git ==================== - async def _git_clone(self, repo, branch) -> None: + async def _git_clone(self, repo, branch, depth) -> None: folder = self._get_folder() body: Dict[str, Any] = {"repo": repo, "branch": branch, "github_token": self._git_token} + if depth is not None: + body["depth"] = depth if folder: body["folder"] = folder await self._request("POST", f"/v2/box/{self.id}/git/clone", body=body) diff --git a/packages/python-sdk/upstash_box/_sync/client.py b/packages/python-sdk/upstash_box/_sync/client.py index 4559b3c8..52eed364 100644 --- a/packages/python-sdk/upstash_box/_sync/client.py +++ b/packages/python-sdk/upstash_box/_sync/client.py @@ -314,8 +314,10 @@ class GitNamespace: def __init__(self, box: "Box") -> None: self._box = box - def clone(self, *, repo: str, branch: Optional[str] = None) -> None: - self._box._git_clone(repo, branch) + def clone( + self, *, repo: str, branch: Optional[str] = None, depth: Optional[int] = None + ) -> None: + self._box._git_clone(repo, branch, depth) def diff(self) -> str: return self._box._git_diff() @@ -1507,9 +1509,11 @@ def _schedule_delete(self, id: str) -> None: # ==================== Git ==================== - def _git_clone(self, repo, branch) -> None: + def _git_clone(self, repo, branch, depth) -> None: folder = self._get_folder() body: Dict[str, Any] = {"repo": repo, "branch": branch, "github_token": self._git_token} + if depth is not None: + body["depth"] = depth if folder: body["folder"] = folder self._request("POST", f"/v2/box/{self.id}/git/clone", body=body) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 42c2ab8b..f1fd5989 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -168,6 +168,7 @@ await box.files.download({ folder: "output/" }); ```ts await box.git.clone({ repo: "https://github.com/user/repo", branch: "main" }); +await box.git.clone({ repo: "https://github.com/user/repo", depth: 1 }); // shallow clone const diff = await box.git.diff(); const status = await box.git.status(); await box.git.commit({ diff --git a/packages/sdk/src/__tests__/box-git.test.ts b/packages/sdk/src/__tests__/box-git.test.ts index a7f94e1e..402c592a 100644 --- a/packages/sdk/src/__tests__/box-git.test.ts +++ b/packages/sdk/src/__tests__/box-git.test.ts @@ -26,6 +26,26 @@ describe("Box git operations", () => { const body = JSON.parse(fetchMock.mock.calls[1]![1]?.body as string); expect(body.branch).toBe("dev"); }); + + it("clones a repo with depth", async () => { + const { box, fetchMock } = await createTestBox(); + fetchMock.mockResolvedValueOnce(mockResponse({})); + + await box.git.clone({ repo: "owner/repo", depth: 1 }); + + const body = JSON.parse(fetchMock.mock.calls[1]![1]?.body as string); + expect(body.depth).toBe(1); + }); + + it("omits depth when not provided", async () => { + const { box, fetchMock } = await createTestBox(); + fetchMock.mockResolvedValueOnce(mockResponse({})); + + await box.git.clone({ repo: "owner/repo" }); + + const body = JSON.parse(fetchMock.mock.calls[1]![1]?.body as string); + expect(body).not.toHaveProperty("depth"); + }); }); describe("git.diff", () => { diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 188f6f22..9336175b 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -2670,6 +2670,7 @@ export class Box { body: { repo: options.repo, branch: options.branch, + depth: options.depth, github_token: this._gitToken, ...(folder ? { folder } : {}), }, diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 3cf8ec7d..3fd167ff 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -902,6 +902,8 @@ export interface FileEntry { export interface GitCloneOptions { repo: string; branch?: string; + /** History depth (git clone --depth N); depth: 1 = shallow clone. Omit for a full clone. */ + depth?: number; } export interface GitExecOptions { diff --git a/skills/SKILL.md b/skills/SKILL.md index cc7aecd1..83c2f757 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -151,6 +151,8 @@ await box.cd("/workspace/home/other") // absolute path ```ts await box.git.clone({ repo: "github.com/org/repo", branch: "main" }) +// large repo? depth: 1 fetches only the latest commit +await box.git.clone({ repo: "github.com/org/repo", depth: 1 }) await box.cd("repo") // cd into cloned repo const status = await box.git.status()