diff --git a/packages/code-storage-go/README.md b/packages/code-storage-go/README.md index b62c9d2..13b4bd4 100644 --- a/packages/code-storage-go/README.md +++ b/packages/code-storage-go/README.md @@ -285,6 +285,23 @@ for _, commit := range commits.Commits { } ``` +### Get an applicable commit diff + +```go +diff, err := repo.GetCommitDiff(context.Background(), storage.GetCommitDiffOptions{ + SHA: "head-commit-sha", + BaseSHA: "base-commit-sha", + GitApplyCompatible: true, +}) +if err != nil { + log.Fatal(err) +} +``` + +`GitApplyCompatible` generates raw diffs for use with `git apply`. When no files are filtered and every +changed file has non-empty `Raw`, concatenate each `diff.Files[i].Raw` in response order to produce +a patch for the exact base tree. + TTL fields use `time.Duration` values (for example `time.Hour`). ### Hydrate a repo without an API request diff --git a/packages/code-storage-go/repo.go b/packages/code-storage-go/repo.go index 82d7bdc..bf57087 100644 --- a/packages/code-storage-go/repo.go +++ b/packages/code-storage-go/repo.go @@ -910,6 +910,9 @@ func (r *Repo) GetCommitDiff(ctx context.Context, options GetCommitDiffOptions) if strings.TrimSpace(options.BaseSHA) != "" { params.Set("baseSha", options.BaseSHA) } + if options.GitApplyCompatible { + params.Set("gitApplyCompatible", "true") + } for _, path := range options.Paths { if strings.TrimSpace(path) != "" { params.Add("path", path) diff --git a/packages/code-storage-go/repo_test.go b/packages/code-storage-go/repo_test.go index 374c647..2c8bc0c 100644 --- a/packages/code-storage-go/repo_test.go +++ b/packages/code-storage-go/repo_test.go @@ -1183,7 +1183,7 @@ func TestCommitDiffQuery(t *testing.T) { t.Fatalf("unexpected path: %s", r.URL.Path) } q := r.URL.Query() - if q.Get("sha") != "abc" || q.Get("baseSha") != "base" { + if q.Get("sha") != "abc" || q.Get("baseSha") != "base" || q.Get("gitApplyCompatible") != "true" { t.Fatalf("unexpected query: %s", r.URL.RawQuery) } w.Header().Set("Content-Type", "application/json") @@ -1197,7 +1197,7 @@ func TestCommitDiffQuery(t *testing.T) { } repo := &Repo{ID: "repo", DefaultBranch: "main", client: client} - result, err := repo.GetCommitDiff(nil, GetCommitDiffOptions{SHA: "abc", BaseSHA: "base"}) + result, err := repo.GetCommitDiff(nil, GetCommitDiffOptions{SHA: "abc", BaseSHA: "base", GitApplyCompatible: true}) if err != nil { t.Fatalf("commit diff error: %v", err) } diff --git a/packages/code-storage-go/types.go b/packages/code-storage-go/types.go index 9b0b588..e4fa91d 100644 --- a/packages/code-storage-go/types.go +++ b/packages/code-storage-go/types.go @@ -22,8 +22,8 @@ const ( // Options configure the Git storage client. type Options struct { Name string - Key string // required unless Token is set - Token string // pre-minted JWT, used verbatim if set + Key string // required unless Token is set + Token string // pre-minted JWT, used verbatim if set APIBaseURL string StorageBaseURL string APIVersion int @@ -866,9 +866,10 @@ type GetBranchDiffResult struct { // GetCommitDiffOptions configures commit diff. type GetCommitDiffOptions struct { InvocationOptions - SHA string - BaseSHA string - Paths []string + SHA string + BaseSHA string + GitApplyCompatible bool // Generate raw diffs that can be applied to the base tree. + Paths []string } // GetCommitDiffResult describes commit diff. diff --git a/packages/code-storage-go/version.go b/packages/code-storage-go/version.go index 771af0e..ddd759e 100644 --- a/packages/code-storage-go/version.go +++ b/packages/code-storage-go/version.go @@ -2,7 +2,7 @@ package storage const ( PackageName = "code-storage-go-sdk" - PackageVersion = "1.16.0" + PackageVersion = "1.16.1" ) func userAgent() string { diff --git a/packages/code-storage-python/README.md b/packages/code-storage-python/README.md index ef90d35..2357f83 100644 --- a/packages/code-storage-python/README.md +++ b/packages/code-storage-python/README.md @@ -359,6 +359,8 @@ print(branch_diff["files"]) # Get commit diff commit_diff = await repo.get_commit_diff( sha="abc123...", + base_sha="def456...", # optional base commit + git_apply_compatible=True, # generate raw diffs for use with git apply ) print(commit_diff["stats"]) print(commit_diff["files"]) @@ -971,6 +973,7 @@ class Repo: *, sha: str, base_sha: Optional[str] = None, + git_apply_compatible: Optional[bool] = None, paths: Optional[List[str]] = None, ttl: Optional[int] = None, ) -> GetCommitDiffResult: ... diff --git a/packages/code-storage-python/pierre_storage/repo.py b/packages/code-storage-python/pierre_storage/repo.py index 57bc7d1..240273e 100644 --- a/packages/code-storage-python/pierre_storage/repo.py +++ b/packages/code-storage-python/pierre_storage/repo.py @@ -1893,6 +1893,7 @@ async def get_commit_diff( *, sha: str, base_sha: Optional[str] = None, + git_apply_compatible: Optional[bool] = None, paths: Optional[list[str]] = None, ttl: Optional[int] = None, ) -> GetCommitDiffResult: @@ -1901,6 +1902,8 @@ async def get_commit_diff( Args: sha: Commit SHA base_sha: Optional base commit SHA to compare against + git_apply_compatible: Generate raw diffs that can be applied to the base tree. Defaults to + False. paths: Optional paths to filter the diff to specific files ttl: Token TTL in seconds @@ -1913,6 +1916,8 @@ async def get_commit_diff( params: list[tuple[str, str]] = [("sha", sha)] if base_sha: params.append(("baseSha", base_sha)) + if git_apply_compatible is not None: + params.append(("gitApplyCompatible", str(git_apply_compatible).lower())) if paths: for p in paths: params.append(("path", p)) diff --git a/packages/code-storage-python/pierre_storage/types.py b/packages/code-storage-python/pierre_storage/types.py index 101d68c..c96a4d2 100644 --- a/packages/code-storage-python/pierre_storage/types.py +++ b/packages/code-storage-python/pierre_storage/types.py @@ -1064,6 +1064,7 @@ async def get_commit_diff( *, sha: str, base_sha: Optional[str] = None, + git_apply_compatible: Optional[bool] = None, paths: Optional[list[str]] = None, ttl: Optional[int] = None, ) -> GetCommitDiffResult: diff --git a/packages/code-storage-python/pierre_storage/version.py b/packages/code-storage-python/pierre_storage/version.py index c8430e8..d7ced78 100644 --- a/packages/code-storage-python/pierre_storage/version.py +++ b/packages/code-storage-python/pierre_storage/version.py @@ -1,7 +1,7 @@ """Version information for Pierre Storage SDK.""" PACKAGE_NAME = "code-storage-py-sdk" -PACKAGE_VERSION = "1.16.1" +PACKAGE_VERSION = "1.16.2" def get_user_agent() -> str: diff --git a/packages/code-storage-python/pyproject.toml b/packages/code-storage-python/pyproject.toml index e75f548..d8aa9c2 100644 --- a/packages/code-storage-python/pyproject.toml +++ b/packages/code-storage-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pierre-storage" -version = "1.16.1" +version = "1.16.2" description = "Pierre Git Storage SDK for Python" readme = "README.md" license = "MIT" diff --git a/packages/code-storage-python/tests/test_repo.py b/packages/code-storage-python/tests/test_repo.py index ee2803e..7453ac2 100644 --- a/packages/code-storage-python/tests/test_repo.py +++ b/packages/code-storage-python/tests/test_repo.py @@ -3055,7 +3055,9 @@ async def test_get_commit_diff_with_base_sha(self, git_storage_options: dict) -> mock_client.return_value.__aenter__.return_value.get = mock_get repo = await storage.create_repo(id="test-repo") - result = await repo.get_commit_diff(sha="abc123", base_sha="def456") + result = await repo.get_commit_diff( + sha="abc123", base_sha="def456", git_apply_compatible=True + ) assert result is not None assert result["stats"]["additions"] == 5 @@ -3068,6 +3070,7 @@ async def test_get_commit_diff_with_base_sha(self, git_storage_options: dict) -> params = parse_qs(parsed.query) assert params["sha"] == ["abc123"] assert params["baseSha"] == ["def456"] + assert params["gitApplyCompatible"] == ["true"] class TestRepoUpstreamOperations: diff --git a/packages/code-storage-python/uv.lock b/packages/code-storage-python/uv.lock index 5e600d2..dfdf4e1 100644 --- a/packages/code-storage-python/uv.lock +++ b/packages/code-storage-python/uv.lock @@ -915,7 +915,7 @@ wheels = [ [[package]] name = "pierre-storage" -version = "1.13.0" +version = "1.16.2" source = { editable = "." } dependencies = [ { name = "cryptography" }, diff --git a/packages/code-storage-typescript/README.md b/packages/code-storage-typescript/README.md index 5216407..6682af6 100644 --- a/packages/code-storage-typescript/README.md +++ b/packages/code-storage-typescript/README.md @@ -339,6 +339,8 @@ console.log(branchDiff.files); // Get commit diff const commitDiff = await repo.getCommitDiff({ sha: 'abc123...', + baseSha: 'def456...', // optional base commit + gitApplyCompatible: true, // generate raw diffs for use with git apply }); console.log(commitDiff.stats); console.log(commitDiff.files); @@ -934,6 +936,7 @@ interface GetBranchDiffOptions { interface GetCommitDiffOptions { sha: string; baseSha?: string; + gitApplyCompatible?: boolean; // default false; generate raw diffs for use with git apply paths?: string[]; ttl?: number; } diff --git a/packages/code-storage-typescript/package.json b/packages/code-storage-typescript/package.json index 95444ea..538da70 100644 --- a/packages/code-storage-typescript/package.json +++ b/packages/code-storage-typescript/package.json @@ -1,6 +1,6 @@ { "name": "@pierre/storage", - "version": "1.15.0", + "version": "1.15.1", "description": "Pierre Git Storage SDK", "repository": { "type": "git", diff --git a/packages/code-storage-typescript/src/index.ts b/packages/code-storage-typescript/src/index.ts index 71b2b69..3da6827 100644 --- a/packages/code-storage-typescript/src/index.ts +++ b/packages/code-storage-typescript/src/index.ts @@ -1575,6 +1575,9 @@ class RepoImpl implements Repo { if (options.baseSha) { params.baseSha = options.baseSha; } + if (typeof options.gitApplyCompatible === 'boolean') { + params.gitApplyCompatible = String(options.gitApplyCompatible); + } if (options.paths && options.paths.length > 0) { params.path = options.paths; } diff --git a/packages/code-storage-typescript/src/types.ts b/packages/code-storage-typescript/src/types.ts index ef9bbc3..47320c9 100644 --- a/packages/code-storage-typescript/src/types.ts +++ b/packages/code-storage-typescript/src/types.ts @@ -731,6 +731,8 @@ export interface GetBranchDiffResult { export interface GetCommitDiffOptions extends GitStorageInvocationOptions { sha: string; baseSha?: string; + /** Generate raw diffs that can be applied to the exact base tree. Defaults to false. */ + gitApplyCompatible?: boolean; /** Optional paths to filter the diff to specific files */ paths?: string[]; } diff --git a/packages/code-storage-typescript/tests/index.test.ts b/packages/code-storage-typescript/tests/index.test.ts index 3858491..041807c 100644 --- a/packages/code-storage-typescript/tests/index.test.ts +++ b/packages/code-storage-typescript/tests/index.test.ts @@ -2938,6 +2938,38 @@ describe('GitStorage', () => { }); }); + describe('Repo getCommitDiff', () => { + it('forwards gitApplyCompatible to the API params', async () => { + const store = new GitStorage({ name: 'v0', key }); + const repo = await store.createRepo({ id: 'repo-commit-diff' }); + + mockFetch.mockImplementationOnce((url) => { + const requestUrl = new URL(url as string); + expect(requestUrl.searchParams.get('sha')).toBe('head'); + expect(requestUrl.searchParams.get('baseSha')).toBe('base'); + expect(requestUrl.searchParams.get('gitApplyCompatible')).toBe('true'); + + return Promise.resolve({ + ok: true, + status: 200, + statusText: 'OK', + json: async () => ({ + sha: 'head', + stats: { files: 0, additions: 0, deletions: 0, changes: 0 }, + files: [], + filtered_files: [], + }), + } as any); + }); + + await repo.getCommitDiff({ + sha: 'head', + baseSha: 'base', + gitApplyCompatible: true, + }); + }); + }); + describe('Repo restoreCommit', () => { it('should post metadata to the restore endpoint and return the response', async () => { const store = new GitStorage({ name: 'v0', key }); diff --git a/skills/code-storage/SKILL.md b/skills/code-storage/SKILL.md index 40d0880..7192127 100644 --- a/skills/code-storage/SKILL.md +++ b/skills/code-storage/SKILL.md @@ -476,11 +476,16 @@ Errors: `400` missing/blank `sha`, `404` commit not found. ## GET /repos/diff — Get Commit Diff ```bash -curl "$CODE_STORAGE_BASE_URL/repos/diff?sha=COMMIT_SHA&baseSha=OPTIONAL_BASE&path=src/foo.go" \ +curl "$CODE_STORAGE_BASE_URL/repos/diff?sha=COMMIT_SHA&baseSha=OPTIONAL_BASE&gitApplyCompatible=true&path=src/foo.go" \ -H "Authorization: Bearer $CODE_STORAGE_TOKEN" ``` -Params: `sha`(required), `baseSha`, `path` (repeatable) +Params: `sha` (required), `baseSha`, `gitApplyCompatible` (boolean), `path` (repeatable). +The default suppresses changes in the amount of whitespace for review readability. Set +`gitApplyCompatible=true` when the raw output will be passed to `git apply`; it includes whitespace +changes consistently in file discovery, raw diffs, and stats. When `filtered_files` is empty and +every changed file has non-empty `raw`, concatenating `files[].raw` in response order produces a +patch for the exact base tree. Response: `{ "sha", "stats", "files": [...], "filtered_files": [...] }` Large files (>500KB) or binary files appear in `filtered_files` without diff content.