feat: draft pr support (#165)
This commit is contained in:
@@ -104,6 +104,7 @@ type GitCreatePRRequest struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
BaseBranch string `json:"base_branch"`
|
||||
Draft bool `json:"draft"`
|
||||
}
|
||||
|
||||
// GitShowCommitRequest for session.commit_diff action
|
||||
@@ -363,7 +364,7 @@ func (h *GitHandlers) wsCreatePR(ctx context.Context, msg *ws.Message) (*ws.Mess
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := client.GitCreatePR(ctx, req.Title, req.Body, req.BaseBranch)
|
||||
result, err := client.GitCreatePR(ctx, req.Title, req.Body, req.BaseBranch, req.Draft)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create PR failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -132,15 +132,17 @@ func (c *Client) GitDiscard(ctx context.Context, paths []string) (*GitOperationR
|
||||
}
|
||||
|
||||
// GitCreatePR creates a pull request using the gh CLI.
|
||||
func (c *Client) GitCreatePR(ctx context.Context, title, body, baseBranch string) (*PRCreateResult, error) {
|
||||
func (c *Client) GitCreatePR(ctx context.Context, title, body, baseBranch string, draft bool) (*PRCreateResult, error) {
|
||||
payload := struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
BaseBranch string `json:"base_branch"`
|
||||
Draft bool `json:"draft"`
|
||||
}{
|
||||
Title: title,
|
||||
Body: body,
|
||||
BaseBranch: baseBranch,
|
||||
Draft: draft,
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(payload)
|
||||
|
||||
@@ -66,6 +66,7 @@ type GitCreatePRRequest struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
BaseBranch string `json:"base_branch"`
|
||||
Draft bool `json:"draft"`
|
||||
}
|
||||
|
||||
// handleGitPull handles POST /api/v1/git/pull
|
||||
@@ -331,7 +332,7 @@ func (s *Server) handleGitCreatePR(c *gin.Context) {
|
||||
}
|
||||
|
||||
gitOp := s.procMgr.GitOperator()
|
||||
result, err := gitOp.CreatePR(c.Request.Context(), req.Title, req.Body, req.BaseBranch)
|
||||
result, err := gitOp.CreatePR(c.Request.Context(), req.Title, req.Body, req.BaseBranch, req.Draft)
|
||||
if err != nil {
|
||||
if errors.Is(err, process.ErrOperationInProgress) {
|
||||
c.JSON(http.StatusConflict, process.PRCreateResult{
|
||||
|
||||
@@ -760,7 +760,7 @@ type PRCreateResult struct {
|
||||
|
||||
// CreatePR creates a pull request using the gh CLI.
|
||||
// It first pushes the current branch to the remote, then creates the PR.
|
||||
func (g *GitOperator) CreatePR(ctx context.Context, title, body, baseBranch string) (*PRCreateResult, error) {
|
||||
func (g *GitOperator) CreatePR(ctx context.Context, title, body, baseBranch string, draft bool) (*PRCreateResult, error) {
|
||||
if !g.tryLock("create-pr") {
|
||||
return nil, ErrOperationInProgress
|
||||
}
|
||||
@@ -795,6 +795,10 @@ func (g *GitOperator) CreatePR(ctx context.Context, title, body, baseBranch stri
|
||||
args = append(args, "--base", cleanBaseBranch)
|
||||
}
|
||||
|
||||
if draft {
|
||||
args = append(args, "--draft")
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "gh", args...)
|
||||
cmd.Dir = g.workDir
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ export const SessionMobileTopBar = memo(function SessionMobileTopBar({
|
||||
const [prDialogOpen, setPrDialogOpen] = useState(false);
|
||||
const [prTitle, setPrTitle] = useState('');
|
||||
const [prBody, setPrBody] = useState('');
|
||||
const [prDraft, setPrDraft] = useState(true);
|
||||
|
||||
const { toast } = useToast();
|
||||
const gitStatus = useSessionGitStatus(sessionId ?? null);
|
||||
@@ -160,10 +161,10 @@ export const SessionMobileTopBar = memo(function SessionMobileTopBar({
|
||||
if (!prTitle.trim()) return;
|
||||
setPrDialogOpen(false);
|
||||
try {
|
||||
const result = await createPR(prTitle.trim(), prBody.trim(), baseBranch);
|
||||
const result = await createPR(prTitle.trim(), prBody.trim(), baseBranch, prDraft);
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Pull Request created',
|
||||
title: prDraft ? 'Draft Pull Request created' : 'Pull Request created',
|
||||
description: result.pr_url || 'PR created successfully',
|
||||
variant: 'success',
|
||||
});
|
||||
@@ -186,7 +187,7 @@ export const SessionMobileTopBar = memo(function SessionMobileTopBar({
|
||||
}
|
||||
setPrTitle('');
|
||||
setPrBody('');
|
||||
}, [prTitle, prBody, baseBranch, createPR, toast]);
|
||||
}, [prTitle, prBody, baseBranch, prDraft, createPR, toast]);
|
||||
|
||||
return (
|
||||
<header className="flex items-center justify-between px-2 py-2 bg-background">
|
||||
@@ -415,6 +416,16 @@ export const SessionMobileTopBar = memo(function SessionMobileTopBar({
|
||||
className="resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="pr-draft-mobile"
|
||||
checked={prDraft}
|
||||
onCheckedChange={(checked) => setPrDraft(checked === true)}
|
||||
/>
|
||||
<Label htmlFor="pr-draft-mobile" className="text-sm cursor-pointer">
|
||||
Create as draft
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
|
||||
@@ -91,6 +91,7 @@ const TaskTopBar = memo(function TaskTopBar({
|
||||
const [prDialogOpen, setPrDialogOpen] = useState(false);
|
||||
const [prTitle, setPrTitle] = useState('');
|
||||
const [prBody, setPrBody] = useState('');
|
||||
const [prDraft, setPrDraft] = useState(true);
|
||||
|
||||
const { toast } = useToast();
|
||||
const gitStatus = useSessionGitStatus(activeSessionId ?? null);
|
||||
@@ -198,10 +199,10 @@ const TaskTopBar = memo(function TaskTopBar({
|
||||
if (!prTitle.trim()) return;
|
||||
setPrDialogOpen(false);
|
||||
try {
|
||||
const result = await createPR(prTitle.trim(), prBody.trim(), baseBranch);
|
||||
const result = await createPR(prTitle.trim(), prBody.trim(), baseBranch, prDraft);
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Pull Request created',
|
||||
title: prDraft ? 'Draft Pull Request created' : 'Pull Request created',
|
||||
description: result.pr_url || 'PR created successfully',
|
||||
variant: 'success',
|
||||
});
|
||||
@@ -225,7 +226,7 @@ const TaskTopBar = memo(function TaskTopBar({
|
||||
}
|
||||
setPrTitle('');
|
||||
setPrBody('');
|
||||
}, [prTitle, prBody, baseBranch, createPR, toast]);
|
||||
}, [prTitle, prBody, baseBranch, prDraft, createPR, toast]);
|
||||
|
||||
const handleBranchClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
@@ -667,6 +668,16 @@ const TaskTopBar = memo(function TaskTopBar({
|
||||
className="resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="pr-draft"
|
||||
checked={prDraft}
|
||||
onCheckedChange={(checked) => setPrDraft(checked === true)}
|
||||
/>
|
||||
<Label htmlFor="pr-draft" className="text-sm cursor-pointer">
|
||||
Create as draft
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
|
||||
@@ -31,7 +31,7 @@ interface UseGitOperationsReturn {
|
||||
stage: (paths?: string[]) => Promise<GitOperationResult>;
|
||||
unstage: (paths?: string[]) => Promise<GitOperationResult>;
|
||||
discard: (paths?: string[]) => Promise<GitOperationResult>;
|
||||
createPR: (title: string, body: string, baseBranch?: string) => Promise<PRCreateResult>;
|
||||
createPR: (title: string, body: string, baseBranch?: string, draft?: boolean) => Promise<PRCreateResult>;
|
||||
|
||||
// State
|
||||
isLoading: boolean;
|
||||
@@ -119,7 +119,7 @@ export function useGitOperations(sessionId: string | null): UseGitOperationsRetu
|
||||
return executeOperation<GitOperationResult>('worktree.discard', { paths: paths ?? [] });
|
||||
}, [executeOperation]);
|
||||
|
||||
const createPR = useCallback(async (title: string, body: string, baseBranch?: string): Promise<PRCreateResult> => {
|
||||
const createPR = useCallback(async (title: string, body: string, baseBranch?: string, draft?: boolean): Promise<PRCreateResult> => {
|
||||
if (!sessionId) {
|
||||
throw new Error('No session ID provided');
|
||||
}
|
||||
@@ -138,6 +138,7 @@ export function useGitOperations(sessionId: string | null): UseGitOperationsRetu
|
||||
title,
|
||||
body,
|
||||
base_branch: baseBranch ?? '',
|
||||
draft: draft ?? true,
|
||||
}, 120000); // 2 min timeout for PR creation (gh cli can be slow)
|
||||
|
||||
if (!result.success && result.error) {
|
||||
|
||||
Reference in New Issue
Block a user