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
39 changes: 36 additions & 3 deletions packages/livekit/src/livestore/task-queries.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { parseGitOriginUrl } from "@getpochi/common/git-utils";
import { Schema, queryDb, sql } from "@livestore/livestore";
import { tables } from "./default-schema";

export const makeTasksQuery = (cwd: string) =>
queryDb(
{
query: sql`select * from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' like '${cwd}/.git/worktrees%') order by updatedAt desc`,
query: sql`select * from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' = '${cwd}') order by updatedAt desc`,
schema: Schema.Array(tables.tasks.rowSchema),
},
{
Expand All @@ -21,7 +22,7 @@ export const makeTasksQuery = (cwd: string) =>
export const makeTasksWithLimitQuery = (cwd: string, limit: number) => {
return queryDb(
{
query: sql`select * from tasks where parentId is null and cwd = '${cwd}' order by updatedAt desc limit ${limit}`,
query: sql`select * from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' = '${cwd}') order by updatedAt desc limit ${limit}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert, querying only with the cwd is sufficient

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image actually, when revert to only query by 'cwd`, the pannel can't retrive the tasks of deleted worktree

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liangfung briefly, to reproduce this bug, you can : let's say your current workspace=xx/main, then create worktree(eg: br-1) with vscode, and DO NOT switch to this worktree; then, hover on the worktree label on Pochi's panel, click the revealed menu btn "create task" on the worktree, hence, the new task was created with : cwd = current workspace(xx/main), and git.worktree.gitdir = xx/br-1, later you can delete the worktree br-1.
so that when you fetch deleted tasks, query by cwd= xx/br-1 won't get the task.

schema: Schema.Array(tables.tasks.rowSchema),
},
{
Expand All @@ -38,7 +39,7 @@ export const makeTasksWithLimitQuery = (cwd: string, limit: number) => {
export const makeTasksCountQuery = (cwd: string) => {
return queryDb(
{
query: sql`select COUNT(*) as count from tasks where parentId is null and cwd = '${cwd}'`,
query: sql`select COUNT(*) as count from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' = '${cwd}')`,
schema: Schema.Array(Schema.Struct({ count: Schema.Number })),
},
{
Expand All @@ -47,3 +48,35 @@ export const makeTasksCountQuery = (cwd: string) => {
},
);
};

export const makeNonCwdWorktreesQuery = (
cwd: string,
activeWorktrees?: { path: string }[],
origin?: string,
) => {
const activePathsStr = activeWorktrees?.length
? activeWorktrees.map((p) => `'${p.path}'`).join(", ")
: "";
const worktreeFilter = activePathsStr
? `and cwd not in (${activePathsStr})`
: "";
const originFilter = origin
? `and git->>'$.origin' = '${parseGitOriginUrl(origin)?.webUrl}'`
: "";

return queryDb(
{
query: sql`select distinct git->>'$.worktree.gitdir' as path, git->>'$.branch' as branch from tasks where parentId is null and cwd != '${cwd}' ${worktreeFilter} ${originFilter} and git->>'$.worktree.gitdir' is not null`,
schema: Schema.Array(
Schema.Struct({
path: Schema.String,
branch: Schema.String,
}),
),
},
{
label: "tasks.activePaths.worktrees",
deps: [origin, activePathsStr],
},
);
};
65 changes: 61 additions & 4 deletions packages/vscode-webui/src/components/worktree-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "@/components/ui/tooltip";
import { useSelectedModels } from "@/features/settings";
import { useCurrentWorkspace } from "@/lib/hooks/use-current-workspace";
import { useDeletedWorktrees } from "@/lib/hooks/use-deleted-worktrees";
import { usePochiTabs } from "@/lib/hooks/use-pochi-tabs";
import { useWorktrees } from "@/lib/hooks/use-worktrees";
import { cn } from "@/lib/utils";
Expand Down Expand Up @@ -65,7 +66,7 @@ interface WorktreeGroup {
isMain: boolean;
createdAt?: number;
branch?: string;
data: GitWorktree["data"];
data?: GitWorktree["data"];
}

export function WorktreeList({
Expand All @@ -77,6 +78,7 @@ export function WorktreeList({
deletingWorktreePaths: Set<string>;
onDeleteWorktree: (worktreePath: string) => void;
}) {
const { t } = useTranslation();
const { data: currentWorkspace, isLoading: isLoadingCurrentWorkspace } =
useCurrentWorkspace();
const {
Expand All @@ -85,6 +87,7 @@ export function WorktreeList({
gitOriginUrl,
isLoading: isLoadingWorktrees,
} = useWorktrees();
const [showDeleted, setShowDeleted] = useState(false);

const groups = useMemo(() => {
if (isLoadingWorktrees || isLoadingCurrentWorkspace) {
Expand Down Expand Up @@ -147,18 +150,70 @@ export function WorktreeList({
return groups.filter((x) => !deletingWorktreePaths.has(x.path));
}, [groups, deletingWorktreePaths]);

const { deletedWorktrees } = useDeletedWorktrees({
cwd,
origin: gitOriginUrl,
activeWorktrees: worktrees,
});
const deletedGroups = useMemo(() => {
return R.pipe(
deletedWorktrees,
R.map((wt): WorktreeGroup => {
const name = getWorktreeNameFromWorktreePath(wt.path) || "unknown";

return {
path: wt.path,
createdAt: 0,
name,
isMain: false,
branch: wt.branch,
};
}),
);
}, [deletedWorktrees]);
return (
<div className="flex flex-col gap-1">
{optimisticGroups.map((group) => (
<WorktreeSection
isLoadingWorktrees={isLoadingWorktrees}
key={group.path}
group={group}
onDeleteGroup={onDeleteWorktree}
gitOriginUrl={gitOriginUrl}
gh={gh}
/>
))}
{deletedGroups.length > 0 && (
<>
<div className="group flex items-center py-2">
<div className="h-px flex-1 bg-border" />
<Button
variant="ghost"
size="sm"
className="mx-2 h-auto gap-2 py-0 text-muted-foreground text-xs hover:bg-transparent"
onClick={() => setShowDeleted(!showDeleted)}
>
<Trash2 className="size-3" />
<span className="w-0 overflow-hidden whitespace-nowrap transition-all group-hover:w-auto">
{showDeleted
? t("tasksPage.hideDeletedWorktrees")
: t("tasksPage.showDeletedWorktrees")}
</span>
</Button>
<div className="h-px flex-1 bg-border" />
</div>

{showDeleted &&
deletedGroups.map((group) => (
<WorktreeSection
key={group.path}
group={group}
gh={gh}
isDeleted={true}
gitOriginUrl={gitOriginUrl}
/>
))}
</>
)}
</div>
);
}
Expand All @@ -168,9 +223,10 @@ function WorktreeSection({
onDeleteGroup,
gh,
gitOriginUrl,
isDeleted,
}: {
group: WorktreeGroup;
isLoadingWorktrees: boolean;
isDeleted?: boolean;
onDeleteGroup?: (worktreePath: string) => void;
gh?: { installed: boolean; authorized: boolean };
gitOriginUrl?: string | null;
Expand Down Expand Up @@ -222,7 +278,7 @@ function WorktreeSection({
{prefixWorktreeName(group.name)}
</span>

<div className="mt-[1px] flex-1">
<div className={cn("mt-[1px] flex-1", isDeleted ? "hidden" : "")}>
{pullRequest ? (
<PrStatusDisplay
prNumber={pullRequest.id}
Expand All @@ -245,6 +301,7 @@ function WorktreeSection({
!isHovered && !showDeleteConfirm
? "pointer-events-none opacity-0"
: "opacity-100",
isDeleted ? "hidden" : "",
)}
>
<>
Expand Down
26 changes: 26 additions & 0 deletions packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { taskCatalog } from "@getpochi/livekit";
import { useStore } from "@livestore/react";
import { useMemo } from "react";

interface DeletedWorktreesOptions {
cwd: string;
origin?: string | null;
activeWorktrees?: { path: string }[];
}
export function useDeletedWorktrees({
cwd,
origin,
activeWorktrees,
}: DeletedWorktreesOptions) {
const { store } = useStore();

const worktreeQuery = useMemo(() => {
return taskCatalog.queries.makeNonCwdWorktreesQuery(
cwd,
activeWorktrees,
origin ?? "",
);
}, [cwd, activeWorktrees, origin]);
const deletedWorktrees = store.useQuery(worktreeQuery);
return { deletedWorktrees };
}