Skip to content
Open
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
355 changes: 332 additions & 23 deletions apps/server/src/git/Layers/CodexTextGeneration.ts

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions apps/server/src/git/Layers/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface FakeGhScenario {
interface FakeGitTextGeneration {
generateCommitMessage: (input: {
cwd: string;
provider?: "codex" | "copilot";
branch: string | null;
stagedSummary: string;
stagedPatch: string;
Expand All @@ -53,6 +54,7 @@ interface FakeGitTextGeneration {
>;
generatePrContent: (input: {
cwd: string;
provider?: "codex" | "copilot";
baseBranch: string;
headBranch: string;
commitSummary: string;
Expand All @@ -61,6 +63,7 @@ interface FakeGitTextGeneration {
}) => Effect.Effect<{ title: string; body: string }, TextGenerationError>;
generateBranchName: (input: {
cwd: string;
provider?: "codex" | "copilot";
message: string;
}) => Effect.Effect<{ branch: string }, TextGenerationError>;
}
Expand Down Expand Up @@ -449,6 +452,7 @@ function runStackedAction(
input: {
cwd: string;
action: "commit" | "commit_push" | "commit_push_pr";
provider?: "codex" | "copilot";
commitMessage?: string;
featureBranch?: boolean;
filePaths?: readonly string[];
Expand Down Expand Up @@ -843,6 +847,60 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
}),
);

it.effect("passes the selected provider to generated commit and PR content", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
yield* runGit(repoDir, ["checkout", "-b", "feature/copilot-pr"]);
const remoteDir = yield* createBareRemote();
yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
fs.writeFileSync(path.join(repoDir, "feature.txt"), "copilot flow\n");

const seenProviders: Array<"codex" | "copilot" | undefined> = [];
const { manager } = yield* makeManager({
ghScenario: {
prListSequence: [
"[]",
JSON.stringify([
{
number: 91,
title: "Add stacked git actions",
url: "https://github.com/pingdotgg/codething-mvp/pull/91",
baseRefName: "main",
headRefName: "feature/copilot-pr",
},
]),
],
},
textGeneration: {
generateCommitMessage: (input) =>
Effect.sync(() => {
seenProviders.push(input.provider);
return { subject: "Use GitHub Copilot for git copy", body: "" };
}),
generatePrContent: (input) =>
Effect.sync(() => {
seenProviders.push(input.provider);
return {
title: "Use GitHub Copilot for git copy",
body: "## Summary\n- Route git copy through Copilot\n\n## Testing\n- Not run",
};
}),
},
});

const result = yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit_push_pr",
provider: "copilot",
});

expect(result.commit.status).toBe("created");
expect(result.pr.status).toBe("created");
expect(seenProviders).toEqual(["copilot", "copilot"]);
}),
);

