feat(git): add multi-remote push with remote selection and fork-aware PR creation (#365)

* fix: Usage drop down should be scrollable

* fix: When there are multiple remotes, provide the user with an option of which branch to push to

* feat(gitview): add remote-push selection and auto checkout on create

* feat: enable selecting remote when creating PR

* fix: show PR icon in create button when not creating

* fix(pullrequest): drop remote picker and use explicit remote

* feat: add remote selection for PR status and creation

* fix: improve PR creation with selected remote and fork head

* chore(ui): simplify PR remote link UI

* fix(web): handle cross-repo PRs and branch validation

* feat: add remote selection for commit and push

* feat(git): delegate PR head source resolution to server

* chore(web): remove noisy PR creation logs
This commit is contained in:
gsxdsm
2026-02-09 19:28:41 +02:00
committed by GitHub
parent 1efe8a1cba
commit 6776ac31c2
9 changed files with 468 additions and 54 deletions
@@ -4,6 +4,7 @@ import {
RiAiGenerate2,
RiLoader4Line,
RiEmotionHappyLine,
RiArrowDownSLine,
} from '@remixicon/react';
import {
Collapsible,
@@ -15,6 +16,13 @@ import { CommitInput } from './CommitInput';
import { AIHighlightsBox } from './AIHighlightsBox';
import { useDeviceInfo } from '@/lib/device';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import type { GitRemote } from '@/lib/api/types';
type CommitAction = 'commit' | 'commitAndPush' | null;
@@ -28,11 +36,12 @@ interface CommitSectionProps {
onGenerateMessage: () => void;
isGeneratingMessage: boolean;
onCommit: () => void;
onCommitAndPush: () => void;
onCommitAndPush: (remote?: GitRemote) => void;
commitAction: CommitAction;
isBusy: boolean;
gitmojiEnabled: boolean;
onOpenGitmojiPicker: () => void;
remotes?: GitRemote[];
variant?: 'framed' | 'plain';
}
@@ -51,11 +60,13 @@ export const CommitSection: React.FC<CommitSectionProps> = ({
isBusy,
gitmojiEnabled,
onOpenGitmojiPicker,
remotes = [],
variant = 'framed',
}) => {
const hasSelectedFiles = selectedCount > 0;
const canCommit = commitMessage.trim() && hasSelectedFiles && commitAction === null;
const { isMobile } = useDeviceInfo();
const hasMultipleRemotes = remotes.length > 1;
const containerClassName =
variant === 'framed'
@@ -165,31 +176,113 @@ export const CommitSection: React.FC<CommitSectionProps> = ({
</ButtonLarge>
{isMobile ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
hasMultipleRemotes ? (
<DropdownMenu>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="default"
size="sm"
disabled={!canCommit || isGeneratingMessage}
className="h-7 gap-0.5 px-1.5"
aria-label="Commit & Push"
>
{commitAction === 'commitAndPush' ? (
<RiLoader4Line className="size-4 animate-spin" />
) : (
<>
<RiArrowUpLine className="size-4" />
<RiArrowDownSLine className="size-3 opacity-60" />
</>
)}
</Button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="top">
<p>Commit & Push</p>
</TooltipContent>
</Tooltip>
<DropdownMenuContent align="end" className="min-w-[200px]">
{remotes.map((remote) => (
<DropdownMenuItem key={remote.name} onSelect={() => onCommitAndPush(remote)}>
<div className="flex flex-col">
<span className="typography-ui-label text-foreground">
{remote.name}
</span>
<span className="typography-meta text-muted-foreground truncate">
{remote.pushUrl}
</span>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
) : (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
size="sm"
onClick={() => onCommitAndPush()}
disabled={!canCommit || isGeneratingMessage}
className="h-7 w-7 p-0"
aria-label="Commit & Push"
>
{commitAction === 'commitAndPush' ? (
<RiLoader4Line className="size-4 animate-spin" />
) : (
<RiArrowUpLine className="size-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent side="top">
<p>Commit & Push</p>
</TooltipContent>
</Tooltip>
)
) : hasMultipleRemotes ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ButtonLarge
variant="default"
size="sm"
onClick={onCommitAndPush}
disabled={!canCommit || isGeneratingMessage}
className="h-7 w-7 p-0"
className="commit-actions__btn"
aria-label="Commit & Push"
>
{commitAction === 'commitAndPush' ? (
<RiLoader4Line className="size-4 animate-spin" />
<>
<RiLoader4Line className="size-4 animate-spin" />
<span className="commit-actions__label commit-actions__label--long">Pushing...</span>
</>
) : (
<RiArrowUpLine className="size-4" />
<>
<RiArrowUpLine className="size-4" />
<span className="commit-actions__label commit-actions__label--long">Commit &amp; Push</span>
<RiArrowDownSLine className="size-3.5 opacity-60 -mr-0.5" />
</>
)}
</Button>
</TooltipTrigger>
<TooltipContent side="top">
<p>Commit & Push</p>
</TooltipContent>
</Tooltip>
</ButtonLarge>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[200px]">
{remotes.map((remote) => (
<DropdownMenuItem key={remote.name} onSelect={() => onCommitAndPush(remote)}>
<div className="flex flex-col">
<span className="typography-ui-label text-foreground">
{remote.name}
</span>
<span className="typography-meta text-muted-foreground truncate">
{remote.pushUrl}
</span>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
) : (
<ButtonLarge
variant="default"
onClick={onCommitAndPush}
onClick={() => onCommitAndPush()}
disabled={!canCommit || isGeneratingMessage}
className="commit-actions__btn"
aria-label="Commit & Push"