feat: unified git repo hook + fix branch dropdown in workspace creation
- New useGitRepo hook: centralizes all git operations (branches, status, history, commit, push, pull, fetch, checkout, create/delete branch, merge) for a given project+repo. Auto-refreshes after mutating ops. - Fix WorkspaceCreateForm branch dropdown: - Always renders <select> (never input fallback) - Uses useGitRepo for branch fetching with loading/error states - Shows 'Loading branches...' while fetching - Shows 'Enter branch name manually...' if API fails - '+ Create new branch...' option with text input reveal - Auto-selects default branch on load - ProjectsPage uses updated form props (defaultProjectId/defaultRepoId) Quality gates: tsc --noEmit clean, eslint clean
This commit is contained in:
@@ -3,8 +3,9 @@
|
|||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import { listProjects } from "../api/projects";
|
import { listProjects } from "../api/projects";
|
||||||
import { listRepositories, listRepositoryBranches } from "../api/git_repositories";
|
import { listRepositories } from "../api/git_repositories";
|
||||||
import { createWorkspaceTopLevel } from "../api/workspaces";
|
import { createWorkspaceTopLevel } from "../api/workspaces";
|
||||||
|
import { useGitRepo } from "../hooks/use-git-repo";
|
||||||
import type { ProjectWithRepos } from "../types";
|
import type { ProjectWithRepos } from "../types";
|
||||||
import type { GitRepository } from "../api/git_repositories";
|
import type { GitRepository } from "../api/git_repositories";
|
||||||
|
|
||||||
@@ -29,17 +30,36 @@ export function WorkspaceCreateForm({
|
|||||||
|
|
||||||
const [projects, setProjects] = useState<ProjectWithRepos[]>([]);
|
const [projects, setProjects] = useState<ProjectWithRepos[]>([]);
|
||||||
const [repos, setRepos] = useState<GitRepository[]>([]);
|
const [repos, setRepos] = useState<GitRepository[]>([]);
|
||||||
const [branches, setBranches] = useState<string[]>([]);
|
const [selectedProject, setSelectedProject] = useState(
|
||||||
const [selectedProject, setSelectedProject] = useState(defaultProjectId ?? "");
|
defaultProjectId ?? "",
|
||||||
|
);
|
||||||
const [selectedRepo, setSelectedRepo] = useState(defaultRepoId ?? "");
|
const [selectedRepo, setSelectedRepo] = useState(defaultRepoId ?? "");
|
||||||
const [selectedBranch, setSelectedBranch] = useState("");
|
const [selectedBranch, setSelectedBranch] = useState("");
|
||||||
const [newBranchName, setNewBranchName] = useState("");
|
const [newBranchName, setNewBranchName] = useState("");
|
||||||
const [isNewBranch, setIsNewBranch] = useState(false);
|
const [isNewBranch, setIsNewBranch] = useState(false);
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [fetching, setFetching] = useState(!isContextual);
|
const [fetchingProjects, setFetchingProjects] = useState(!isContextual);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
/* Git repo hook handles branch fetching, loading, errors */
|
||||||
|
const git = useGitRepo(
|
||||||
|
selectedProject || undefined,
|
||||||
|
selectedRepo || undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Sync local branch state with hook data */
|
||||||
|
useEffect(() => {
|
||||||
|
if (git.branches.length > 0 && !selectedBranch) {
|
||||||
|
const preferred =
|
||||||
|
git.defaultBranch && git.branches.includes(git.defaultBranch)
|
||||||
|
? git.defaultBranch
|
||||||
|
: git.branches[0];
|
||||||
|
setSelectedBranch(preferred);
|
||||||
|
setIsNewBranch(false);
|
||||||
|
}
|
||||||
|
}, [git.branches, git.defaultBranch, selectedBranch]);
|
||||||
|
|
||||||
/* ── Load projects (standalone mode only) ── */
|
/* ── Load projects (standalone mode only) ── */
|
||||||
const loadProjects = useCallback(async () => {
|
const loadProjects = useCallback(async () => {
|
||||||
if (isContextual) return;
|
if (isContextual) return;
|
||||||
@@ -52,7 +72,7 @@ export function WorkspaceCreateForm({
|
|||||||
} catch {
|
} catch {
|
||||||
setError("Failed to load projects");
|
setError("Failed to load projects");
|
||||||
} finally {
|
} finally {
|
||||||
setFetching(false);
|
setFetchingProjects(false);
|
||||||
}
|
}
|
||||||
}, [isContextual, defaultProjectId]);
|
}, [isContextual, defaultProjectId]);
|
||||||
|
|
||||||
@@ -81,42 +101,15 @@ export function WorkspaceCreateForm({
|
|||||||
void loadRepos();
|
void loadRepos();
|
||||||
}, [selectedProject, defaultRepoId]);
|
}, [selectedProject, defaultRepoId]);
|
||||||
|
|
||||||
/* ── Load branches when repo changes ── */
|
|
||||||
useEffect(() => {
|
|
||||||
if (!selectedProject || !selectedRepo) {
|
|
||||||
setBranches([]);
|
|
||||||
setSelectedBranch("");
|
|
||||||
setIsNewBranch(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const loadBranches = async () => {
|
|
||||||
try {
|
|
||||||
const data = await listRepositoryBranches(selectedProject, selectedRepo);
|
|
||||||
const branchNames = data.branches.map((b) => b.name);
|
|
||||||
setBranches(branchNames);
|
|
||||||
if (branchNames.length >= 1) {
|
|
||||||
// Prefer default branch, else first branch
|
|
||||||
const preferred = data.default_branch && branchNames.includes(data.default_branch)
|
|
||||||
? data.default_branch
|
|
||||||
: branchNames[0];
|
|
||||||
setSelectedBranch(preferred);
|
|
||||||
setIsNewBranch(false);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Fallback to free-text branch input
|
|
||||||
setBranches([]);
|
|
||||||
setIsNewBranch(true);
|
|
||||||
setSelectedBranch("__new__");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
void loadBranches();
|
|
||||||
}, [selectedProject, selectedRepo]);
|
|
||||||
|
|
||||||
const handleBranchChange = (value: string) => {
|
const handleBranchChange = (value: string) => {
|
||||||
if (value === "__new__") {
|
if (value === "__new__") {
|
||||||
setIsNewBranch(true);
|
setIsNewBranch(true);
|
||||||
setSelectedBranch("__new__");
|
setSelectedBranch("__new__");
|
||||||
setNewBranchName("");
|
setNewBranchName("");
|
||||||
|
} else if (value === "__manual__") {
|
||||||
|
setIsNewBranch(true);
|
||||||
|
setSelectedBranch("__manual__");
|
||||||
|
setNewBranchName("");
|
||||||
} else {
|
} else {
|
||||||
setIsNewBranch(false);
|
setIsNewBranch(false);
|
||||||
setSelectedBranch(value);
|
setSelectedBranch(value);
|
||||||
@@ -156,7 +149,10 @@ export function WorkspaceCreateForm({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fetching) {
|
/* Show single combined error */
|
||||||
|
const displayError = error || git.error;
|
||||||
|
|
||||||
|
if (fetchingProjects) {
|
||||||
return (
|
return (
|
||||||
<div className="card workspace-create-inline">
|
<div className="card workspace-create-inline">
|
||||||
<p className="muted">Loading projects...</p>
|
<p className="muted">Loading projects...</p>
|
||||||
@@ -164,6 +160,9 @@ export function WorkspaceCreateForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const branchSelectDisabled =
|
||||||
|
!selectedRepo || submitting || (git.loading && git.branches.length === 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card workspace-create-inline">
|
<div className="card workspace-create-inline">
|
||||||
<h3>
|
<h3>
|
||||||
@@ -176,7 +175,10 @@ export function WorkspaceCreateForm({
|
|||||||
<label>Project</label>
|
<label>Project</label>
|
||||||
<select
|
<select
|
||||||
value={selectedProject}
|
value={selectedProject}
|
||||||
onChange={(e) => setSelectedProject(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setSelectedProject(e.target.value);
|
||||||
|
setSelectedBranch("");
|
||||||
|
}}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option value="">Select project...</option>
|
<option value="">Select project...</option>
|
||||||
@@ -195,7 +197,10 @@ export function WorkspaceCreateForm({
|
|||||||
<label>Repository</label>
|
<label>Repository</label>
|
||||||
<select
|
<select
|
||||||
value={selectedRepo}
|
value={selectedRepo}
|
||||||
onChange={(e) => setSelectedRepo(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setSelectedRepo(e.target.value);
|
||||||
|
setSelectedBranch("");
|
||||||
|
}}
|
||||||
required
|
required
|
||||||
disabled={!selectedProject || repos.length === 0}
|
disabled={!selectedProject || repos.length === 0}
|
||||||
>
|
>
|
||||||
@@ -231,53 +236,52 @@ export function WorkspaceCreateForm({
|
|||||||
<label>
|
<label>
|
||||||
<Icon name="branch" size="sm" /> Branch
|
<Icon name="branch" size="sm" /> Branch
|
||||||
</label>
|
</label>
|
||||||
{branches.length > 0 ? (
|
<select
|
||||||
<>
|
value={selectedBranch}
|
||||||
<select
|
onChange={(e) => handleBranchChange(e.target.value)}
|
||||||
value={selectedBranch}
|
required
|
||||||
onChange={(e) => handleBranchChange(e.target.value)}
|
disabled={branchSelectDisabled}
|
||||||
required
|
>
|
||||||
disabled={!selectedRepo || submitting}
|
<option value="">
|
||||||
>
|
{git.loading && git.branches.length === 0
|
||||||
<option value="">Select branch...</option>
|
? "Loading branches..."
|
||||||
{branches.map((b) => (
|
: !selectedRepo
|
||||||
<option key={b} value={b}>
|
? "Select a repository first"
|
||||||
{b}
|
: "Select branch..."}
|
||||||
</option>
|
</option>
|
||||||
))}
|
|
||||||
<option value="__new__">+ Create new branch...</option>
|
{git.branches.map((b) => (
|
||||||
</select>
|
<option key={b} value={b}>
|
||||||
{isNewBranch && (
|
{b}
|
||||||
<input
|
{b === git.defaultBranch ? " (default)" : ""}
|
||||||
type="text"
|
</option>
|
||||||
value={newBranchName}
|
))}
|
||||||
onChange={(e) => setNewBranchName(e.target.value)}
|
|
||||||
placeholder="new-branch-name"
|
<option value="__new__">+ Create new branch...</option>
|
||||||
required
|
|
||||||
style={{ marginTop: "0.5rem" }}
|
{/* If branch fetch failed, offer manual entry */}
|
||||||
disabled={submitting}
|
{git.error && git.branches.length === 0 && (
|
||||||
/>
|
<option value="__manual__">✎ Enter branch name manually...</option>
|
||||||
)}
|
)}
|
||||||
</>
|
</select>
|
||||||
) : (
|
|
||||||
|
{/* New branch text input */}
|
||||||
|
{isNewBranch && (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={isNewBranch ? newBranchName : selectedBranch}
|
value={newBranchName}
|
||||||
onChange={(e) => {
|
onChange={(e) => setNewBranchName(e.target.value)}
|
||||||
setIsNewBranch(true);
|
placeholder="new-branch-name"
|
||||||
setNewBranchName(e.target.value);
|
|
||||||
setSelectedBranch("__new__");
|
|
||||||
}}
|
|
||||||
placeholder="main"
|
|
||||||
required
|
required
|
||||||
disabled={!selectedRepo || submitting}
|
style={{ marginTop: "0.5rem" }}
|
||||||
|
disabled={submitting}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{displayError && (
|
||||||
<div className="form-error" style={{ gridColumn: "1 / -1" }}>
|
<div className="form-error" style={{ gridColumn: "1 / -1" }}>
|
||||||
{error}
|
{displayError}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
/** Unified hook for git repository operations.
|
||||||
|
*
|
||||||
|
* Centralizes branch fetching, status, history, and git actions
|
||||||
|
* so components don't duplicate this logic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import {
|
||||||
|
listRepositoryBranches,
|
||||||
|
getRepositoryStatus,
|
||||||
|
getRepositoryHistory,
|
||||||
|
getCommitDetail,
|
||||||
|
commitChanges,
|
||||||
|
pushRepository,
|
||||||
|
pullRepository,
|
||||||
|
fetchRepository,
|
||||||
|
checkoutBranch,
|
||||||
|
createBranch,
|
||||||
|
deleteBranch,
|
||||||
|
mergeBranches,
|
||||||
|
type Branch,
|
||||||
|
type CommitHistoryResponse,
|
||||||
|
type CommitDetail,
|
||||||
|
} from "../api/git_repositories";
|
||||||
|
|
||||||
|
export interface GitStatus {
|
||||||
|
branch: string;
|
||||||
|
modified: string[];
|
||||||
|
added: string[];
|
||||||
|
deleted: string[];
|
||||||
|
untracked: string[];
|
||||||
|
renamed: string[];
|
||||||
|
ahead: number;
|
||||||
|
behind: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseGitRepoResult {
|
||||||
|
/** Available branch names. */
|
||||||
|
branches: string[];
|
||||||
|
/** The repo's default branch. */
|
||||||
|
defaultBranch: string;
|
||||||
|
/** Current working-directory status. */
|
||||||
|
status: GitStatus | null;
|
||||||
|
/** Commit history. */
|
||||||
|
history: CommitHistoryResponse | null;
|
||||||
|
/** Selected commit detail. */
|
||||||
|
commitDetail: CommitDetail | null;
|
||||||
|
/** True while any async operation is in flight. */
|
||||||
|
loading: boolean;
|
||||||
|
/** Error message from the last failed operation. */
|
||||||
|
error: string | null;
|
||||||
|
/** Refresh branches list. */
|
||||||
|
refreshBranches: () => Promise<void>;
|
||||||
|
/** Refresh working-directory status. */
|
||||||
|
refreshStatus: () => Promise<void>;
|
||||||
|
/** Refresh commit history. */
|
||||||
|
refreshHistory: (branch?: string, limit?: number) => Promise<void>;
|
||||||
|
/** Fetch a single commit's details. */
|
||||||
|
loadCommitDetail: (hash: string) => Promise<void>;
|
||||||
|
/** Stage + commit changes. */
|
||||||
|
commit: (message: string, files?: string[]) => Promise<void>;
|
||||||
|
/** Push current branch (or named branch) to remote. */
|
||||||
|
push: (branch?: string) => Promise<void>;
|
||||||
|
/** Pull from remote. */
|
||||||
|
pull: (branch?: string) => Promise<void>;
|
||||||
|
/** Fetch from remote. */
|
||||||
|
fetch: () => Promise<void>;
|
||||||
|
/** Checkout an existing branch. */
|
||||||
|
checkout: (branch: string) => Promise<void>;
|
||||||
|
/** Create and checkout a new branch. */
|
||||||
|
createBranch: (name: string, baseBranch?: string) => Promise<void>;
|
||||||
|
/** Delete a branch. */
|
||||||
|
deleteBranch: (name: string, force?: boolean) => Promise<void>;
|
||||||
|
/** Merge source into current (or target) branch. */
|
||||||
|
merge: (sourceBranch: string, targetBranch?: string, message?: string) => Promise<void>;
|
||||||
|
/** Clear the current error. */
|
||||||
|
clearError: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGitRepo(
|
||||||
|
projectId: string | undefined,
|
||||||
|
repoId: string | undefined,
|
||||||
|
): UseGitRepoResult {
|
||||||
|
const [branches, setBranches] = useState<string[]>([]);
|
||||||
|
const [defaultBranch, setDefaultBranch] = useState("");
|
||||||
|
const [status, setStatus] = useState<GitStatus | null>(null);
|
||||||
|
const [history, setHistory] = useState<CommitHistoryResponse | null>(null);
|
||||||
|
const [commitDetail, setCommitDetail] = useState<CommitDetail | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const withLoading = useCallback(
|
||||||
|
async <T,>(fn: () => Promise<T>): Promise<T> => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : "Git operation failed";
|
||||||
|
setError(msg);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshBranches = useCallback(async () => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
const data = await withLoading(() =>
|
||||||
|
listRepositoryBranches(projectId, repoId),
|
||||||
|
);
|
||||||
|
setBranches(data.branches.map((b: Branch) => b.name));
|
||||||
|
setDefaultBranch(data.default_branch ?? "");
|
||||||
|
}, [projectId, repoId, withLoading]);
|
||||||
|
|
||||||
|
const refreshStatus = useCallback(async () => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
const data = await withLoading(() =>
|
||||||
|
getRepositoryStatus(projectId, repoId),
|
||||||
|
);
|
||||||
|
setStatus({
|
||||||
|
branch: data.branch,
|
||||||
|
modified: data.modified,
|
||||||
|
added: data.added,
|
||||||
|
deleted: data.deleted,
|
||||||
|
untracked: data.untracked,
|
||||||
|
renamed: data.renamed ?? [],
|
||||||
|
ahead: data.ahead,
|
||||||
|
behind: data.behind,
|
||||||
|
});
|
||||||
|
}, [projectId, repoId, withLoading]);
|
||||||
|
|
||||||
|
const refreshHistory = useCallback(
|
||||||
|
async (branch?: string, limit = 50) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
const data = await withLoading(() =>
|
||||||
|
getRepositoryHistory(projectId, repoId, branch, limit),
|
||||||
|
);
|
||||||
|
setHistory(data);
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadCommitDetail = useCallback(
|
||||||
|
async (hash: string) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
const data = await withLoading(() =>
|
||||||
|
getCommitDetail(projectId, repoId, hash),
|
||||||
|
);
|
||||||
|
setCommitDetail(data);
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading],
|
||||||
|
);
|
||||||
|
|
||||||
|
const commit = useCallback(
|
||||||
|
async (message: string, files?: string[]) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() =>
|
||||||
|
commitChanges(projectId, repoId, message, files),
|
||||||
|
);
|
||||||
|
await refreshStatus();
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading, refreshStatus],
|
||||||
|
);
|
||||||
|
|
||||||
|
const push = useCallback(
|
||||||
|
async (branch?: string) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() => pushRepository(projectId, repoId, branch));
|
||||||
|
await refreshStatus();
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading, refreshStatus],
|
||||||
|
);
|
||||||
|
|
||||||
|
const pull = useCallback(
|
||||||
|
async (branch?: string) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() => pullRepository(projectId, repoId, branch));
|
||||||
|
await refreshStatus();
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading, refreshStatus],
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetch = useCallback(async () => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() => fetchRepository(projectId, repoId));
|
||||||
|
await refreshStatus();
|
||||||
|
}, [projectId, repoId, withLoading, refreshStatus]);
|
||||||
|
|
||||||
|
const checkout = useCallback(
|
||||||
|
async (branch: string) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() => checkoutBranch(projectId, repoId, branch));
|
||||||
|
await refreshStatus();
|
||||||
|
await refreshBranches();
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading, refreshStatus, refreshBranches],
|
||||||
|
);
|
||||||
|
|
||||||
|
const createBranchFn = useCallback(
|
||||||
|
async (name: string, baseBranch = "HEAD") => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() =>
|
||||||
|
createBranch(projectId, repoId, name, baseBranch),
|
||||||
|
);
|
||||||
|
await refreshBranches();
|
||||||
|
await refreshStatus();
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading, refreshBranches, refreshStatus],
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteBranchFn = useCallback(
|
||||||
|
async (name: string, force = false) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() => deleteBranch(projectId, repoId, name, force));
|
||||||
|
await refreshBranches();
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading, refreshBranches],
|
||||||
|
);
|
||||||
|
|
||||||
|
const merge = useCallback(
|
||||||
|
async (sourceBranch: string, targetBranch?: string, message?: string) => {
|
||||||
|
if (!projectId || !repoId) return;
|
||||||
|
await withLoading(() =>
|
||||||
|
mergeBranches(projectId, repoId, sourceBranch, targetBranch, message),
|
||||||
|
);
|
||||||
|
await refreshStatus();
|
||||||
|
await refreshHistory();
|
||||||
|
},
|
||||||
|
[projectId, repoId, withLoading, refreshStatus, refreshHistory],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Auto-refresh branches when projectId/repoId become valid
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId && repoId) {
|
||||||
|
void refreshBranches();
|
||||||
|
} else {
|
||||||
|
setBranches([]);
|
||||||
|
setDefaultBranch("");
|
||||||
|
}
|
||||||
|
}, [projectId, repoId, refreshBranches]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
branches,
|
||||||
|
defaultBranch,
|
||||||
|
status,
|
||||||
|
history,
|
||||||
|
commitDetail,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refreshBranches,
|
||||||
|
refreshStatus,
|
||||||
|
refreshHistory,
|
||||||
|
loadCommitDetail,
|
||||||
|
commit,
|
||||||
|
push,
|
||||||
|
pull,
|
||||||
|
fetch,
|
||||||
|
checkout,
|
||||||
|
createBranch: createBranchFn,
|
||||||
|
deleteBranch: deleteBranchFn,
|
||||||
|
merge,
|
||||||
|
clearError: () => setError(null),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,10 +10,7 @@ import {
|
|||||||
type ProjectCreateInput,
|
type ProjectCreateInput,
|
||||||
type ProjectUpdateInput,
|
type ProjectUpdateInput,
|
||||||
} from "../api/projects";
|
} from "../api/projects";
|
||||||
import {
|
import { deleteWorkspace, syncWorkspace } from "../api/workspaces";
|
||||||
deleteWorkspace,
|
|
||||||
syncWorkspace,
|
|
||||||
} from "../api/workspaces";
|
|
||||||
import {
|
import {
|
||||||
EmptyState,
|
EmptyState,
|
||||||
ErrorState,
|
ErrorState,
|
||||||
@@ -111,8 +108,6 @@ export const ProjectsPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSyncWorkspace = async (
|
const handleSyncWorkspace = async (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
repoId: string,
|
repoId: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user