Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/code-storage-go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,25 @@ if err != nil {
fmt.Println(result.CommitSHA)
```

### Inspect commit parents

`ListCommits` and `GetCommit` expose parent SHAs in Git parent order. Root
commits return an empty slice.

```go
commits, err := repo.ListCommits(context.Background(), storage.ListCommitsOptions{
Branch: "main",
Limit: 20,
})
if err != nil {
log.Fatal(err)
}

for _, commit := range commits.Commits {
fmt.Println(commit.SHA, commit.ParentSHAs)
}
```

TTL fields use `time.Duration` values (for example `time.Hour`).

### Hydrate a repo without an API request
Expand Down
2 changes: 2 additions & 0 deletions packages/code-storage-go/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ func (r *Repo) ListCommits(ctx context.Context, options ListCommitsOptions) (Lis
for _, commit := range payload.Commits {
result.Commits = append(result.Commits, CommitInfo{
SHA: commit.SHA,
ParentSHAs: commit.ParentSHAs,
Message: commit.Message,
AuthorName: commit.AuthorName,
AuthorEmail: commit.AuthorEmail,
Expand Down Expand Up @@ -556,6 +557,7 @@ func (r *Repo) GetCommit(ctx context.Context, options GetCommitOptions) (GetComm

commit := CommitInfo{
SHA: payload.Commit.SHA,
ParentSHAs: payload.Commit.ParentSHAs,
Message: payload.Commit.Message,
AuthorName: payload.Commit.AuthorName,
AuthorEmail: payload.Commit.AuthorEmail,
Expand Down
15 changes: 12 additions & 3 deletions packages/code-storage-go/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1937,7 +1937,7 @@ func TestListCommitsDateParsing(t *testing.T) {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"commits":[{"sha":"abc123","message":"feat: add endpoint","author_name":"Jane Doe","author_email":"jane@example.com","committer_name":"Jane Doe","committer_email":"jane@example.com","date":"2024-01-15T14:32:18Z"}],"has_more":false}`))
_, _ = w.Write([]byte(`{"commits":[{"sha":"abc123","parent_shas":["def456","789abc"],"message":"feat: add endpoint","author_name":"Jane Doe","author_email":"jane@example.com","committer_name":"Jane Doe","committer_email":"jane@example.com","date":"2024-01-15T14:32:18Z"}],"has_more":false}`))
}))
defer server.Close()

Expand All @@ -1955,6 +1955,9 @@ func TestListCommitsDateParsing(t *testing.T) {
t.Fatalf("expected one commit")
}
commit := result.Commits[0]
if !reflect.DeepEqual([]string{"def456", "789abc"}, commit.ParentSHAs) {
t.Fatalf("unexpected parent SHAs: %#v", commit.ParentSHAs)
}
if commit.RawDate != "2024-01-15T14:32:18Z" {
t.Fatalf("unexpected raw date")
}
Expand Down Expand Up @@ -2054,7 +2057,7 @@ func TestGetCommit(t *testing.T) {
t.Fatalf("unexpected sha query: %q", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"commit":{"sha":"abc123","message":"feat: add endpoint","author_name":"Jane Doe","author_email":"jane@example.com","committer_name":"Jane Doe","committer_email":"jane@example.com","date":"2024-01-15T14:32:18Z","signature":"-----BEGIN PGP SIGNATURE-----\nABC\n-----END PGP SIGNATURE-----\n","payload":"tree deadbeef\nauthor Jane Doe <jane@example.com> 1700000000 +0000\n"}}`))
_, _ = w.Write([]byte(`{"commit":{"sha":"abc123","parent_shas":["def456"],"message":"feat: add endpoint","author_name":"Jane Doe","author_email":"jane@example.com","committer_name":"Jane Doe","committer_email":"jane@example.com","date":"2024-01-15T14:32:18Z","signature":"-----BEGIN PGP SIGNATURE-----\nABC\n-----END PGP SIGNATURE-----\n","payload":"tree deadbeef\nauthor Jane Doe <jane@example.com> 1700000000 +0000\n"}}`))
}))
defer server.Close()

Expand All @@ -2071,6 +2074,9 @@ func TestGetCommit(t *testing.T) {
if result.Commit.SHA != "abc123" {
t.Fatalf("unexpected sha: %q", result.Commit.SHA)
}
if !reflect.DeepEqual([]string{"def456"}, result.Commit.ParentSHAs) {
t.Fatalf("unexpected parent SHAs: %#v", result.Commit.ParentSHAs)
}
if result.Commit.Message != "feat: add endpoint" {
t.Fatalf("unexpected message: %q", result.Commit.Message)
}
Expand All @@ -2091,7 +2097,7 @@ func TestGetCommit(t *testing.T) {
func TestGetCommitUnsigned(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"commit":{"sha":"abc123","message":"chore: noop","author_name":"Jane Doe","author_email":"jane@example.com","committer_name":"Jane Doe","committer_email":"jane@example.com","date":"2024-01-15T14:32:18Z"}}`))
_, _ = w.Write([]byte(`{"commit":{"sha":"abc123","parent_shas":[],"message":"chore: noop","author_name":"Jane Doe","author_email":"jane@example.com","committer_name":"Jane Doe","committer_email":"jane@example.com","date":"2024-01-15T14:32:18Z"}}`))
}))
defer server.Close()