it.effect("featureBranch uses custom commit message and derives branch name", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
Expand Down
17 changes: 15 additions & 2 deletions apps/server/src/git/Layers/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ export const makeGitManager = Effect.gen(function* () {

const resolveCommitAndBranchSuggestion = (input: {
cwd: string;
provider?: "codex" | "copilot";
branch: string | null;
commitMessage?: string;
/** When true, also produce a semantic feature branch name. */
Expand Down Expand Up @@ -661,6 +662,7 @@ export const makeGitManager = Effect.gen(function* () {
const generated = yield* textGeneration
.generateCommitMessage({
cwd: input.cwd,
...(input.provider ? { provider: input.provider } : {}),
branch: input.branch,
stagedSummary: limitContext(context.stagedSummary, 8_000),
stagedPatch: limitContext(context.stagedPatch, 50_000),
Expand All @@ -678,6 +680,7 @@ export const makeGitManager = Effect.gen(function* () {

const runCommitStep = (
cwd: string,
provider: "codex" | "copilot" | undefined,
branch: string | null,
commitMessage?: string,
preResolvedSuggestion?: CommitAndBranchSuggestion,
Expand All @@ -688,6 +691,7 @@ export const makeGitManager = Effect.gen(function* () {
preResolvedSuggestion ??
(yield* resolveCommitAndBranchSuggestion({
cwd,
...(provider ? { provider } : {}),
branch,
...(commitMessage ? { commitMessage } : {}),
...(filePaths ? { filePaths } : {}),
Expand All @@ -704,7 +708,11 @@ export const makeGitManager = Effect.gen(function* () {
};
});

const runPrStep = (cwd: string, fallbackBranch: string | null) =>
const runPrStep = (
cwd: string,
provider: "codex" | "copilot" | undefined,
fallbackBranch: string | null,
) =>
Effect.gen(function* () {
const details = yield* gitCore.statusDetails(cwd);
const branch = details.branch ?? fallbackBranch;
Expand Down Expand Up @@ -743,6 +751,7 @@ export const makeGitManager = Effect.gen(function* () {

const generated = yield* textGeneration.generatePrContent({
cwd,
...(provider ? { provider } : {}),
baseBranch,
headBranch: headContext.headBranch,
commitSummary: limitContext(rangeContext.commitSummary, 20_000),
Expand Down Expand Up @@ -969,13 +978,15 @@ export const makeGitManager = Effect.gen(function* () {

const runFeatureBranchStep = (
cwd: string,
provider: "codex" | "copilot" | undefined,
branch: string | null,
commitMessage?: string,
filePaths?: readonly string[],
) =>
Effect.gen(function* () {
const suggestion = yield* resolveCommitAndBranchSuggestion({
cwd,
...(provider ? { provider } : {}),
branch,
...(commitMessage ? { commitMessage } : {}),
...(filePaths ? { filePaths } : {}),
Expand Down Expand Up @@ -1025,6 +1036,7 @@ export const makeGitManager = Effect.gen(function* () {
if (input.featureBranch) {
const result = yield* runFeatureBranchStep(
input.cwd,
input.provider,
initialStatus.branch,
input.commitMessage,
input.filePaths,
Expand All @@ -1040,6 +1052,7 @@ export const makeGitManager = Effect.gen(function* () {

const commit = yield* runCommitStep(
input.cwd,
input.provider,
currentBranch,
commitMessageForStep,
preResolvedCommitSuggestion,
Expand All @@ -1051,7 +1064,7 @@ export const makeGitManager = Effect.gen(function* () {
: { status: "skipped_not_requested" as const };

const pr = wantsPr
? yield* runPrStep(input.cwd, currentBranch)
? yield* runPrStep(input.cwd, input.provider, currentBranch)
: { status: "skipped_not_requested" as const };

return {
Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/git/Services/TextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
*/
import { ServiceMap } from "effect";
import type { Effect } from "effect";
import type { ChatAttachment } from "@t3tools/contracts";
import type { ChatAttachment, ProviderKind } from "@t3tools/contracts";

import type { TextGenerationError } from "../Errors.ts";

export interface CommitMessageGenerationInput {
cwd: string;
provider?: ProviderKind;
branch: string | null;
stagedSummary: string;
stagedPatch: string;
Expand All @@ -30,6 +31,7 @@ export interface CommitMessageGenerationResult {

export interface PrContentGenerationInput {
cwd: string;
provider?: ProviderKind;
baseBranch: string;
headBranch: string;
commitSummary: string;
Expand All @@ -44,6 +46,7 @@ export interface PrContentGenerationResult {

export interface BranchNameGenerationInput {
cwd: string;
provider?: ProviderKind;
message: string;
attachments?: ReadonlyArray<ChatAttachment> | undefined;
}
Expand Down
11 changes: 10 additions & 1 deletion apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4278,6 +4278,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
availableEditors={availableEditors}
diffToggleShortcutLabel={diffPanelShortcutLabel}
gitCwd={gitCwd}
activeProvider={activeProvider}
diffOpen={diffOpen}
onRunProjectScript={(script) => {
void runProjectScript(script);
Expand Down Expand Up @@ -5022,6 +5023,7 @@ interface ChatHeaderProps {
availableEditors: ReadonlyArray<EditorId>;
diffToggleShortcutLabel: string | null;
gitCwd: string | null;
activeProvider: ProviderKind;
diffOpen: boolean;
onRunProjectScript: (script: ProjectScript) => void;
onAddProjectScript: (input: NewProjectScriptInput) => Promise<void>;
Expand All @@ -5042,6 +5044,7 @@ const ChatHeader = memo(function ChatHeader({
availableEditors,
diffToggleShortcutLabel,
gitCwd,
activeProvider,
diffOpen,
onRunProjectScript,
onAddProjectScript,
Expand Down Expand Up @@ -5089,7 +5092,13 @@ const ChatHeader = memo(function ChatHeader({
openInCwd={openInCwd}
/>
)}
{activeProjectName && <GitActionsControl gitCwd={gitCwd} activeThreadId={activeThreadId} />}
{activeProjectName && (
<GitActionsControl
gitCwd={gitCwd}
activeThreadId={activeThreadId}
activeProvider={activeProvider}
/>
)}
<Tooltip>
<TooltipTrigger
render={
Expand Down
22 changes: 18 additions & 4 deletions apps/web/src/components/GitActionsControl.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GitStackedAction, GitStatusResult, ThreadId } from "@t3tools/contracts";
import type { GitStackedAction, GitStatusResult, ProviderKind, ThreadId } from "@t3tools/contracts";
import { useIsMutating, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ChevronDownIcon, CloudUploadIcon, GitCommitIcon, InfoIcon } from "lucide-react";
Expand Down Expand Up @@ -48,12 +48,14 @@ import { readNativeApi } from "~/nativeApi";
interface GitActionsControlProps {
gitCwd: string | null;
activeThreadId: ThreadId | null;
activeProvider: ProviderKind;
}

interface PendingDefaultBranchAction {
action: DefaultBranchConfirmableAction;
branchName: string;
includesCommit: boolean;
provider: ProviderKind;
commitMessage?: string;
forcePushOnlyProgress: boolean;
onConfirmed?: () => void;
Expand Down Expand Up @@ -153,7 +155,11 @@ function GitQuickActionIcon({ quickAction }: { quickAction: GitQuickAction }) {
return <InfoIcon className={iconClassName} />;
}

export default function GitActionsControl({ gitCwd, activeThreadId }: GitActionsControlProps) {
export default function GitActionsControl({
gitCwd,
activeThreadId,
activeProvider,
}: GitActionsControlProps) {
const threadToastData = useMemo(
() => (activeThreadId ? { threadId: activeThreadId } : undefined),
[activeThreadId],
Expand Down Expand Up @@ -258,6 +264,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
const runGitActionWithToast = useCallback(
async ({
action,
provider = activeProvider,
commitMessage,
forcePushOnlyProgress = false,
onConfirmed,
Expand All @@ -269,6 +276,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
filePaths,
}: {
action: GitStackedAction;
provider?: ProviderKind;
commitMessage?: string;
forcePushOnlyProgress?: boolean;
onConfirmed?: () => void;
Expand Down Expand Up @@ -297,6 +305,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
action,
branchName: actionBranch,
includesCommit,
provider,
...(commitMessage ? { commitMessage } : {}),
forcePushOnlyProgress,
...(onConfirmed ? { onConfirmed } : {}),
Expand Down Expand Up @@ -348,6 +357,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions

const promise = runImmediateGitActionMutation.mutateAsync({
action,
provider,
...(commitMessage ? { commitMessage } : {}),
...(featureBranch ? { featureBranch } : {}),
...(filePaths ? { filePaths } : {}),
Expand Down Expand Up @@ -442,6 +452,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
},

[
activeProvider,
isDefaultBranch,
runImmediateGitActionMutation,
setPendingDefaultBranchAction,
Expand All @@ -452,11 +463,12 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions

const continuePendingDefaultBranchAction = useCallback(() => {
if (!pendingDefaultBranchAction) return;
const { action, commitMessage, forcePushOnlyProgress, onConfirmed, filePaths } =
const { action, provider, commitMessage, forcePushOnlyProgress, onConfirmed, filePaths } =
pendingDefaultBranchAction;
setPendingDefaultBranchAction(null);
void runGitActionWithToast({
action,
provider,
...(commitMessage ? { commitMessage } : {}),
forcePushOnlyProgress,
...(onConfirmed ? { onConfirmed } : {}),
Expand All @@ -468,6 +480,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
const checkoutNewBranchAndRunAction = useCallback(
(actionParams: {
action: GitStackedAction;
provider?: ProviderKind;
commitMessage?: string;
forcePushOnlyProgress?: boolean;
onConfirmed?: () => void;
Expand All @@ -484,11 +497,12 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions

const checkoutFeatureBranchAndContinuePendingAction = useCallback(() => {
if (!pendingDefaultBranchAction) return;
const { action, commitMessage, forcePushOnlyProgress, onConfirmed, filePaths } =
const { action, provider, commitMessage, forcePushOnlyProgress, onConfirmed, filePaths } =
pendingDefaultBranchAction;
setPendingDefaultBranchAction(null);
checkoutNewBranchAndRunAction({
action,
provider,
...(commitMessage ? { commitMessage } : {}),
forcePushOnlyProgress,
...(onConfirmed ? { onConfirmed } : {}),
Expand Down
Loading
Loading