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
4 changes: 2 additions & 2 deletions packages/code-storage-go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,8 @@ fmt.Println(repo.ID)
Because this Go module lives in a monorepo, git tags must be prefixed with the module's subdirectory path:

```bash
git tag packages/code-storage-go/v0.7.0
git push origin packages/code-storage-go/v0.7.0
git tag packages/code-storage-go/v0.8.0
git push origin packages/code-storage-go/v0.8.0
```

Make sure the version in `version.go` (`PackageVersion`) matches the tag before tagging.
Expand Down
3 changes: 3 additions & 0 deletions packages/code-storage-go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ func (c *Client) ListRepos(ctx context.Context, options ListReposOptions) (ListR
if options.Limit > 0 {
params.Set("limit", itoa(options.Limit))
}
if q := strings.TrimSpace(options.Q); q != "" {
params.Set("q", q)
}
if len(params) == 0 {
params = nil
}
Expand Down
4 changes: 4 additions & 0 deletions packages/code-storage-go/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,9 @@ func (r *Repo) Merge(ctx context.Context, options MergeOptions) (MergeResult, er
if strategy != MergeStrategyMerge && strategy != MergeStrategyFFOnly && strategy != MergeStrategyFFPrefer {
return MergeResult{}, errors.New("merge strategy is invalid")
}
if options.Squash && strategy == MergeStrategyFFOnly {
return MergeResult{}, errors.New("merge squash is incompatible with the ff_only strategy")
}

body := &mergeRequest{
SourceBranch: sourceBranch,
Expand All @@ -993,6 +996,7 @@ func (r *Repo) Merge(ctx context.Context, options MergeOptions) (MergeResult, er
TargetIsEphemeral: options.TargetIsEphemeral,
Strategy: string(strategy),
AllowUnrelatedHistories: options.AllowUnrelatedHistories,
Squash: options.Squash,
}
if expectedTargetSHA := strings.TrimSpace(options.ExpectedTargetSHA); expectedTargetSHA != "" {
body.ExpectedTargetSHA = expectedTargetSHA
Expand Down
1 change: 1 addition & 0 deletions packages/code-storage-go/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type mergeRequest struct {
Committer *authorInfo `json:"committer,omitempty"`
Strategy string `json:"strategy"`
AllowUnrelatedHistories bool `json:"allow_unrelated_histories,omitempty"`
Squash bool `json:"squash,omitempty"`
}

type createTagRequest struct {
Expand Down
6 changes: 6 additions & 0 deletions packages/code-storage-go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ type ListReposOptions struct {
InvocationOptions
Cursor string
Limit int
// Q is a case-insensitive substring matched against the repository URL.
// Trimmed before matching; empty after trim is treated as omitted.
Q string
}

// ListReposResult returns paginated repos.
Expand Down Expand Up @@ -352,6 +355,8 @@ type MergeOptions struct {
Committer *CommitSignature
Strategy MergeStrategy
AllowUnrelatedHistories bool
// Squash is incompatible with MergeStrategyFFOnly.
Squash bool
}

// MergeResultStatus describes a merge operation outcome.
Expand All @@ -361,6 +366,7 @@ const (
MergeResultMergeCommit MergeResultStatus = "merge_commit"
MergeResultFastForward MergeResultStatus = "fast_forward"
MergeResultNoOp MergeResultStatus = "no_op"
MergeResultSquash MergeResultStatus = "squash"
MergeResultUnknown MergeResultStatus = "unknown"
)

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.7.0"
PackageVersion = "0.8.0"
)

func userAgent() string {
Expand Down
11 changes: 10 additions & 1 deletion packages/code-storage-python/pierre_storage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,14 @@ async def list_repos(
*,
cursor: Optional[str] = None,
limit: Optional[int] = None,
q: Optional[str] = None,
ttl: Optional[int] = None,
) -> ListReposResult:
"""List repositories for the organization."""
"""List repositories for the organization.

Pass ``q`` to filter by a case-insensitive substring match against the
repository ``url``. Empty/whitespace ``q`` is treated as omitted.
"""
ttl = ttl or DEFAULT_TOKEN_TTL_SECONDS
jwt = self._generate_jwt(
"org",
Expand All @@ -231,6 +236,10 @@ async def list_repos(
params["cursor"] = cursor
if limit is not None:
params["limit"] = str(limit)
if q is not None:
q_clean = q.strip()
if q_clean:
params["q"] = q_clean

url = f"{self.options['api_base_url']}/api/v{self.options['api_version']}/repos"
if params:
Expand Down
6 changes: 6 additions & 0 deletions packages/code-storage-python/pierre_storage/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ async def merge(
author: Optional[CommitSignature] = None,
committer: Optional[CommitSignature] = None,
allow_unrelated_histories: Optional[bool] = None,
squash: Optional[bool] = None,
ttl: Optional[int] = None,
) -> MergeBranchesResult:
"""Merge a source branch into a target branch."""
Expand All @@ -738,6 +739,8 @@ async def merge(
raise ValueError("merge strategy is required")
if strategy_clean not in {"merge", "ff_only", "ff_prefer"}:
raise ValueError("merge strategy must be one of merge, ff_only, ff_prefer")
if squash is True and strategy_clean == "ff_only":
raise ValueError("merge squash is incompatible with the ff_only strategy")

payload: Dict[str, Any] = {
"source_branch": source_branch_clean,
Expand Down Expand Up @@ -768,6 +771,9 @@ async def merge(
if allow_unrelated_histories is not None:
payload["allow_unrelated_histories"] = bool(allow_unrelated_histories)

if squash is not None:
payload["squash"] = bool(squash)

ttl_value = resolve_invocation_ttl_seconds({"ttl": ttl} if ttl is not None else None)
jwt = self.generate_jwt(
self._id,
Expand Down
4 changes: 3 additions & 1 deletion packages/code-storage-python/pierre_storage/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ class CreateCommitOptions(TypedDict, total=False):


MergeStrategy = Literal["merge", "ff_only", "ff_prefer"]
MergeResultLabel = Literal["merge_commit", "fast_forward", "no_op", "unknown"]
MergeResultLabel = Literal["merge_commit", "fast_forward", "no_op", "squash", "unknown"]


class MergeBranchesOptions(TypedDict, total=False):
Expand All @@ -469,6 +469,7 @@ class MergeBranchesOptions(TypedDict, total=False):
committer: CommitSignature
strategy: MergeStrategy # required
allow_unrelated_histories: bool
squash: bool


class MergeSourceResult(TypedDict):
Expand Down Expand Up @@ -726,6 +727,7 @@ async def merge(
author: Optional[CommitSignature] = None,
committer: Optional[CommitSignature] = None,
allow_unrelated_histories: Optional[bool] = None,
squash: Optional[bool] = None,
ttl: Optional[int] = None,
) -> MergeBranchesResult:
"""Merge a source branch into a target branch."""
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.8.0"
PACKAGE_VERSION = "1.9.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.8.0"
version = "1.9.0"
description = "Pierre Git Storage SDK for Python"
readme = "README.md"
license = "MIT"
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.

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.7.0",
"version": "1.8.0",
"description": "Pierre Git Storage SDK",
"repository": {
"type": "git",
Expand Down
35 changes: 28 additions & 7 deletions packages/code-storage-typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { FetchDiffCommitTransport, sendCommitFromDiff } from './diff-commit';
import { RefUpdateError } from './errors';
import { ApiError, ApiFetcher } from './fetch';
import type { RestoreCommitAckRaw } from './schemas';
import type { MergeResponseRaw, RestoreCommitAckRaw } from './schemas';
import {
branchDiffResponseSchema,
commitDiffResponseSchema,
Expand Down Expand Up @@ -111,7 +111,7 @@ import type {
ListReposResponse,
ListReposResult,
MergeOptions,
MergeResponse,
MergeResultLabel,
MergeResult,
ListTagsOptions,
ListTagsResponse,
Expand Down Expand Up @@ -504,9 +504,19 @@ function transformCreateBranchResult(
};
}

function transformMergeResult(raw: MergeResponse): MergeResult {
function normalizeMergeResultLabel(
result: MergeResponseRaw['result']
): MergeResultLabel {
if (result === 'squash') {
return 'merge_commit';
}

return result;
}

function transformMergeResult(raw: MergeResponseRaw): MergeResult {
return {
result: raw.result,
result: normalizeMergeResultLabel(raw.result),
commitSha: raw.commit_sha,
treeSha: raw.tree_sha,
source: {
Expand Down Expand Up @@ -1495,6 +1505,9 @@ class RepoImpl implements Repo {
if (strategy !== 'merge' && strategy !== 'ff_only' && strategy !== 'ff_prefer') {
throw new Error('merge strategy must be merge, ff_only, or ff_prefer');
}
if (options.squash === true && strategy === 'ff_only') {
throw new Error('merge squash is incompatible with the ff_only strategy');
}

const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
const jwt = await this.generateJWT(this.id, {
Expand Down Expand Up @@ -1549,6 +1562,10 @@ class RepoImpl implements Repo {
body.allow_unrelated_histories = options.allowUnrelatedHistories;
}

if (typeof options.squash === 'boolean') {
body.squash = options.squash;
}

const response = await this.api.post({ path: 'repos/merge', body }, jwt);
const raw = mergeResponseSchema.parse(await response.json());
return transformMergeResult(raw);
Expand Down Expand Up @@ -1894,15 +1911,19 @@ export class GitStorage {
ttl,
});

const trimmedQ = options?.q?.trim();
let params: Record<string, string> | undefined;
if (options?.cursor || typeof options?.limit === 'number') {
if (options?.cursor || typeof options?.limit === 'number' || trimmedQ) {
params = {};
if (options.cursor) {
if (options?.cursor) {
params.cursor = options.cursor;
}
if (typeof options.limit === 'number') {
if (typeof options?.limit === 'number') {
params.limit = options.limit.toString();
}
if (trimmedQ) {
params.q = trimmedQ;
}
}

const response = await this.api.get({ path: 'repos', params }, jwt);
Expand Down
2 changes: 1 addition & 1 deletion packages/code-storage-typescript/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export const mergeTargetSchema = z.object({
});

export const mergeResponseSchema = z.object({
result: z.enum(['merge_commit', 'fast_forward', 'no_op', 'unknown']),
result: z.enum(['merge_commit', 'fast_forward', 'no_op', 'squash', 'unknown']),
commit_sha: z.string(),
tree_sha: z.string(),
source: mergeRefSchema,
Expand Down
11 changes: 10 additions & 1 deletion packages/code-storage-typescript/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ export interface GitCredential {
export interface ListReposOptions extends GitStorageInvocationOptions {
cursor?: string;
limit?: number;
/**
* Case-insensitive substring matched against repository `url`. Trimmed before
* matching; empty after trim is treated as omitted.
*/
q?: string;
}

export type RawRepoBaseInfo = SchemaRawRepoBaseInfo;
Expand Down Expand Up @@ -827,9 +832,13 @@ export interface MergeOptions extends GitStorageInvocationOptions {
committer?: CommitSignature;
strategy: MergeStrategy;
allowUnrelatedHistories?: boolean;
/** Incompatible with the `ff_only` strategy. */
squash?: boolean;
}

export type MergeResponse = MergeResponseRaw;
export type MergeResponse = Omit<MergeResponseRaw, 'result'> & {
result: MergeResultLabel;
};

export interface MergeSourceResult {
branch: string;
Expand Down
56 changes: 56 additions & 0 deletions packages/code-storage-typescript/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,62 @@ describe('GitStorage', () => {
});
});

it('normalizes squash merge results to merge_commit for 1.x compatibility', async () => {
const store = new GitStorage({ name: 'v0', key });
const repo = store.repo({ id: 'repo-merge-squash' });

mockFetch.mockImplementationOnce((_url, init) => {
const body = JSON.parse((init as RequestInit).body as string);
expect(body).toEqual({
source_branch: 'feature',
target_branch: 'main',
strategy: 'merge',
squash: true,
});

return Promise.resolve({
ok: true,
status: 200,
statusText: 'OK',
json: async () => ({
result: 'squash',
commit_sha: 'squash-sha',
tree_sha: 'tree-sha',
source: { branch: 'feature', ephemeral: false, sha: 'source-sha' },
target: {
branch: 'main',
ephemeral: false,
old_sha: 'old-sha',
new_sha: 'squash-sha',
},
promoted_commits: 3,
}),
} as any);
});

const result = await repo.merge({
sourceBranch: 'feature',
targetBranch: 'main',
strategy: 'merge',
squash: true,
});

expect(result).toEqual({
result: 'merge_commit',
commitSha: 'squash-sha',
treeSha: 'tree-sha',
source: { branch: 'feature', ephemeral: false, sha: 'source-sha' },
target: {
branch: 'main',
ephemeral: false,
oldSha: 'old-sha',
newSha: 'squash-sha',
},
mergeBaseSha: undefined,
promotedCommits: 3,
});
});

it('validates merge inputs locally', async () => {
const store = new GitStorage({ name: 'v0', key });
const repo = store.repo({ id: 'repo-merge-validation' });
Expand Down
Loading
Loading