Expand All @@ -2108,6 +2114,9 @@ func TestGetCommitUnsigned(t *testing.T) {
if result.Commit.Signature != "" || result.Commit.Payload != "" {
t.Fatalf("expected empty signature/payload for unsigned commit, got %+v", result.Commit)
}
if result.Commit.ParentSHAs == nil || len(result.Commit.ParentSHAs) != 0 {
t.Fatalf("expected non-nil empty parent SHAs for root commit, got %#v", result.Commit.ParentSHAs)
}
}

func TestGetCommitRequiresSHA(t *testing.T) {
Expand Down
15 changes: 8 additions & 7 deletions packages/code-storage-go/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,14 @@ type blameLineRaw struct {
}

type commitInfoRaw struct {
SHA string `json:"sha"`
Message string `json:"message"`
AuthorName string `json:"author_name"`
AuthorEmail string `json:"author_email"`
CommitterName string `json:"committer_name"`
CommitterEmail string `json:"committer_email"`
Date string `json:"date"`
SHA string `json:"sha"`
ParentSHAs []string `json:"parent_shas"`
Message string `json:"message"`
AuthorName string `json:"author_name"`
AuthorEmail string `json:"author_email"`
CommitterName string `json:"committer_name"`
CommitterEmail string `json:"committer_email"`
Date string `json:"date"`
}

// commitInfoWithSignatureRaw extends commitInfoRaw with the signature details
Expand Down
5 changes: 4 additions & 1 deletion packages/code-storage-go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,10 @@ type ListCommitsOptions struct {

// CommitInfo describes a commit entry.
type CommitInfo struct {
SHA string
SHA string
// ParentSHAs contains parent commit SHAs in Git parent order. It is empty
// for root commits.
ParentSHAs []string
Message string
AuthorName string
AuthorEmail string
Expand Down
2 changes: 1 addition & 1 deletion packages/code-storage-go/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package storage

const (
PackageName = "code-storage-go-sdk"
PackageVersion = "0.11.0"
PackageVersion = "0.12.0"
)

func userAgent() string {
Expand Down
8 changes: 7 additions & 1 deletion packages/code-storage-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,16 @@ commits = await repo.list_commits(
cursor=None, # for pagination
)
print(commits["commits"])
for commit in commits["commits"]:
print(commit["sha"], commit["parent_shas"]) # Git parent order; [] for a root commit

# Get a single commit's metadata (no diff)
result = await repo.get_commit(sha="abc123...")
print(result["commit"]["message"], result["commit"]["author_name"])
print(
result["commit"]["message"],
result["commit"]["author_name"],
result["commit"]["parent_shas"],
)
# Signed commits also include the armored signature plus the exact signed
# payload (raw commit object minus the gpgsig header) so you can verify it
# yourself. Both keys are absent for unsigned commits.
Expand Down
2 changes: 2 additions & 0 deletions packages/code-storage-python/pierre_storage/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,7 @@ async def list_commits(
commits.append(
{
"sha": c["sha"],
"parent_shas": c["parent_shas"],
"message": c["message"],
"author_name": c["author_name"],
"author_email": c["author_email"],
Expand Down Expand Up @@ -1460,6 +1461,7 @@ async def get_commit(
date = datetime.fromisoformat(commit_raw["date"].replace("Z", "+00:00"))
commit: CommitInfo = {
"sha": commit_raw["sha"],
"parent_shas": commit_raw["parent_shas"],
"message": commit_raw["message"],
"author_name": commit_raw["author_name"],
"author_email": commit_raw["author_email"],
Expand Down
1 change: 1 addition & 0 deletions packages/code-storage-python/pierre_storage/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ class CommitInfo(TypedDict):
"""

sha: str
parent_shas: List[str]
message: str
author_name: str
author_email: str
Expand Down
2 changes: 1 addition & 1 deletion packages/code-storage-python/pierre_storage/version.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Version information for Pierre Storage SDK."""

PACKAGE_NAME = "code-storage-py-sdk"
PACKAGE_VERSION = "1.11.0"
PACKAGE_VERSION = "1.13.0"


def get_user_agent() -> str:
Expand Down
2 changes: 1 addition & 1 deletion packages/code-storage-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pierre-storage"
version = "1.12.0"
version = "1.13.0"
description = "Pierre Git Storage SDK for Python"
readme = "README.md"
license = "MIT"
Expand Down
9 changes: 9 additions & 0 deletions packages/code-storage-python/tests/test_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,7 @@ async def test_list_commits(self, git_storage_options: dict) -> None:
"commits": [
{
"sha": "abc123",
"parent_shas": [],
"message": "Initial commit",
"author_name": "Test User",
"author_email": "test@example.com",
Expand All @@ -1776,6 +1777,7 @@ async def test_list_commits(self, git_storage_options: dict) -> None:
},
{
"sha": "def456",
"parent_shas": ["abc123"],
"message": "Second commit",
"author_name": "Test User",
"author_email": "test@example.com",
Expand Down Expand Up @@ -1803,7 +1805,9 @@ async def test_list_commits(self, git_storage_options: dict) -> None:
assert "commits" in result
assert len(result["commits"]) == 2
assert result["commits"][0]["sha"] == "abc123"
assert result["commits"][0]["parent_shas"] == []
assert result["commits"][0]["message"] == "Initial commit"
assert result["commits"][1]["parent_shas"] == ["abc123"]

@pytest.mark.asyncio
async def test_list_commits_ephemeral_query_param(self, git_storage_options: dict) -> None:
Expand Down Expand Up @@ -1889,6 +1893,7 @@ async def test_get_commit(self, git_storage_options: dict) -> None:
commit_response.json.return_value = {
"commit": {
"sha": "abc123",
"parent_shas": ["def456"],
"message": "feat: add endpoint",
"author_name": "Jane Doe",
"author_email": "jane@example.com",
Expand All @@ -1910,6 +1915,7 @@ async def test_get_commit(self, git_storage_options: dict) -> None:

commit = result["commit"]
assert commit["sha"] == "abc123"
assert commit["parent_shas"] == ["def456"]
assert commit["message"] == "feat: add endpoint"
assert commit["author_name"] == "Jane Doe"
assert commit["author_email"] == "jane@example.com"
Expand Down Expand Up @@ -1952,6 +1958,7 @@ async def test_get_commit_unsigned_omits_signature(self, git_storage_options: di
commit_response.json.return_value = {
"commit": {
"sha": "abc123",
"parent_shas": [],
"message": "chore: noop",
"author_name": "Jane Doe",
"author_email": "jane@example.com",
Expand All @@ -1970,6 +1977,7 @@ async def test_get_commit_unsigned_omits_signature(self, git_storage_options: di
result = await repo.get_commit(sha="abc123")

commit = result["commit"]
assert commit["parent_shas"] == []
assert "signature" not in commit
assert "payload" not in commit

Expand All @@ -1990,6 +1998,7 @@ async def test_get_commit_trims_sha_and_honors_ttl(self, git_storage_options: di
commit_response.json.return_value = {
"commit": {
"sha": "abc123",
"parent_shas": [],
"message": "msg",
"author_name": "A",
"author_email": "a@example.com",
Expand Down
2 changes: 1 addition & 1 deletion packages/code-storage-python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/code-storage-typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,11 @@ const commits = await repo.listCommits({
cursor: undefined, // for pagination
});
console.log(commits.commits);
console.log(commits.commits[0]?.parentShas); // Git parent order; [] for a root commit

// Get a single commit's metadata (no diff)
const { commit } = await repo.getCommit({ sha: 'abc123...' });
console.log(commit.message, commit.authorName);
console.log(commit.message, commit.authorName, commit.parentShas);
// Signed commits also expose the armored signature plus the exact signed
// payload (raw commit object minus the gpgsig header) so you can verify it
// yourself. Both are undefined for unsigned commits.
Expand Down Expand Up @@ -817,6 +818,8 @@ interface ListCommitsResult {

interface CommitInfo {
sha: string;
// Parent commit SHAs in Git parent order. Empty for root commits.
parentShas: string[];
message: string;
authorName: string;
authorEmail: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/code-storage-typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pierre/storage",
"version": "1.13.0",
"version": "1.14.0",
"description": "Pierre Git Storage SDK",
"repository": {
"type": "git",
Expand Down
1 change: 1 addition & 0 deletions packages/code-storage-typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ function transformCommitInfo(raw: RawCommitInfo): CommitInfo {
const parsedDate = new Date(raw.date);
return {
sha: raw.sha,
parentShas: raw.parent_shas,
message: raw.message,
authorName: raw.author_name,
authorEmail: raw.author_email,
Expand Down
1 change: 1 addition & 0 deletions packages/code-storage-typescript/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const listBranchesResponseSchema = z.object({

export const commitInfoRawSchema = z.object({
sha: z.string(),
parent_shas: z.array(z.string()),
message: z.string(),
author_name: z.string(),
author_email: z.string(),
Expand Down
2 changes: 2 additions & 0 deletions packages/code-storage-typescript/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,8 @@ export type RawCommitInfo = SchemaRawCommitInfo;

export interface CommitInfo {
sha: string;
/** Parent commit SHAs in Git parent order. Empty for root commits. */
parentShas: string[];
message: string;
authorName: string;
authorEmail: string;
Expand Down
8 changes: 8 additions & 0 deletions packages/code-storage-typescript/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ describe('GitStorage', () => {
json: async () => ({
commit: {
sha: 'abc123',
parent_shas: [],
message: 'msg',
author_name: 'A',
author_email: 'a@example.com',
Expand All @@ -217,6 +218,7 @@ describe('GitStorage', () => {
commits: [
{
sha: 'abc123',
parent_shas: ['def456', '789abc'],
message: 'feat: add endpoint',
author_name: 'Jane Doe',
author_email: 'jane@example.com',
Expand All @@ -238,6 +240,7 @@ describe('GitStorage', () => {
);

const commits = await repo.listCommits();
expect(commits.commits[0].parentShas).toEqual(['def456', '789abc']);
expect(commits.commits[0].rawDate).toBe('2024-01-15T14:32:18Z');
expect(commits.commits[0].date).toBeInstanceOf(Date);
expect(commits.commits[0].date.toISOString()).toBe(
Expand Down Expand Up @@ -333,6 +336,7 @@ describe('GitStorage', () => {
json: async () => ({
commit: {
sha: 'abc123',
parent_shas: ['def456'],
message: 'feat: add endpoint',
author_name: 'Jane Doe',
author_email: 'jane@example.com',
Expand All @@ -349,6 +353,7 @@ describe('GitStorage', () => {

const result = await repo.getCommit({ sha: 'abc123' });
expect(result.commit.sha).toBe('abc123');
expect(result.commit.parentShas).toEqual(['def456']);
expect(result.commit.message).toBe('feat: add endpoint');
expect(result.commit.authorName).toBe('Jane Doe');
expect(result.commit.authorEmail).toBe('jane@example.com');
Expand All @@ -375,6 +380,7 @@ describe('GitStorage', () => {
json: async () => ({
commit: {
sha: 'abc123',
parent_shas: [],
message: 'chore: noop',
author_name: 'Jane Doe',
author_email: 'jane@example.com',
Expand All @@ -387,6 +393,7 @@ describe('GitStorage', () => {
);

const result = await repo.getCommit({ sha: 'abc123' });
expect(result.commit.parentShas).toEqual([]);
expect(result.commit.signature).toBeUndefined();
expect(result.commit.payload).toBeUndefined();
});
Expand All @@ -412,6 +419,7 @@ describe('GitStorage', () => {
json: async () => ({
commit: {
sha: 'abc123',
parent_shas: [],
message: 'msg',
author_name: 'A',
author_email: 'a@example.com',
Expand Down
Loading
Loading