Handle remote branches

This commit is contained in:
Gregory Schier
2025-02-07 13:21:30 -08:00
parent 2da898d2d4
commit a42bee098b
20 changed files with 272 additions and 32 deletions

View File

@@ -12,6 +12,7 @@ import classNames from 'classnames';
import { useMemo, useState } from 'react';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { showToast } from '../lib/toast';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import type { CheckboxProps } from './core/Checkbox';
@@ -46,8 +47,9 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
};
const handleCreateCommitAndPush = async () => {
await handleCreateCommit();
await commit.mutateAsync({ message });
await push.mutateAsync();
showToast({ id: 'git-push-success', message: 'Pushed changes', color: 'success' });
onDone();
};

View File

@@ -34,9 +34,16 @@ export function GitDropdown() {
function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
const workspace = useActiveWorkspace();
const [{ status, log }, { branch, deleteBranch, mergeBranch, push, pull, checkout }] =
const [{ status, log }, { branch, deleteBranch, fetchAll, mergeBranch, push, pull, checkout }] =
useGit(syncDir);
const localBranches = status.data?.localBranches ?? [];
const remoteBranches = status.data?.remoteBranches ?? [];
const remoteOnlyBranches = remoteBranches.filter(
(b) => !localBranches.includes(b.replace(/^origin\//, '')),
);
const currentBranch = status.data?.headRefShorthand ?? 'UNKNOWN';
if (workspace == null) {
return null;
}
@@ -69,12 +76,12 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
showErrorToast('git-checkout-error', String(err));
}
},
async onSuccess() {
async onSuccess(branchName) {
showToast({
id: 'git-checkout-success',
message: (
<>
Switched branch <InlineCode>{branch}</InlineCode>
Switched branch <InlineCode>{branchName}</InlineCode>
</>
),
color: 'success',
@@ -124,7 +131,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
{
label: 'Merge Branch',
leftSlot: <Icon icon="merge" />,
hidden: (status.data?.branches ?? []).length <= 1,
hidden: localBranches.length <= 1,
async onSelect() {
showDialog({
id: 'git-merge',
@@ -132,15 +139,13 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
size: 'sm',
description: (
<>
Select a branch to merge into <InlineCode>{status.data?.headRefShorthand}</InlineCode>
Select a branch to merge into <InlineCode>{currentBranch}</InlineCode>
</>
),
render: ({ hide }) => (
<BranchSelectionDialog
selectText="Merge"
branches={(status.data?.branches ?? []).filter(
(b) => b !== status.data?.headRefShorthand,
)}
branches={localBranches.filter((b) => b !== currentBranch)}
onCancel={hide}
onSelect={async (branch) => {
await mergeBranch.mutateAsync(
@@ -153,7 +158,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
message: (
<>
Merged <InlineCode>{branch}</InlineCode> into{' '}
<InlineCode>{status.data?.headRefShorthand}</InlineCode>
<InlineCode>{currentBranch}</InlineCode>
</>
),
});
@@ -173,10 +178,9 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
{
label: 'Delete Branch',
leftSlot: <Icon icon="trash" />,
hidden: (status.data?.branches ?? []).length <= 1,
hidden: localBranches.length <= 1,
color: 'danger',
async onSelect() {
const currentBranch = status.data?.headRefShorthand;
if (currentBranch == null) return;
const confirmed = await showConfirmDelete({
@@ -256,9 +260,17 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
});
},
},
{ type: 'separator', label: 'Branches', hidden: (status.data?.branches ?? []).length < 1 },
...(status.data?.branches ?? []).map((branch) => {
const isCurrent = status.data?.headRefShorthand === branch;
{ type: 'separator', label: 'Branches', hidden: localBranches.length < 1 },
...localBranches.map((branch) => {
const isCurrent = currentBranch === branch;
return {
label: branch,
leftSlot: <Icon icon={isCurrent ? 'check' : 'empty'} />,
onSelect: isCurrent ? undefined : () => tryCheckout(branch, false),
};
}),
...remoteOnlyBranches.map((branch) => {
const isCurrent = currentBranch === branch;
return {
label: branch,
leftSlot: <Icon icon={isCurrent ? 'check' : 'empty'} />,
@@ -268,9 +280,9 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
];
return (
<Dropdown fullWidth items={items}>
<Dropdown fullWidth items={items} onOpen={fetchAll.mutate}>
<GitMenuButton>
{noRepo ? 'Configure Git' : <InlineCode>{status.data?.headRefShorthand}</InlineCode>}
<InlineCode>{currentBranch}</InlineCode>
<Icon icon="git_branch" size="sm" />
</GitMenuButton>
</Dropdown>

View File

@@ -71,6 +71,7 @@ export interface DropdownProps {
items: DropdownItem[];
fullWidth?: boolean;
hotKeyAction?: HotkeyAction;
onOpen?: () => void;
}
export interface DropdownRef {
@@ -89,7 +90,7 @@ export interface DropdownRef {
const openAtom = atom<string | null>(null);
export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown(
{ children, items, hotKeyAction, fullWidth }: DropdownProps,
{ children, items, hotKeyAction, fullWidth, onOpen }: DropdownProps,
ref,
) {
const id = useRef(generateId());
@@ -116,13 +117,14 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
const newIsOpen = typeof o === 'function' ? o(prevIsOpen) : o;
// Persist background color of button until we close the dropdown
if (newIsOpen) {
onOpen?.();
buttonRef.current!.style.backgroundColor = window
.getComputedStyle(buttonRef.current!)
.getPropertyValue('background-color');
}
return newIsOpen ? id.current : null; // Set global atom to current ID to signify open state
});
}, []);
}, [onOpen]);
// Because a different dropdown can cause ours to close, a useEffect([isOpen]) is the only method
// we have of detecting the dropdown closed, to do cleanup.